跳到主要内容

第一次编译与查询

本节用一个 Java 命令执行示例,完成从源程序到查询结果的最小流程。目标是定位 Runtime.exec 调用、追踪传入参数,并把结果映射回源码。

准备环境与示例​

先按照 CLI 总览安装 Yak,再检查本地版本:

yak version

本文的命令与编译与查询指南使用同一套接口。具体版本支持的参数可以通过 yak ssa-compile --help 和 yak ssa-query --help 检查。

克隆练习项目,进入第一课:

git clone https://github.com/yaklang/syntaxflow-zero-to-hero
cd syntaxflow-zero-to-hero/lesson-1-hello-world

这个目录中,HelloWorld.java 是待分析程序,lesson-1.sf 是查询规则。先阅读源码,再运行分析,以便理解每个命中值对应的实际代码。

编译为 Program​

yak ssa-compile -t . -p lesson1 -l java --re-compile
参数作用
-t .以当前目录为编译目标
-p lesson1将编译结果保存为名为 lesson1 的 Program
-l java指定源程序语言
--re-compile允许重新编译同名 Program

编译完成后,检查输出中是否存在失败信息。Program 名称是后续查询的入口;它与源程序中的类名或项目目录名并不是同一个概念。

阅读一条数据流查询​

示例规则的核心表达式如下:

lesson-1.sf · 查询部分
Runtime.getRuntime().exec(* #-> * as $source) as $sink;

check $source then "找到命令执行参数来源" else "未找到参数来源";
check $sink then "找到命令执行点" else "未找到命令执行点";

从左到右阅读:

  1. Runtime.getRuntime().exec 定位关注的调用。
  2. 括号内的匹配表达式选择调用参数。
  3. #-> 沿定义链向上追踪影响参数的值。
  4. as $source 保存追踪结果;as $sink 保存调用匹配结果。
  5. check 根据命名结果是否存在输出说明。

这里的 #-> 表达数据依赖追踪,不表示控制流图中的“支配者”。追踪方向和配置项可继续查阅数据流运算。

执行规则​

使用已经编译的 Program:

yak ssa-query -p lesson1 --sf lesson-1.sf

也可以使用同一查询接口的别名:

yak sf -p lesson1 --sf lesson-1.sf

-p lesson1 必须与编译时的 Program 名称一致。修改源码后应重新编译;修改规则后则可以在已有 Program 上再次查询。

从结果回到代码​

查询输出包含命名变量及其匹配的值。优先检查:

  • $source: 是否指向示例中的方法参数 cmd,以及该参数来自何处。
  • $sink: 是否对应调用 exec(cmd) 的位置。
  • 源码范围: 文件名与行列范围是否与本地练习项目一致。

输出中的 IR 编号和行列位置可能随版本与源码变化。它们是定位证据,不能仅凭某个编号或示例日志判断查询是否正确。

从匹配到结论

找到调用与参数来源,说明规则发现了一条值得检查的数据依赖。是否存在可利用的命令注入,还需要确认参数是否可控、路径是否可达,以及输入是否经过有效约束。

接下来的两个练习​

  1. 修改调用参数或增加一个中间变量,重新编译,观察 $source 的变化。
  2. 阅读规则文件结构,为需要关注的结果增加 alert。如果希望直接生成扫描报告,转到代码扫描与报告。