源码审计,逐行溯源。
源码漏洞审计命令行工具。 给它一个目录、一个文件、或者一个 zip,它会告诉你:哪一行有问题、这个变量从哪来、该怎么修。
为 AWD / AWDP 那种「几十分钟内必须揪出对手能打穿的洞」的场景打造,也适用于日常代码审计和 CI 卡口。
- 跨行污点分析,不是「正则匹配关键词」——
$q = $_GET['id']拼进第 4 行的mysqli_query(),能追出来 - 331 条规则(污点来源 66 / 危险函数 212 / 静态特征 53),26 类漏洞,8 种语言(PHP / Java / Go / Python / Node.js / Ruby / ASP / C#)
- 目录、单文件、
.zip直接吃,零 npm 依赖,只要 Node ≥ 18 - 输出彩色文本 / JSON,退出码可挂 CI
- 引擎来自 LingOps 的「🔬 深度审计」,本项目自带一份副本,规则只有一份,不存在两套实现漂移
$ sourceaudit ./src -s critical
sourceaudit 1.0.0 · 跨行污点分析引擎
34 个文件(PHP×10 Python×8 Node.js×7 Java/JSP×3 Go×3 Ruby×3),跳过 0 个
CRITICAL a01_php_sqli.php:4 sqli
SQL 执行
│ $r = mysqli_query($conn, $q);
└ 污点来源 L2 $_GET/$_POST 数组取值 · $id = $_GET['id'];
CRITICAL a02_php_rce.php:3 rce
命令执行函数
│ system("ping -c 1 " . $host);
└ 污点来源 L2 $_GET/$_POST 数组取值 · $host = $_GET['host'];
────────────────────────────────────────────────────────
严重 5 · 高危 0 · 中危 0 · 低危 0 合计 5 处 / 34 文件 / 15ms
- 一、它解决什么问题
- 二、为什么不是「正则扫关键词」
- 三、安装
- 四、快速开始
- 五、看懂输出
- 六、选项全表
- 七、退出码与 CI 集成
- 八、JSON 输出
- 九、规则集
- 十、同步引擎(上游更新了规则怎么办)
- 十一、自测
- 十二、限制
- 十三、目录结构
- 十四、免责与致谢
AWD / AWDP 的 Fix 阶段,你要在几十分钟里把自己那套别人写的 PHP/Java 源码过一遍,找出能被对手打穿的洞。现实是:
| 现状 | 结果 |
|---|---|
人工 grep eval(、grep system( |
漏掉拼接出来的、绕了一层变量的、藏在 include 里的 |
IDE 全局搜索 $_GET |
几百个命中,九成是安全的(过了 intval、用了预处理) |
| 商业 SAST | 装不上、跑不动、报告看不懂、比赛机器没网 |
| 网页版扫描器 | 要开浏览器、要传包、一次一个、没法批量、没法进 CI |
这个工具就是把这四件事收拢成一条命令:
sourceaudit challenge.zip -s high三秒内给你一张按严重度排好的清单,每条都带「污点来源」,也就是这个危险参数是从哪个 $_GET 流过来的——这是判断真漏洞还是误报的关键信息。
普通扫描器看到 mysqli_query($conn, $q) 只会想「有个变量进 SQL 了」,于是:
- 报
$q = "SELECT * FROM t WHERE id=" . intval($id)→ 误报 - 漏
$q是在第 2 行拼的、第 4 行才用 → 漏报(如果它只扫单行)
sourceaudit 做的是跨行污点传播:
source($_GET / $_POST / $_FILES / $_COOKIE / request.getParameter / req.query ...)
↓ 赋值、拼接、函数传参、数组下标
sanitizer(intval / (int) / htmlspecialchars / PreparedStatement / escapeshellarg ...)→ 降级或消解
↓
sink(system / mysqli_query / include / unserialize / Runtime.exec ...)→ 报
所以它能同时做到:
- 报出「第 2 行
$id = $_GET['id'],第 4 行进了 SQL」这种跨行链路 - 不报「过了
intval」的写法(sanitized: true,严重度自动降级) - 把「追不到来源的危险函数」单独标成低置信(
awdpplus-lowconf),不跟真漏洞混在一起
规则表本身就是一份「坏模式清单」:66 条污点来源、212 个危险函数、53 条静态特征,覆盖 RCE / SQLi / LFI / 文件上传 / 反序列化 / SSRF / XSS / XXE / 路径穿越 / 弱加密 / 硬编码凭据等 26 类。
git clone https://github.com/GeWu-academy/sourceaudit.git
cd sourceaudit
chmod +x bin/sourceaudit.js
./bin/sourceaudit.js ./your-sourcebin/sourceaudit.js 带 shebang,chmod +x 后可直接执行;也可以 node bin/sourceaudit.js ...。
不需要 npm install——本项目零依赖。
git clone https://github.com/GeWu-academy/sourceaudit.git
cd sourceaudit
npm install -g .
sourceaudit ./your-source # 任意目录下可用macOS 上
npm install -g若报operation was rejected by your operating system(全局目录属 root), 二选一:命令前加sudo,或者npm config set prefix ~/.npm-global再把~/.npm-global/bin加进PATH。
cd /path/to/your-project
npm install --save-dev /path/to/sourceaudit
npx sourceaudit ./src -s high适合给单个仓库加审计卡口(配合 package.json 的 scripts.audit)。
| 项 | 要求 | 说明 |
|---|---|---|
| Node.js | ≥ 18 | ZIP 输入依赖 DecompressionStream('deflate-raw');低于 18 时目录/单文件照扫 |
| 第三方依赖 | 0 个 | 纯标准库,不需要 npm install |
| 网络 | 不需要 | 纯本地静态分析,不联网 |
# 扫一个目录(递归)
sourceaudit ./app
# 比赛发的源码包,直接扔进去
sourceaudit challenge.zip
# 只看能拿 shell 的
sourceaudit ./src -c rce,upload,lfi,deserialization
# 只要高危以上,出 JSON 报告
sourceaudit ./src -s high -f json -o report.json
# 一行汇总,适合批量循环
sourceaudit ./src -q扫描范围(与 LingOps 网页端一致):
- 跳过目录:
node_modules、vendor、.git、dist、build、target、.idea、.vscode、__pycache__、bower_components、.next、coverage(--vendor可关掉这条规则) - 跳过文件:二进制(含
\0)、单文件 > 3MB、.png/.jpg/.zip/.so/.exe/.min/.css等非源码后缀 - 认的语言:PHP / Java(JSP) / Go / Python / Node.js / Ruby / ASP(ASPX) / C#,其余后缀不扫
- ZIP:只解到内存,不落盘,包内相对路径原样出现在报告里
sourceaudit 1.0.0 · 跨行污点分析引擎
34 个文件(PHP×10 Python×8 Node.js×7 Java/JSP×3 Go×3 Ruby×3),跳过 0 个
CRITICAL a01_php_sqli.php:4 sqli
SQL 执行
│ $r = mysqli_query($conn, $q);
└ 污点来源 L2 $_GET/$_POST 数组取值 · $id = $_GET['id'];
| 行 | 含义 |
|---|---|
CRITICAL file:4 sqli |
严重度 / 文件:行号 / 漏洞类别 |
SQL 执行 |
命中的危险函数名 |
│ 源码行 |
出问题的那一行原文 |
└ 污点来源 L2 ... |
这个变量从哪来,L2 是来源所在行号 |
严重度着色:CRITICAL 红、HIGH 橙、MEDIUM 黄、LOW 灰。重定向到文件或设 NO_COLOR=1 时自动关色。
$ sourceaudit ./src -q
sourceaudit: 34 文件 · 严重 5 高危 10 中危 2 低危 19 · 15ms| 选项 | 取值 | 默认 | 说明 |
|---|---|---|---|
-f, --format |
text | json |
text |
输出格式 |
-s, --min-sev |
critical | high | medium | low |
low |
只显示不低于该级别 |
-c, --cat |
类别名,逗号分隔 | 全部 | 如 rce,sqli,upload |
-l, --lang |
php | java | go | python | node | ruby | asp | csharp |
全部 | 只扫指定语言 |
--exclude |
glob | 无 | 排除路径,可重复 |
--vendor |
— | 关闭 | 不跳过 vendor/ node_modules/ 等第三方目录 |
--no-lowconf |
— | 关闭 | 不输出「未追到污点来源」的低置信结果 |
--fail-on |
critical | high | medium | low | none |
high |
达到该级别时退出码 1 |
--engine |
文件路径 | engine/awdpplus.js |
指定引擎文件 |
--list-rules |
— | — | 打印规则统计后退出 |
-o, --out |
文件路径 | 无 | 结果同时写入文件 |
-q, --quiet |
— | 关闭 | 只输出汇总行 |
--no-color |
— | 关闭 | 关 ANSI 颜色 |
-v, --version |
— | — | 版本(工具版本 + 引擎版本) |
-h, --help |
— | — | 帮助 |
--exclude 是 glob 不是正则:* 只匹配单层(不跨 /),** 跨目录,? 匹配单字符。
sourceaudit . --exclude 'vendor/**' --exclude 'tests/**' --exclude '*.min.js'| 退出码 | 含义 |
|---|---|
0 |
没有达到 --fail-on 级别的问题 |
1 |
有达到级别的问题 |
2 |
参数错误 / 路径不存在 / 引擎加载失败 |
name: audit
on: [push, pull_request]
jobs:
awdp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- name: 源码审计
run: node bin/sourceaudit.js . -s high --fail-on high -f json -o awdp-report.json
- uses: actions/upload-artifact@v4
if: always()
with: { name: awdp-report, path: awdp-report.json }#!/bin/sh
# .git/hooks/pre-commit
sourceaudit . -s high --fail-on high -q || {
echo "发现高危及以上问题,提交已阻止。详情:sourceaudit . -s high"
exit 1
}sourceaudit . --fail-on none # 永远退出 0sourceaudit ./app -f json -o report.json{
"tool": "sourceaudit",
"version": "1.0.0",
"engine_version": "1.3.9",
"engine": "/path/to/engine/awdpplus.js",
"targets": ["./app"],
"files": 34,
"skipped": 0,
"langStr": "PHP×10 Python×8 Node.js×7 Java/JSP×3 Go×3 Ruby×3",
"elapsed_ms": 20,
"summary": { "critical": 5, "high": 10, "medium": 2, "low": 19, "total": 36 },
"findings": [
{
"file": "a01_php_sqli.php",
"lang": "php",
"line": 4,
"cat": "sqli",
"sev": "critical",
"name": "SQL 执行",
"fix": "改用预处理语句 / 参数化查询",
"sanitized": false,
"source": {
"line": 2,
"label": "$_GET/$_POST 数组取值",
"varName": "$id",
"code": "$id = $_GET['id'];"
},
"code": "$r = mysqli_query($conn, $q);",
"rule": "awdpplus"
}
]
}rule 三种取值,决定可信度:
rule |
含义 | 建议 |
|---|---|---|
awdpplus |
污点链路完整(source → sink) | 基本都是真漏洞 |
awdpplus-static |
静态特征命中(eval(、弱哈希等) |
大概率真,看上下文 |
awdpplus-lowconf |
危险函数命中但没追到污点来源 | 低置信,--no-lowconf 可关 |
source: null = 没追到来源;sanitized: true = 中间过了过滤函数(已降级但仍提示)。
$ sourceaudit --list-rules
规则统计(引擎 v1.3.9)
语言 污点来源 危险函数 静态特征
PHP 11 42 43
Java/JSP 13 33 48
Go 11 21 43
Python 7 35 44
Node.js 14 33 46
Ruby 4 17 44
ASP/ASPX 3 12 43
C# 3 19 45
────────────────────────────────────────────
合计 66 212 53 = 331 条
注:静态特征多为多语言共用,按语言分列之和会大于总数。
漏洞类别 26 类331 = 66 污点来源 + 212 危险函数 + 53 静态特征。
在命中行(或上一行)加注释即可:
// lingops:ignore 爬虫必须请求外部 URL
$html = file_get_contents($target);// lingops:ignore 路径来自白名单常量
new FileInputStream(path);支持 //、#、/* */、<!-- -->。带标记的命中会在引擎里被直接丢弃,不进报告、不计入汇总——
所以只在确认过是误报时才加,别拿它当消音器。
工具本身就要读任意路径 / 出网的(爬虫、指纹识别、SSRF 测试器)属于正常情况——加标记,别改代码迎合扫描器。
本项目的规则不是自己维护的,而是 engine/awdpplus.js —— 一份从 LingOps 带过来的引擎副本。
上游(LingOps 的「🔬 深度审计」)加规则后,用同步脚本拉过来:
# 自动探测同级目录里的 LingOps 包
npm run sync-engine
# 或者指定目录
npm run sync-engine -- /path/to/LingOps_v0.2_darwin_arm64
# 只比较不写(不一致退出码 1,可挂 CI 检查是否落后)
node scripts/sync-engine.js --check
# 先看看差多少,不落盘
node scripts/sync-engine.js --dry-run脚本做的事:
- 找
<LingOps 包>/frontend/dist/assets_v2/lp-ext/awdpplus.js - 算 sha256,与
engine/awdpplus.js比对,相同就直接退出 - 不同则先写到临时文件,验证能切出分析段(切不出就报错,不会装坏引擎)
- 打印规则数变化(来源/危险函数/静态特征/类别/版本)
- 原子替换,并把来源、指纹、规则数写进
engine/ENGINE.json - 自动跑一遍回归语料(
--no-test跳过)
环境变量
LINGOPS_HOME可以指定默认来源目录。
引擎为什么能直接搬:awdpplus.js 是个浏览器 IIFE,前半段是纯函数(规则表 + 分析器),
后半段才是 DOM / fetch / 定时器。本项目按边界(注释块 * 8. 与官方引擎对比,兜底
function getToken()把前半段切出来,用 new Function 在只带一个空 window 的环境里跑。
所以:不复制规则、不重写分析器,上游改了规则这边自动跟着变。
仓库自带 105 个已知答案的样本(故意埋洞的代码 + 正确写法 + 对抗样本),一条命令验证引擎:
npm test # 只打汇总
npm run test:verbose # 逐文件看漏报/误报
node test/run.js --json # 机器可读引擎 engine/awdpplus.js v1.3.9
真漏洞 34/34 100.0% ✅
安全代码 27/27 100.0% ✅
回归 12/12 100.0% ✅
新型漏洞 17/17 100.0% ✅
对抗语料 14/14 100.0% ✅ (另有 1 个已知盲区不计分)
CLI ✅ sourceaudit: 34 文件 · 严重 5 高危 10 中危 2 低危 19 · 15ms
全部通过 ✅
| 语料 | 文件数 | 期望 | 说明 |
|---|---|---|---|
vuln |
34 | 全命中 | 6 种语言的典型漏洞 |
safe |
27 | 零误报 | 过了 intval / 预处理 / 白名单的正确写法 |
regress |
12 | 全命中 | 历史上漏报过的写法(防回退) |
modern |
17 | 全命中 | 新框架/新语法下的同类漏洞 |
adversarial |
15 | 高危以上 | 刻意对抗的写法,1 个已文档化盲区 |
--json 里还包含 CLI 冒烟测试结果(引擎能加载 ≠ 命令行能用)。
- 只做静态分析,不跑代码、不连数据库——运行期才拼出来的 SQL 追不到
- 不做跨文件数据流:变量从 A 文件传到 B 文件就断了
- 不认自定义框架封装:自己写的
db()->query()不在危险函数表里就不报(可加进引擎的SINKS) - ZIP 需要 Node ≥ 18;老环境先手动解压
- 已知盲区:JS 里
obj[k] = v与arr[i] = x在无类型信息时不可区分,只报低置信(x08_js_proto_direct.js) - 报告 ≠ 可利用:报的是「污点能流到危险函数」,实际能不能打穿还要看过滤和上下文
- 中文源码需要 Node 自带 full-ICU(Node ≥ 14 默认有)
sourceaudit/
├── bin/
│ └── sourceaudit.js # CLI 入口:参数解析、文件收集、报告渲染
├── lib/
│ └── engine.js # 引擎加载(切出分析段)+ 规则统计
├── engine/
│ ├── awdpplus.js # 引擎副本(来自 LingOps,勿手改)
│ └── ENGINE.json # 来源包名 / sha256 / 规则数 / 同步时间
├── scripts/
│ └── sync-engine.js # 从 LingOps 同步引擎
├── test/
│ ├── run.js # 回归自测(105 个已知答案样本 + CLI 冒烟)
│ └── corpus/ # vuln / safe / regress / modern / adversarial
├── package.json
└── README.md
engine/awdpplus.js 不要手改——下次 sync-engine 会覆盖。要加规则就改上游 LingOps,然后同步过来。
本工具只做静态代码分析,不发起任何网络请求、不执行被扫代码、不包含任何攻击载荷。 请在你自己拥有或已获授权的代码上使用;用于未经授权的目标可能违法。
引擎与规则来自 LingOps(原作者 zhanglinglingc, 扩展由 GeWu-academy 维护)——没有那个干净利落的深度审计实现,就没有这个命令行壳。
本项目由 GeWu-academy 维护:github.com/GeWu-academy/sourceaudit。
MIT