sast-rce について
sast-rceは、コードベースのリモートコード実行脆弱性を調べるセキュリティ評価スキルです。危険な実行箇所を探し、ユーザー入力の到達経路を並列で追跡して結果を統合します。実行には `sast/architecture.md` が必要です。
主な機能
- 01コマンド実行などの危険箇所の検出
- 02コードインジェクションと危険なデシリアライズの確認
- 03ユーザー入力から実行箇所への経路を並列追跡
- 04分析結果を統合してレポート化
- 05RCEと他の脆弱性分類の区別
活用シーン
- 01コードベースのコマンドインジェクションリスク確認
- 02evalや動的コード実行経路の調査
- 03ユーザーデータのデシリアライズリスク分析
- 04バッチ分析で得たセキュリティ所見の統合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-rceインストール後、Claude Code で/sast-rceを入力して読み込まれたか確認します。
詳細
リモートコード実行(RCE)の検出
コードベースにおけるリモートコード実行の脆弱性を見つけることに重点を置いたセキュリティ評価を実施します。このスキルでは、サブエージェントを使用した3段階のアプローチを採用します。recon(危険な実行シンクを見つける)、batched verify(ユーザー入力が各シンクに到達するかどうかを、3件ずつ並列に追跡して検証する)、merge(バッチごとの結果をまとめて最終レポートを作成する)。
前提条件:sast/architecture.md が存在する必要があります。存在しない場合は、先に分析スキルを実行してください。
リモートコード実行とは
リモートコード実行(RCE)は、攻撃者が制御する任意の OS コマンドまたはアプリケーションレベルのコードを、攻撃者がアプリケーションに実行させられる場合に発生します。これは通常、最も深刻度の高い脆弱性クラスであり、多くの場合、サーバーの完全な侵害につながります。
RCE は、主に次の3つの根本原因によって発生します。
- OS コマンドインジェクション:ユーザー入力が OS コマンド文字列に安全でない方法で埋め込まれ、シェルのメタ文字によって追加コマンドを注入できる状態です。
- コードインジェクション(eval 系):ユーザー入力が、実行可能なコードとして解釈する関数(
eval、exec、Function()など)に渡されます。 - 安全でないデシリアライゼーション:ユーザーが提供したシリアライズ済みデータが、ガジェットを悪用できるデシリアライザーでデシリアライズされ、細工されたペイロードによって任意のコード実行が引き起こされます。
RCE に該当するもの
- シェルによる解釈が有効な状態で、ユーザー入力を直接または間接的に OS コマンド実行関数に渡すこと
- ユーザーが制御する文字列を使用して、
eval()、exec()、Function()または同等の構文を使うこと - ユーザーが提供したバイト列/文字列を、本質的に安全でないデシリアライザー(pickle、PHP の unserialize、Java のネイティブシリアライゼーション、Ruby の Marshal など)でデシリアライズすること
- ユーザーが提供したコンテンツに対して、安全なローダーを使わずに
yaml.load()を使用すること - ユーザーが制御するモジュールパスで動的に
require()/import()すること - PHP のファイルインクルード(
include/require)で、ユーザーが制御するパスを使用すること
RCE に該当しないもの
次の項目は RCE として報告しないでください。
- SSRF:攻撃者が制御する URL に HTTP リクエストを送信すること — 別の脆弱性クラスです(コード実行はありません)
- パストラバーサル:任意のファイルを読み取り/書き込みすること — 別のクラスです(読み取ったファイルが実行またはデシリアライズされる場合を除きます)
- SSTI:テンプレートエンジンを介したテンプレートインジェクション — 関連はありますが別のクラスです。RCE ではなく SSTI として報告してください
- XSS:被害者のブラウザーで JavaScript を実行すること — クライアント側に限られ、サーバー側の RCE ではありません
- SQL インジェクション:データベースクエリに値を注入すること — 別のクラスです(
xp_cmdshellによって OS コマンド実行につながる場合でも、SQLi として報告してください) - 安全な subprocess のリスト形式呼び出し:リストを使い、
shell=Trueを指定せずにsubprocess.run(["ls", user_arg])を呼び出すこと — 引数はシェル展開されずに OS に直接渡されるため、コマンドインジェクションに対して脆弱ではありません - 安全なデシリアライゼーション:
json.loads()、yaml.safe_load()、xml.etree.ElementTree.parse()— これらの形式にはコード実行の意味論がありません
RCE を防ぐパターン
次のパターンが見られる場合、そのコードは脆弱ではない可能性が高いです。
1. シェルによる解釈を伴わない subprocess のリスト形式
# Python — list args, no shell=True
subprocess.run(["convert", "-resize", size, input_file, output_file])
subprocess.Popen(["git", "clone", repo_url])
# Node.js — spawn with separate args (no shell)
child_process.spawn("ffmpeg", ["-i", inputFile, outputFile])
# Java — ProcessBuilder with list
new ProcessBuilder("ls", "-la", dir).start()
# Ruby — system() with multiple args (not a single interpolated string)
system("ffmpeg", "-i", "input.mp4", "-f", format, "output")
2. 安全なデシリアライゼーション形式
# Python — JSON instead of pickle
import json
data = json.loads(user_input) # no code execution semantics
# Python — safe YAML loader
import yaml
data = yaml.safe_load(user_input) # restricts to basic types only
# Java — Jackson without enableDefaultTyping, with concrete target type
ObjectMapper mapper = new ObjectMapper();
MyClass obj = mapper.readValue(json, MyClass.class); # safe
3. コマンド構築前の厳密な許可リスト
# Python — allowlist for dynamic arguments
ALLOWED_FORMATS = {"png", "jpg", "webp"}
if fmt not in ALLOWED_FORMATS:
return abort(400)
subprocess.run(["convert", infile, f"output.{fmt}"])
# Node.js — allowlist for dynamic args
const ALLOWED_COMMANDS = ['ls', 'pwd'];
if (!ALLOWED_COMMANDS.includes(cmd)) return res.status(400).end();
spawn(cmd, []);
脆弱な例と安全な例
OS コマンドインジェクション — Python
# VULNERABLE: shell=True with f-string
@app.route('/ping')
def ping():
host = request.args.get('host')
result = subprocess.run(f"ping -c 1 {host}", shell=True, capture_output=True, text=True)
return result.stdout
# Payload: ?host=127.0.0.1;id → executes "id"
# VULNERABLE: os.system with string formatting
def convert_image(filename):
size = request.form.get('size')
os.system(f"convert {filename} -resize {size} output.jpg")
# SECURE: list-form subprocess, no shell
@app.route('/ping')
def ping():
host = request.args.get('host')
result = subprocess.run(["ping", "-c", "1", host], capture_output=True, text=True, timeout=5)
return result.stdout
OS コマンドインジェクション — Node.js
// VULNERABLE: exec with template literal
app.get('/search', (req, res) => {
const query = req.query.q;
exec(`grep -r "${query}" /var/log/app/`, (err, stdout) => {
res.send(stdout);
});
});
// Payload: ?q=foo" /etc/passwd "
// VULNERABLE: execSync with concatenation
function runScript(userScript) {
return execSync('node scripts/' + userScript);
}
// SECURE: spawn with separate args
app.get('/search', (req, res) => {
const query = req.query.q;
const proc = spawn('grep', ['-r', query, '/var/log/app/']);
proc.stdout.on('data', (data) => res.write(data));
proc.on('close', () => res.end());
});
OS コマンドインジェクション — PHP
// VULNERABLE: shell_exec with user input
function generateThumbnail($file) {
$size = $_GET['size'];
shell_exec("convert {$file} -resize {$size} thumb.jpg");
}
// VULNERABLE: backtick operator
function checkHost() {
$host = $_POST['host'];
$result = `ping -c 1 $host`;
return $result;
}
// SECURE: escapeshellarg (reduces risk — but prefer removing shell entirely)
function generateThumbnail($file) {
$size = escapeshellarg($_GET['size']);
$file = escapeshellarg($file);
shell_exec("convert $file -resize $size thumb.jpg");
}
OS コマンドインジェクション — Ruby
# VULNERABLE: string interpolation in system()
get '/convert' do
format = params[:format]
system("ffmpeg -i input.mp4 -f #{format} output")
end
# VULNERABLE: backtick with user input
def check_dns
`nslookup #{params[:host]}`
end
# SECURE: system() with separate args (no shell expansion)
get '/convert' do
format = params[:format]
ALLOWED = %w[mp4 avi mkv]
return 400 unless ALLOWED.include?(format)
system("ffmpeg", "-i", "input.mp4", "-f", format, "output")
end
コードインジェクション — Python の eval/exec
# VULNERABLE: eval with user input
@app.route('/calculate')
def calculate():
expr = request.args.get('expr')
result = eval(expr) # attacker can run __import__('os').system('id')
return str(result)
# VULNERABLE: exec with user code
@app.route('/run')
def run_code():
code = request.json.get('code')
exec(code) # full arbitrary code execution
return "ok"
# SECURE: ast.literal_eval for safe expression parsing (literals only)
from ast import literal_eval
@app.route('/parse')
def parse():
data = request.args.get('data')
result = literal_eval(data) # only parses strings/numbers/lists/dicts/bools
return str(result)
コードインジェクション — JavaScript の eval/Function
// VULNERABLE: eval with user input
app.post('/formula', (req, res) => {
const formula = req.body.formula;
const result = eval(formula); // RCE: process.exit(), require('child_process')...
res.json({ result });
});
// VULNERABLE: new Function() constructor
function compute(userExpression) {
const fn = new Function('x', `return ${userExpression}`);
return fn(42);
}
// VULNERABLE: vm.runInNewContext (sandbox escape via __proto__ pollution)
const vm = require('vm');
app.post('/eval', (req, res) => {
const result = vm.runInNewContext(req.body.code);
res.json({ result });
});
// SECURE: use a math expression library (no arbitrary code)
const { evaluate } = require('mathjs');
app.post('/formula', (req, res) => {
const result = evaluate(req.body.formula); // sandboxed math expressions only
res.json({ result });
});
安全でないデシリアライゼーション — Python の pickle
# VULNERABLE: deserializing user-supplied pickle data
@app.route('/load', methods=['POST'])
def load_session():
data = request.get_data()
session = pickle.loads(data) # attacker controls __reduce__ → RCE
return jsonify(session)
# VULNERABLE: base64-encoded pickle from cookie
@app.route('/profile')
def profile():
session_cookie = request.cookies.get('session')
data = base64.b64decode(session_cookie)
user = pickle.loads(data) # crafted cookie → arbitrary code at deserialization
return render_template('profile.html', user=user)
# SECURE: use JSON (no code execution semantics)
@app.route('/profile')
def profile():
session_cookie = request.cookies.get('session')
user = json.loads(base64.b64decode(session_cookie))
return render_template('profile.html', user=user)
安全でないデシリアライゼーション — Java
// VULNERABLE: ObjectInputStream.readObject() on user-supplied stream
@PostMapping("/deserialize")
public ResponseEntity<?> deserialize(@RequestBody byte[] data) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object obj = ois.readObject(); // gadget chains (Commons Collections, Spring, etc.) → RCE
return ResponseEntity.ok(obj);
}
// VULNERABLE: Jackson with enableDefaultTyping
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(); // attacker specifies arbitrary class type in JSON → RCE
MyData data = mapper.readValue(userJson, MyData.class);
// SECURE: Jackson with concrete type, no enableDefaultTyping
ObjectMapper mapper = new ObjectMapper();
MyData data = mapper.readValue(userJson, MyData.class); // safe with concrete target type
安全でないデシリアライゼーション — PHP
// VULNERABLE: unserialize() with user input
function loadProfile() {
$data = base64_decode($_COOKIE['profile']);
$user = unserialize($data); // PHP object injection → POP chain → RCE
return $user;
}
// VULNERABLE: unserialize from POST body
$obj = unserialize($_POST['data']);
// SECURE: json_decode instead
function loadProfile() {
$data = base64_decode($_COOKIE['profile']);
$user = json_decode($data, true); // no code execution semantics
return $user;
}
安全でないデシリアライゼーション — Ruby の Marshal
# VULNERABLE: Marshal.load with user-supplied data
post '/restore' do
data = Base64.decode64(params[:state])
object = Marshal.load(data) # arbitrary Ruby object graph → RCE via gadgets
object.process
end
# SECURE: use JSON
post '/restore' do
data = JSON.parse(Base64.decode64(params[:state]))
# work with plain data structures only
end
安全でないデシリアライゼーション — Node.js
// VULNERABLE: node-serialize (known RCE via IIFE in serialized string)
const serialize = require('node-serialize');
app.post('/restore', (req, res) => {
const obj = serialize.unserialize(req.body.data); // IIFE payload → RCE
res.json(obj);
});
// VULNERABLE: js-yaml v3 yaml.load (executes JS functions in YAML tags)
const yaml = require('js-yaml');
const data = yaml.load(userInput); // !!js/function payload → RCE
// SECURE: yaml.safeLoad (v3) or FAILSAFE_SCHEMA (v4)
const data = yaml.safeLoad(userInput); // only loads plain data types
安全でない YAML — Python
# VULNERABLE: yaml.load without Loader
import yaml
data = yaml.load(user_input) # !!python/object/apply: payload → RCE
# SECURE: yaml.safe_load
data = yaml.safe_load(user_input) # only loads basic data types
実行
このスキルは、サブエージェントを使用して3つのフェーズで実行されます。sast/architecture.md の内容をコンテキストとしてすべてのサブエージェントに渡してください。
フェーズ1:危険な実行シンクを見つける
次の指示でサブエージェントを起動します。
目標:コードベース内で、OS コマンドが実行される場所、コードが動的に評価される場所、または安全でないデシリアライザーを使ってデータがデシリアライズされる場所をすべて見つけます。動的変数がこれらのシンクに渡されている場合は、その出所にかかわらず、すべて検出してください。結果を
sast/rce-recon.mdに書き込んでください。コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。これを使って、技術スタック、言語、フレームワーク、使用されているシリアライズパターンを把握してください。
カテゴリ 1 — OS コマンド実行シンク
コマンド文字列または引数が動的に構築される可能性がある、OS コマンドを実行する関数を探してください。危険な位置に定数ではない変数が含まれている場合は検出してください。
Python:
os.system(var)— 変数が含まれていれば、常に検出os.popen(var)— 変数が含まれていれば、常に検出subprocess.run(var, shell=True)、subprocess.call(var, shell=True)、subprocess.Popen(var, shell=True)、subprocess.check_output(var, shell=True)—shell=Trueかつコマンド文字列に変数が含まれる場合、またはコマンドが文字列(リストではない)で変数が含まれる場合に検出subprocess.run(f"cmd {var}")(shell=Trueなし) — 文字列(リストではない)を subprocess に渡すのは依然として危険なため、検出commands.getoutput(var)、commands.getstatusoutput(var)— 常に検出Node.js / JavaScript:
child_process.exec(var)、child_process.execSync(var)— コマンド文字列に変数が含まれる場合に検出child_process.execFile(var, ...)— コマンドまたは引数に変数が含まれる場合に検出child_process.spawn(var, ...)またはspawn(cmd, args)—shell: trueかつコマンドに変数が含まれる場合に検出shelljs.exec(var)、execa(var)— コマンドに変数が含まれる場合に検出PHP:
exec(var)、system(var)、passthru(var)、shell_exec(var)、popen(var, ...)、proc_open(var, ...)— コマンド文字列に変数が含まれる場合に検出- バッククォート演算子:
`...{$var}...`または`$var`— 常に検出Ruby:
system(var)、exec(var)、spawn(var)、IO.popen(var)、Open3.popen3(var)— 文字列形式で変数が埋め込まれている場合に検出- バッククォート演算子:
`...#{var}...`— 常に検出%x{...#{var}...}— 常に検出Java:
Runtime.getRuntime().exec(var)— 文字列引数に変数の連結が含まれる場合に検出new ProcessBuilder(var)または変数を含むリストから構築されたProcessBuilder— 検出Go:
exec.Command(var, ...)— コマンド名または引数が変数から動的に構築されている場合(特に外部入力を文字列分割したもの)に検出C# / .NET:
Process.Start(var)— FileName または Arguments が変数の場合に検出ProcessStartInfo { FileName = var, Arguments = var }— 検出
カテゴリ 2 — コード評価シンク
文字列を実行可能コードとして解釈する関数を探してください。
Python:
eval(var)— 引数が変数の場合に検出exec(var)— 引数が変数の場合に検出compile(var, ...)の後にexec()を呼び出す場合 — 検出importlib.import_module(var)、__import__(var)— モジュール名が変数の場合に検出JavaScript / Node.js:
eval(var)— 引数が変数の場合に検出new Function(var)、new Function('x', var)— 本体が変数の場合に検出setTimeout(var, delay)、setInterval(var, delay)— 第 1 引数が文字列変数の場合に検出vm.runInNewContext(var)、vm.runInContext(var)、vm.runInThisContext(var)— 変数の場合に検出require(var)— モジュールパスが変数の場合に検出(外部入力による動的な require → パストラバーサルおよびコード実行の可能性)PHP:
eval(var)— 引数に変数が含まれていれば、常に検出- パターンに
/e修飾子を含むpreg_replace(pattern, replacement, subject)— 常に検出- 文字列引数を取る
assert(var)— 変数の場合に検出create_function('', var)— 本体が変数の場合に検出call_user_func(var)、call_user_func_array(var, ...)— 関数名が変数の場合に検出Ruby:
eval(var)、instance_eval(var)、class_eval(var)、module_eval(var)— 変数の場合に検出binding.eval(var)— 変数の場合に検出
カテゴリ 3 — 安全でないデシリアライズシンク
外部から取得された可能性があるデータのデシリアライズを探してください。デシリアライズシンクはすべて検出してください。データがユーザー制御下にあるかどうかの判断は、フェーズ 2 の担当です。
Python:
pickle.loads(var)、pickle.load(file_var)— 常に検出(pickle は信頼できないデータに対して本質的に安全ではありません)marshal.loads(var)、marshal.load(file_var)— 常に検出- 明示的に
Loader=yaml.SafeLoaderを指定しないyaml.load(var)— 検出(安全なローダーを指定していないすべての形式)jsonpickle.decode(var)— 常に検出- 外部の影響を受けるキーを使った
shelveへのアクセスJava:
ObjectInputStream.readObject()、ObjectInputStream.readUnshared()— 常に検出XMLDecoder.readObject()— 常に検出XStream.fromXML(var)— 常に検出(XStream のセキュリティフィルターが明示的に設定されている場合を除く).enableDefaultTyping()または.activateDefaultTyping(...)が設定されたObjectMapper—readValue呼び出しを検出Kryo.readObject(var, ...)、Kryo.readClassAndObject(var)— 入力ストリームが外部ソースに由来する場合に検出PHP:
unserialize(var)— 引数が変数の場合、常に検出Ruby:
Marshal.load(var)、Marshal.restore(var)— 常に検出permitted_classes: []を指定していないYAML.load(var)(Psych)— 検出Node.js:
require('node-serialize').unserialize(var)— 常に検出yaml.load(var)(js-yaml v3 のデフォルトの安全でない読み込み)— 検出.NET:
BinaryFormatter.Deserialize(var)— 常に検出SoapFormatter.Deserialize(var)— 常に検出NetDataContractSerializer.ReadObject(var)— 検出JavaScriptSerializer.Deserialize(var)— 引数が変数の場合に検出LosFormatter.Deserialize(var)— 常に検出
スキップ対象(これらは安全なため、検出しないでください):
- リストを渡し、
shell=Trueを指定していないsubprocess.run(["cmd", arg1, arg2])— シェル展開はありませんjson.loads(var)、JSON.parse(var)、json_decode(var)— コードを実行しない安全な形式ですyaml.safe_load(var)またはyaml.load(var, Loader=yaml.SafeLoader)— 安全なローダーですast.literal_eval(var)— Python のリテラルのみを解析し、任意のコードは実行しません
出力形式 —
sast/rce-recon.mdに書き込んでください:# RCE 調査:[プロジェクト名] ## 概要 潜在的な RCE シンクが [N] 件見つかりました:OS コマンド [X] 件、コードインジェクション [Y] 件、安全でないデシリアライズ [Z] 件。 ## 検出されたシンク ### 1. [説明的な名前 — 例:「画像コンバーターの shell=True subprocess」] - **ファイル**:`path/to/file.ext`(行 X-Y) - **関数 / エンドポイント**:[関数名またはルート] - **カテゴリ**:[OS コマンドインジェクション / コードインジェクション / 安全でないデシリアライズ] - **シンク**:[危険な関数呼び出し — 例:subprocess.run(..., shell=True)] - **動的な引数**:`var_name` — [何を表していると思われるかについての簡単な注記] - **コードスニペット**:[シンク周辺の関連コード]
[シンクごとに繰り返す]
フェーズ 1 の後:続行する前に候補を確認する
フェーズ 1 が完了したら、sast/rce-recon.md を読みます。調査でシンクが 0 件だった場合(概要に「Found 0」と報告されている、または「Sinks Found」セクションが空か存在しない場合)は、フェーズ 2 とフェーズ 3 を完全にスキップします。代わりに、以下の内容を sast/rce-results.md に書き込み、sast/rce-recon.md を削除して、終了します。
# RCE Analysis Results
No vulnerabilities found.
フェーズ 1 で潜在的なシンクが 1 件以上見つかった場合に限り、フェーズ 2 に進みます。
フェーズ 2:ユーザー入力からシンクまでを追跡する(バッチ処理)
フェーズ 1 が完了したら、sast/rce-recon.md を読み、シンクを最大 3 件ずつのバッチに分割します(## Sinks Found の下にある番号付きセクション:### 1.、### 2. など)。バッチごとにサブエージェントを 1 つ並列で起動します。各サブエージェントは、割り当てられたシンクについてのみ汚染を追跡し、結果をそれぞれのバッチファイルに書き込みます。
バッチ処理の手順(サブエージェントではなく、あなた(オーケストレーター)が実行します):
sast/rce-recon.mdを読み、番号付きシンクセクション(### 1.、### 2.、...)の数を数えます。- 最大 3 件ずつのバッチに分割します。たとえば、シンクが 8 件の場合は 3 バッチ(1~3、4~6、7~8)に分けます。
- 各バッチについて、調査ファイルから該当するシンクセクションの全文を抽出します。
- 各サブエージェントには、割り当てられたシンクだけを渡して、すべてのバッチサブエージェントを並列で起動します。
- 各サブエージェントは
sast/rce-batch-N.mdに書き込みます。N は 1 から始まるバッチ番号です。 sast/architecture.mdからプロジェクトの主要な言語/フレームワークを特定し、上記の「脆弱な例と安全な例」セクションから該当する例だけを選択します。たとえば、Python が中心のプロジェクトの場合は、該当する Python の OS コマンド、eval、pickle、YAML のサブセクションを含めます。以下の[TECH-STACK EXAMPLES]が示す箇所に、選択した例を各サブエージェントへの指示に含めます。
各バッチサブエージェントには、以下の指示を与えます(バッチ固有の値を置き換えてください)。
目標:割り当てられた各 RCE シンクについて、ユーザーが指定した値が危険な引数に到達するかどうかを判断します。コード実行の脆弱性を見つけることが目標です。結果を
sast/rce-batch-[N].mdに記録してください。割り当てられたシンク(偵察フェーズより):
[割り当てられたシンクのセクション全文を、元の番号を維持してここに貼り付けてください]
コンテキスト:プロジェクトのアーキテクチャ概要が提供されます。アーキテクチャを使って、リクエストのエントリーポイント、ミドルウェア、アプリケーション内のデータフローを理解してください。
RCE の参考情報 — 確認する内容:
各シンクの動的引数を、その発生源までたどってください。RCE が成立するには、攻撃者が制御できるデータが危険なシンク(シェル解釈を伴う OS コマンド、eval のような実行、または安全でないデシリアライズ)に到達する必要があります。
RCE に該当しないもの — 次のものを RCE として報告しないでください:
- SSRF、パストラバーサル、SSTI、XSS、SQLi — これらは別の脆弱性クラスです(スキルの前文を参照)。
- シェルを使わない安全なサブプロセスのリスト形式:シェル展開なしで引数を渡す場合、コマンドインジェクションにはなりません。
- 安全な形式:
json.loads、yaml.safe_load、ast.literal_eval— コード実行のセマンティクスはありません。悪用を防止する緩和策 — 次の対策が存在し、有効であれば、そのシンクはおそらく安全です:
- シェルを使わないサブプロセスのリスト形式:
shell=Trueを指定せずにsubprocess.run(["cmd", var])を使用する場合、シェルのメタ文字によるインジェクションは発生しません。- 厳密な許可リスト:使用前に、安全な値だけからなる固定セットで検証します。
- 安全なデシリアライズ:JSON、
yaml.safe_load、デフォルトタイピングを使わない具象型指定の Jackson 読み込み。このプロジェクトの技術スタックにおける脆弱な例と安全な例:
[TECH-STACK EXAMPLES]
各シンクについて、動的引数を発生源までさかのぼってください:
直接的なユーザー入力 — 変数が変換を経ずにリクエストソースから直接代入されています:
- HTTP クエリパラメーター:
request.GET.get(...)、req.query.x、params[:x]、$_GET['x']、c.Query("x")- パスパラメーター:
request.path_params['id']、req.params.id、params[:id]- リクエストボディ/フォームフィールド:
request.POST.get(...)、req.body.x、params[:x]、$_POST['x']- HTTP ヘッダー:
request.headers.get(...)、req.headers['x']- Cookie:
request.COOKIES.get(...)、req.cookies.x- ファイルアップロードの内容:
request.files['file'].read()、req.file.buffer- WebSocket メッセージ、キュー/イベントのペイロード
間接的なユーザー入力 — 変数が変換、関数呼び出し、中間代入を通じてユーザー入力から導出されています。連鎖を最後までたどってください:
- 関数の戻り値から変数に代入されている場合 → その関数の引数の発生源を確認します
- 変数が関数の引数として渡されている場合 → 呼び出し元を確認します
- 変数が条件付きで代入されている場合 → すべての分岐を確認します
外部から影響を受けるデシリアライズデータ — デシリアライズシンクの場合、生のバイト列/文字列はネットワークソケット、HTTP リクエストボディ、Cookie、ファイルアップロード、または元々ユーザーが指定したデータベース値から来ていますか?外部から制御できるバイトストリームが安全でないデシリアライザーに渡される場合、悪用可能です。
サーバー側/ハードコード値 — 変数が設定、環境変数、ハードコードされた定数、または外部の影響を受けないサーバー側のロジックに由来する場合、悪用できません。
各シンクで確認する緩和策:
- 許可リストによる検証:使用前に、既知の安全な値の固定セットに対して変数を検証していますか?厳密かつ完全であれば、「脆弱性なし」と判定してください。
- 整数/型へのキャスト:
int/floatへのキャストは、このコンテキストで実際にインジェクションを防止しますか?引用符の問題がなく、引数が純粋な数値の場合にのみ有効です。- escapeshellarg / escapeshellcmd(PHP):リスクを軽減しますが、なくすわけではありません。シェルエスケープには、特定の状況で回避された過去があるため、「脆弱性の可能性あり」と判定してください。
- サブプロセスのリスト形式:
shell=Trueを指定せずにsubprocess.run(["cmd", var])を使用する場合、引数はシェル展開なしで OS に直接渡されます。これはコマンドインジェクションに対する有効な緩和策です(インジェクションについては「脆弱性なし」と判定してください。値はコマンドに渡されますが、新たなコマンドをインジェクトすることはできません)。- 安全なデシリアライザーが使用されている:
json.loads()、yaml.safe_load()などが使用されている場合は、スキップしてください(フェーズ 1 でフラグが付いているべきではありません)。分類:
- 脆弱性あり:有効な緩和策がなく、ユーザー入力が危険なシンクに到達することを確認できる場合。
- 脆弱性の可能性あり:ユーザー入力がシンクに到達する可能性が高い(間接的なフロー)か、弱い緩和策(シェルエスケープ、一部のみの検証、不明確な許可リスト)しかない場合。
- 脆弱性なし:引数がサーバー側のデータのみである、または有効な緩和策(サブプロセスのリスト形式、厳密な許可リスト、安全なデシリアライズ形式)が実施されている場合。
- 手動確認が必要:引数の発生源を確信をもって特定できない場合(不透明なヘルパーを経由する、複雑な条件分岐がある、または外部ライブラリを使用している場合)。
出力形式 —
sast/rce-batch-[N].mdに記録してください:# RCE バッチ [N] の結果 ## 検出結果 ### [脆弱性あり] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **カテゴリ**:[OS コマンドインジェクション/コードインジェクション/安全でないデシリアライズ] - **問題**:[例:「HTTP クエリパラメーター `host` が `shell=True` のサブプロセス呼び出しに直接渡される」] - **汚染トレース**:[エントリーポイントからシンクまでの手順 — 例:「request.args.get('host') → host → subprocess.run(f'ping -c 1 {host}', shell=True)」] - **影響**:[攻撃者ができること — 任意の OS コマンドの実行、/etc/passwd の読み取り、リバースシェルの確立、サーバーの完全な侵害など] - **修正方法**:[具体的な修正 — リスト形式の subprocess を使う、eval を安全な代替手段に置き換える、json.loads/yaml.safe_load に切り替えるなど] - **動的テスト**:[検出結果を確認するための curl コマンドまたはペイロード。 正確なパラメーターとペイロード、およびレスポンスで確認する内容を示してください。 例: curl "https://app.example.com/ping?host=127.0.0.1;id" curl "https://app.example.com/ping?host=127.0.0.1%3Bid" デシリアライズの場合:ysoserial または pickletools を使った悪意のあるペイロードの作成方法を示してください]
### [脆弱性の可能性あり] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **カテゴリ**:[OS コマンドインジェクション/コードインジェクション/安全でないデシリアライズ] - **問題**:[例:「ヘルパー関数を介してユーザー入力から取得されている可能性が高い変数」または「escapeshellarg が適用されているが、特定の状況では回避可能」] - **汚染トレース**:[不確かな箇所を特定した、可能な範囲でのトレース] - **懸念事項**:[不確実性があるにもかかわらず、リスクが残る理由] - **修正方法**:[修正内容] - **動的テスト**:[試行するペイロード]
### [脆弱性なし] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **理由**:[例:「引数はハードコードされた定数である」、「subprocess はリスト形式で呼び出され、shell=True がないためシェルインジェクションは不可能」または「使用前に厳密な許可リストで値が制限されている」] ### [手動確認が必要] 説明的な名前 - **ファイル**:`path/to/file.ext`(行 X-Y) - **エンドポイント/関数**:[ルートまたは関数名] - **不確実性**:[変数の発生源を特定できなかった理由] - **提案**:[手動でたどる内容 — 例:「utils.py の `build_command()` を追跡し、戻り値の発生源を確認してください」]
フェーズ 3:マージ — バッチ結果の統合
Phase 2 のすべてのバッチサブエージェントが完了したら、sast/rce-batch-*.md ファイルをすべて読み込み、結果を 1 つの sast/rce-results.md に統合します。これはオーケストレーターであるあなたが直接行います。サブエージェントは必要ありません。
マージ手順:
sast/rce-batch-1.md、sast/rce-batch-2.md、…の各ファイルを読み込みます。- 各バッチファイルから検出事項をすべて集め、元の分類とすべての詳細フィールドを保持したまま、1 つのリストにまとめます。
- エグゼクティブサマリー用に、すべてのバッチを通じた合計数を数えます。
- 次の形式で、統合レポートを
sast/rce-results.mdに書き込みます。
# RCE Analysis Results: [Project Name]
## Executive Summary
- Sinks analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]
## Findings
[All findings from all batches, grouped by classification:
VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
Preserve every field from the batch results exactly as written.]
sast/rce-results.mdを書き込んだ後、中間バッチファイル(sast/rce-batch-*.md)をすべて削除し、sast/rce-recon.mdも削除します。
重要な注意事項
sast/architecture.mdを読み込み、その内容をすべてのサブエージェントにコンテキストとして渡してください。- Phase 2 は Phase 1 の完了後に実行する必要があります。Phase 2 は偵察結果に依存します。
- Phase 3 は Phase 2 のすべてのバッチ完了後に実行する必要があります。Phase 3 はすべてのバッチ出力に依存します。
- バッチサイズはサブエージェントあたりシンク 3 個です。シンクの合計が 1~3 個なら、サブエージェントを 1 つ使用します。10 個なら、サブエージェントを 4 つ使用します(3+3+3+1)。
- すべてのバッチサブエージェントを並列に起動してください。順番に実行してはいけません。
- 各バッチサブエージェントには、偵察ファイル全体ではなく、割り当てられたシンクのテキストだけを渡します。これにより、各サブエージェントのコンテキストを小さく、焦点を絞ったものにできます。
- Phase 1 は純粋に構造的な分析です。危険な位置に定数でない変数が現れるシンクは、その変数の由来にかかわらずすべて検出してください。Phase 1 ではユーザー入力を追跡しないでください。
- Phase 2 は純粋に taint 分析です。Phase 1 で見つかった各シンクについて、動的引数の起点をたどります。ユーザーが制御できるソースに由来する場合、その箇所は実際の脆弱性です。
- デシリアライズのシンクについて:外部から制御可能なバイトストリームはすべて危険です。HTTP ボディ、Cookie、ファイルアップロード、WebSocket フレーム、キューメッセージが該当します。外部ソースからのデータフローを否定できないデシリアライズのシンクは、すべて保守的に検出してください。
- OS コマンドのシンクについて:リスト形式で
shell=Trueを指定せずにsubprocess.run(["cmd", var])を実行する場合、引数はシェルによる解釈を受けずにプロセスへ直接渡されるため、コマンドインジェクションには該当しません。シェルによる解釈が可能な場合(文字列形式のコマンドとshell=Trueの組み合わせ、またはexec()/system()相当のもの)のみ検出してください。 eval相当のシンクについて:ユーザー入力に対してeval()を安全に使用できる方法はほとんどありません。外部データを受け取るeval相当のシンクは、すべて Vulnerable として検出してください。- 判断に迷う場合は、「Not Vulnerable」ではなく「Needs Manual Review」に分類してください。セキュリティ評価では、偽陰性は偽陽性よりも重大です。
- Taint は、ミドルウェア、ヘルパー関数、クラス属性、中間変数を介して間接的に流れることがあります。経路全体を追跡してください。
- 第二段階の RCE が発生する可能性もあります。ユーザー入力から保存された値が、後に別のコードパスでデシリアライズまたは評価されることがあります(例:ユーザー提供の設定が DB に保存され、後で cron ジョブによって
eval()される)。 - Java のデシリアライズについて:クラスパスに危険なガジェットライブラリ(Apache Commons Collections、Spring Framework など)が存在するかどうかで、悪用可能性が決まります。デシリアライズ呼び出しを検出し、
architecture.mdに記載された関連ライブラリを示してください。 - 中間ファイルをクリーンアップしてください。最終的な
sast/rce-results.mdを書き込んだ後、sast/rce-recon.mdとすべてのsast/rce-batch-*.mdファイルを削除します(Phase 3 のマージ手順 5 で実行します)。