关于 sast-rce
sast-rce 是针对代码库检测远程代码执行漏洞的安全审计技能,面向安全分析工作流。它分阶段查找危险执行点、并行追踪用户输入流向,并汇总结果;运行前要求存在 `sast/architecture.md`。
主要功能
- 01查找命令执行等危险入口
- 02检查代码注入与不安全反序列化
- 03并行追踪用户输入到执行点
- 04合并分析结果并生成报告
- 05区分 RCE 与其他漏洞类型
适用场景
- 01审查代码库中的命令注入风险
- 02检查 eval 或动态代码执行路径
- 03分析用户数据反序列化风险
- 04汇总分批安全审计发现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-rce安装后在 Claude Code 中输入/sast-rce确认已加载。
详细介绍
远程代码执行(RCE)检测
你正在进行有针对性的安全评估,以查找代码库中的远程代码执行漏洞。此技能采用由子 Agent 执行的三阶段方法:侦察(查找危险的执行汇点)、分批验证(在每批 3 个汇点的并行批次中,追踪用户提供的输入是否会到达每个汇点),以及合并(汇总各批次结果并生成最终报告)。
前置条件:sast/architecture.md 必须存在。如果不存在,请先运行分析技能。
什么是远程代码执行
远程代码执行(RCE)是指攻击者能够使应用执行由其控制的任意操作系统命令或应用程序级代码。这通常是严重性最高的漏洞类别之一,往往会导致服务器完全失陷。
RCE 源于三个主要根本原因:
- 操作系统命令注入:用户输入未经安全处理就嵌入操作系统命令字符串,允许攻击者通过 Shell 元字符注入额外命令。
- 代码注入(类似 eval):用户输入被传递给会将其解释为可执行代码的函数(
eval、exec、Function()等)。 - 不安全的反序列化:使用易受利用的反序列化器处理用户提供的序列化数据,触发精心构造的负载执行任意代码。
什么属于 RCE
- 直接或间接地将用户输入传入启用了 Shell 解释的操作系统命令执行函数
- 使用
eval()、exec()、Function()或等效结构处理用户可控的字符串 - 使用本质上不安全的反序列化器(pickle、PHP unserialize、Java 原生序列化、Ruby Marshal 等)反序列化用户提供的字节或字符串
- 在没有安全加载器的情况下,对用户提供的内容使用
yaml.load() - 使用用户可控的模块路径动态执行
require()/import() - 使用用户可控的路径进行 PHP 文件包含(
include/require)
什么不属于 RCE
以下情况不要报告为 RCE:
- SSRF:向攻击者控制的 URL 发起 HTTP 请求——这是不同的漏洞类别(不会执行代码)
- 路径遍历:读取或写入任意文件——属于单独的漏洞类别(除非读取的文件随后被执行或反序列化)
- SSTI:通过模板引擎进行模板注入——这是不同但相关的漏洞类别;应报告为 SSTI,而非 RCE
- XSS:在受害者浏览器中执行 JavaScript——仅影响客户端,不属于服务端 RCE
- SQL 注入:向数据库查询中注入内容——属于不同的漏洞类别(即使
xp_cmdshell可能导致执行操作系统命令,也应报告为 SQLi) - 安全的 subprocess 列表形式调用:使用列表且未设置
shell=True的subprocess.run(["ls", user_arg])——参数会直接传递给操作系统,不经过 Shell 展开;不会受到命令注入攻击 - 安全的反序列化:
json.loads()、yaml.safe_load()、xml.etree.ElementTree.parse()——这些格式不具备代码执行语义
可防止 RCE 的模式
如果看到以下模式,代码很可能不易受攻击:
1. 不进行 Shell 解释的 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, []);
易受攻击与安全示例
操作系统命令注入 — 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
操作系统命令注入 — 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());
});
操作系统命令注入 — 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");
}
操作系统命令注入 — 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
执行
此技能通过子 Agent 分三个阶段运行。将 sast/architecture.md 的内容作为上下文传递给所有子 Agent。
阶段 1:查找危险的执行汇点
使用以下说明启动一个子 Agent:
目标:找出代码库中执行操作系统命令、动态求值代码或使用不安全反序列化器反序列化数据的所有位置。标记传入这些危险调用点的任何动态变量,无论其来源是什么。将结果写入
sast/rce-recon.md。上下文:你将获得项目的架构摘要。请利用它了解技术栈、语言、框架以及正在使用的序列化模式。
类别 1 — 操作系统命令执行调用点
查找执行操作系统命令的函数,其中命令字符串或参数可能是动态构造的。当危险位置出现任何非恒定变量时,进行标记:
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)— 如果第一个参数是字符串变量,则标记vm.runInNewContext(var)、vm.runInContext(var)、vm.runInThisContext(var)— 如果参数是变量,则标记require(var)— 如果模块路径是变量,则标记(使用外部输入动态 require → 路径遍历 + 潜在代码执行)PHP:
eval(var)— 参数中包含变量时始终标记preg_replace(pattern, replacement, subject),且 pattern 中带有/e修饰符 — 始终标记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)— 始终标记yaml.load(var),未显式指定Loader=yaml.SafeLoader— 标记(未使用安全加载器的任何形式都要标记)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)— 始终标记YAML.load(var)(Psych)未设置permitted_classes: []— 标记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)— 始终标记
跳过以下情况(这些是安全的,不应标记):
subprocess.run(["cmd", arg1, arg2])使用列表且未设置shell=True— 不进行 shell 展开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 Recon:[项目名称] ## 摘要 共发现 [N] 个潜在 RCE 调用点:[X] 个操作系统命令,[Y] 个代码注入,[Z] 个不安全反序列化。 ## 发现的调用点 ### 1. [描述性名称 — 例如:“图像转换器中的 shell=True subprocess”] - **文件**:`path/to/file.ext`(第 X-Y 行) - **函数 / 端点**:[函数名称或路由] - **类别**:[操作系统命令注入 / 代码注入 / 不安全反序列化] - **调用点**:[危险的函数调用 — 例如:subprocess.run(..., shell=True)] - **动态参数**:`var_name` — [简要说明其可能代表什么] - **代码片段**:[调用点附近的相关代码]
[每个调用点重复以上格式]
Phase 1 后:在继续之前检查候选项
Phase 1 完成后,读取 sast/rce-recon.md。如果侦察发现零个 sink(摘要显示“Found 0”,或“Sinks Found”部分为空或不存在),则完全跳过 Phase 2 和 Phase 3。改为将以下内容写入 sast/rce-results.md,删除 sast/rce-recon.md,然后停止:
# RCE Analysis Results
No vulnerabilities found.
只有在 Phase 1 至少发现一个潜在 sink 时,才继续 Phase 2。
Phase 2:追踪用户输入到 sink 的路径(分批处理)
Phase 1 完成后,读取 sast/rce-recon.md,并将 sink 分成每批最多 3 个 sink(## Sinks Found 下带编号的各部分:### 1.、### 2. 等)。并行启动每批一个子 Agent。每个子 Agent 仅追踪分配给自己的 sink 的污点,并将结果写入各自的批次文件。
分批步骤(由你这个协调者执行,而非子 Agent):
- 读取
sast/rce-recon.md并统计带编号的 sink 部分(### 1.、### 2.……)。 - 将它们分成每批最多 3 个。例如,8 个 sink → 3 批(1-3、4-6、7-8)。
- 对每批,从侦察文件中提取这些 sink 部分的完整文本。
- 并行启动所有批次子 Agent,并且只向每个子 Agent提供分配给它的 sink。
- 每个子 Agent 将结果写入
sast/rce-batch-N.md,其中 N 为从 1 开始的批次编号。 - 从
sast/architecture.md确定项目的主要语言/框架,并从上文的“易受攻击与安全示例”部分中仅选择匹配的示例。例如,如果项目主要使用 Python,则包含适用的 Python OS 命令、eval、pickle 和 YAML 小节。在下方[TECH-STACK EXAMPLES]指示的位置,将选出的示例加入每个子 Agent 的指令中。
向每个批次子 Agent 提供以下指令(替换为对应批次的值):
目标:针对每个分配的 RCE 危险调用点,判断用户提供的值是否会到达危险参数。我们的目标是查找代码执行漏洞。将结果写入
sast/rce-batch-[N].md。分配给你的危险调用点(来自侦察阶段):
[在此粘贴分配的危险调用点章节的完整文本,并保留原始编号]
上下文:你将获得项目的架构摘要。利用该架构了解请求入口点、中间件以及数据在应用中的流向。
RCE 参考——检查要点:
追踪每个危险调用点的动态参数,回溯至其来源。发生 RCE 的前提是攻击者可控的数据到达危险调用点(具有 shell 解释功能的操作系统命令、类似 eval 的执行,或不安全的反序列化)。
哪些情况不属于 RCE——不要将以下情况标记为 RCE:
- SSRF、路径遍历、SSTI、XSS、SQLi——属于其他漏洞类型(参见技能前言)。
- 不使用 shell 的安全子进程列表形式——参数未经 shell 展开直接传递,因此不构成命令注入。
- 安全格式:
json.loads、yaml.safe_load、ast.literal_eval——不具有代码执行语义。可防止漏洞利用的缓解措施——如果存在且有效,该危险调用点很可能是安全的:
- 不使用 shell 的子进程列表形式:
subprocess.run(["cmd", var]),且未设置shell=True——不会发生 shell 元字符注入。- 严格的允许列表:使用前仅允许一组固定的安全值。
- 安全反序列化: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、文件上传,或最初由用户提供的数据库值?任何传入不安全反序列化器、且可由外部控制的字节流都可被利用。
服务器端/硬编码值——变量来自配置、环境变量、硬编码常量,或不受外部影响的服务器端逻辑——不可利用。
每个危险调用点都要检查的缓解措施:
- 允许列表验证:变量在使用前是否经过固定已知安全值的验证?如果验证严格且完整,标记为 Not Vulnerable。
- 整数/类型转换:在此上下文中,转换为
int/float是否确实能防止注入?仅当参数纯为数值且不存在引号问题时才有效。- escapeshellarg / escapeshellcmd(PHP):虽能降低风险,但无法完全消除风险——标记为 Likely Vulnerable;在某些上下文中,shell 转义曾出现过绕过情况。
- 子进程列表形式:
subprocess.run(["cmd", var]),且未设置shell=True——参数直接传给操作系统,不进行 shell 展开。这是防止命令注入的有效缓解措施(对注入问题标记为 Not Vulnerable;该值仍会传给命令,但无法注入新命令)。- 使用了安全反序列化器:如果使用
json.loads()、yaml.safe_load()等,则跳过(第一阶段不应标记这些情况)。分类:
- Vulnerable:用户输入明确到达危险调用点,且没有有效缓解措施。
- Likely Vulnerable:用户输入可能到达危险调用点(存在间接数据流),或仅采用了较弱的缓解措施(shell 转义、部分验证、允许列表不明确)。
- Not Vulnerable:参数仅来自服务器端,或已采取有效缓解措施(子进程列表形式、严格允许列表、安全反序列化格式)。
- Needs Manual Review:无法有把握地确定参数来源(经过不透明的辅助函数、复杂的条件数据流或外部库)。
输出格式——写入
sast/rce-batch-[N].md:# RCE Batch [N] Results ## Findings ### [VULNERABLE] 描述性名称 - **File**:`path/to/file.ext`(第 X-Y 行) - **Endpoint / function**:[路由或函数名称] - **Category**:[OS Command Injection / Code Injection / Unsafe Deserialization] - **Issue**:[例如,“HTTP 查询参数 `host` 直接流入 `shell=True` 的子进程调用”] - **Taint trace**:[从入口点到危险调用点的逐步追踪——例如,“request.args.get('host') → host → subprocess.run(f'ping -c 1 {host}', shell=True)”] - **Impact**:[攻击者能造成的影响——执行任意操作系统命令、读取 /etc/passwd、建立反向 shell、完全控制服务器等] - **Remediation**:[具体修复方法——使用列表形式的子进程调用、用安全替代方案替换 eval、改用 json.loads/yaml.safe_load 等] - **Dynamic Test**:[用于确认发现的 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 构造恶意负载]
### [LIKELY VULNERABLE] 描述性名称 - **File**:`path/to/file.ext`(第 X-Y 行) - **Endpoint / function**:[路由或函数名称] - **Category**:[OS Command Injection / Code Injection / Unsafe Deserialization] - **Issue**:[例如,“变量可能通过辅助函数从用户输入获取”或“使用了 escapeshellarg,但在某些上下文中可被绕过”] - **Taint trace**:[尽可能详细地追踪,并指出不确定的步骤] - **Concern**:[尽管存在不确定性,仍有风险的原因] - **Remediation**:[修复方法] - **Dynamic Test**:[尝试使用的负载]
### [NOT VULNERABLE] 描述性名称 - **File**:`path/to/file.ext`(第 X-Y 行) - **Endpoint / function**:[路由或函数名称] - **Reason**:[例如,“参数为硬编码常量”或“子进程调用使用列表形式,未设置 shell=True——不可能发生 shell 注入”或“使用前通过严格的允许列表限制该值”] ### [NEEDS MANUAL REVIEW] 描述性名称 - **File**:`path/to/file.ext`(第 X-Y 行) - **Endpoint / function**:[路由或函数名称] - **Uncertainty**:[无法确定变量来源的原因] - **Suggestion**:[建议手动追踪的内容——例如,“跟踪 utils.py 中的 `build_command()`,检查其返回值的来源”]
阶段 3:合并 — 整合批次结果
所有阶段 2 批次子 Agent 全部完成后,读取每个 sast/rce-batch-*.md 文件,并将它们合并为一个 sast/rce-results.md。你(协调者)直接完成此操作——无需子 Agent。
合并步骤:
- 读取所有
sast/rce-batch-1.md、sast/rce-batch-2.md……文件。 - 收集每个批次文件中的所有发现,并合并为一个列表,同时保留原始分类和所有详细字段。
- 汇总所有批次的数量,供执行摘要使用。
- 按以下格式将合并后的报告写入
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,并将其内容作为上下文传递给所有子 Agent。 - 阶段 2 必须在阶段 1 完成后运行——它依赖侦察输出。
- 阶段 3 必须在所有阶段 2 批次完成后运行——它依赖所有批次输出。
- 每个子 Agent 的批次大小为 3 个 sink。如果总共有 1–3 个 sink,则使用一个子 Agent。如果有 10 个,则使用 4 个子 Agent(3+3+3+1)。
- 并行启动所有批次子 Agent——不要按顺序逐个运行。
- 每个批次子 Agent 只接收侦察文件中分配给它的 sink 文本,而不是整个侦察文件。这能让每个子 Agent 的上下文保持精简且聚焦。
- 阶段 1 纯粹是结构性分析:只要非恒定变量出现在危险位置,就标记相应的 sink,无论该变量来自何处。阶段 1 不要追踪用户输入。
- 阶段 2 纯粹是污点分析:对于阶段 1 发现的每个 sink,追踪动态参数的来源。如果它来自用户可控的来源,则该位置存在真实漏洞。
- 对于反序列化 sink:任何可由外部控制的字节流都具有危险性——HTTP 请求体、Cookie、文件上传、WebSocket 帧、队列消息。如果无法排除数据来自外部来源,应保守处理并标记所有反序列化 sink。
- 对于 OS 命令 sink:使用列表形式且未设置
shell=True的subprocess.run(["cmd", var])不属于命令注入——参数会直接传递给进程,不经过 shell 解析。只有在可能进行 shell 解析时才标记(字符串命令搭配shell=True,或等效的exec()/system())。 - 对于类似
eval的 sink:几乎没有安全的方式能让eval()处理用户输入。任何接收外部数据的类似 eval 的 sink 都应标记为 Vulnerable。 - 有疑问时,分类为“需要人工审查”,而不是“无漏洞”。在安全评估中,漏报比误报更糟糕。
- 污点可能通过中间件、辅助函数、类属性和中间变量间接传播。追踪完整链路。
- 可能存在二阶 RCE:从用户输入存储的值,之后可能在不同代码路径中被反序列化或求值(例如,用户提供的配置存入数据库,之后由定时任务执行
eval())。 - 对于 Java 反序列化:类路径中是否存在危险的 gadget 库(Apache Commons Collections、Spring Framework 等)决定了漏洞能否被利用。标记反序列化调用,并注明
architecture.md中提到的相关库。 - 清理中间文件:写入最终的
sast/rce-results.md后,删除sast/rce-recon.md和所有sast/rce-batch-*.md文件(阶段 3 合并步骤 5 会执行此操作)。