从微信小程序到全端:一个安全扫描器的演进之路(v0.5.0 → v0.6.0)
上一篇讲了"从 0 到 GitHub Marketplace",这一篇讲"从专用到通用"。一个只能扫微信原生小程序的工具,如何通过 3 次技术迭代,支持 Taro、uni-app、React、Vue,最终变成一个"全端前端安全扫描器"。
项目地址:
- GitHub: github.com/lathuninh/m...
- PyPI: pypi.org/project/min...
- Marketplace: github.com/marketplace...
一、起点:v0.5.0 的能力边界
v0.5.0 发布时,工具能扫:
- ✅ 微信原生小程序 (
wx.request/e.detail.value/wx.setStorageSync) - ✅ 普通 Web 应用 (
location.search/innerHTML)
但扫不了:
- ❌ Taro (
Taro.request/export default) - ❌ uni-app (
uni.request) - ❌ React 组件 (
export default function App()) - ❌ Vue 单文件组件 (
.vue文件的<script>)
这 4 个场景,覆盖了 80% 的中国前端项目。
决定 :把工具从"微信专用"扩展成"全端通用"。
二、第一次迭代:Taro 支持
2.1 加规则:Taro API 到 taint.yaml
Taro 的 API 和微信原生几乎一样 (Taro.request vs wx.request)------只要把 Taro 的 API 加到 sink 和 source 列表:
yaml
sinks:
- name: "Taro.request"
dangerous_arg: [0, 1]
severity: medium
message: "Tainted data into Taro.request"
- name: "Taro.setStorageSync"
dangerous_arg: [1]
severity: high
sources:
- member: "Taro.getStorageSync"
- member: "Taro.getStorage"
这一步很简单。
2.2 加项目类型识别
_detect_project_type 读 package.json:
python
kotlin
if (root / "package.json").exists():
try:
import json
pkg = json.loads((root / "package.json").read_text(encoding="utf-8-sig"))
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
if "@tarojs/taro" in deps:
return "taro"
if "@dcloudio/uni-app" in deps:
return "uni-app"
except Exception:
pass
2.3 🐛 踩坑 1:PowerShell 写 package.json 带 BOM
用 Set-Content -Encoding UTF8 写的文件带 BOM (开头几个不可见字节 )------json.loads 读到就报错:
text
arduino
json.decoder.JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig)
修复 :utf-8 → utf-8-sig (自动去 BOM)。
2.4 🐛 踩坑 2:export default 没被处理
Taro 项目用 export default:
javascript
javascript
export default function Pay() {
const handlePay = (e) => {
const amount = e.detail.value.amount;
Taro.request({ url: '...' + amount });
};
}
_exec_stmt 只处理 FunctionDeclaration ------但 export default function Pay() 的 AST 是 ExportDefaultDeclaration ------Pay 函数体根本没被执行 ------所以 Taro.request 没扫到。
修复 :在 _exec_stmt 里加 ExportDefaultDeclaration 和 ExportNamedDeclaration:
python
csharp
elif t == "ExportDefaultDeclaration":
decl = node.get("declaration", {})
dt = decl.get("type")
if dt == "FunctionDeclaration":
self._handle_function_like(decl, decl.get("id", {}).get("name", ""), file_path)
elif dt in ("ArrowFunctionExpression", "FunctionExpression"):
self._handle_function_like(decl, "__default__", file_path)
elif t == "ExportNamedDeclaration":
decl = node.get("declaration")
if decl:
self._exec_stmt(decl, file_path)
这个 bug 影响很大 ------不只是 Taro ------所有用 ES Module 的项目 (React / Vue / 现代前端 )都受影响。
2.5 🐛 踩坑 3: _handle_var_decl 的缩进错误
const handlePay = (e) => { ... } 是 VariableDeclaration------需要处理 ArrowFunctionExpression ------但你加 elif 时缩进错了:
python
ini
elif it == "MemberExpression":
obj = self._member_name(init.get("object"))
prop = self._member_name(init.get("property"))
if obj and prop and obj != "this":
self.aliases.record_prop_alias(name, obj, prop)
elif it == "ObjectExpression": # ← 错误!这个 elif 缩进太深了
...
ObjectExpression 和 FunctionExpression 的 elif 被错误地嵌套在了 if it == "MemberExpression" 里面 ------永远进不去。
修复 :统一缩进:
python
python
elif it == "MemberExpression":
obj = self._member_name(init.get("object"))
prop = self._member_name(init.get("property"))
if obj and prop and obj != "this":
self.aliases.record_prop_alias(name, obj, prop)
elif it == "ObjectExpression": # ← 正确:和 if 平级
...
elif it in ("FunctionExpression", "ArrowFunctionExpression"):
self._handle_function_like(init, name, file_path)
2.6 结果
修复后 ------Taro 100% 支持 ------并且顺手修好了 export default (顺便覆盖 React / Vue)。
三、第二次迭代:uni-app 支持
uni-app 的 API 更简单 ------uni.request / uni.setStorageSync ------只要加规则:
yaml
yaml
sinks:
- name: "uni.request"
dangerous_arg: [0, 1]
severity: medium
- name: "uni.setStorageSync"
dangerous_arg: [1]
severity: high
sources:
- member: "uni.getStorageSync"
- member: "uni.getStorage"
10 分钟搞定 ------因为项目类型识别已经在 Taro 那次做好了。
四、修复 _resolve 单层路径(重要)
4.1 问题
你的 DSL 规则用了 match: [value, ...] ------但 value 被当字面量返回 ------导致规则不命中。
4.2 根因
_resolve 方法:
python
lua
if "." not in path:
return path # ← 单层路径一律当字面量
"value" 不含 . ------返回字符串 "value" ------而不是 ctx.node["value"] 。
4.3 修复
python
lua
if "." not in path:
# 单层路径:先尝试从 node 取
if isinstance(ctx.node, dict) and path in ctx.node:
return ctx.node[path]
# 否则当字面量(比如 "eval"、"debug")
return path
这个修复让 DSL 规则表达能力提升了一个档次 ------可以写 eq: [value, "..."] 这种细粒度规则。
五、新增规则:信息泄露(补天场景)
加了 3 条"补天场景"规则:
- DSL-060 :API 路径泄露(
/api/admin/xxx//api/internal/xxx) - DSL-061 :Debug 模式开启(
debug: true) - DSL-062 :测试凭据硬编码(
test_user: "admin")
这些规则能帮你在补天上快速发现"信息泄露"线索。
六、踩过的所有坑(完整清单)
| 坑 | 原因 | 修复 |
|---|---|---|
export default 没扫到 |
AST 是 ExportDefaultDeclaration |
加节点处理 |
package.json BOM 报错 |
Set-Content 写文件带 BOM |
用 utf-8-sig |
elif 缩进错 |
粘贴时缩进多了 | 统一缩进 |
_resolve 单层路径 |
不含 . 一律当字面量 |
先查 ctx.node |
Taro.request 不命中 |
ObjectExpression 没返回污点 |
修 _handle_var_decl |
editable install 缓存 |
改代码不生效 | --force-reinstall |
| CLI 编码崩溃 | GBK 不支持 emoji | colorama.just_fix_windows_console |
每一个坑都值得写一篇文章。
七、数据:v0.5.0 → v0.6.0
| 指标 | v0.5.0 | v0.6.0 | 变化 |
|---|---|---|---|
| 支持框架 | 微信 / Web | 微信 / Taro / uni-app / React / Vue / Web | +4 |
| 规则数 | 50 | 55+ | +5 |
| 污点 sink | 15 | 23 | +8 |
| 污点 source | 25 | 35 | +10 |
| 测试 | 25 | 25(全绿) | 不变 |
| Commits | 37 | 42+ | +5 |
核心变化 :从"微信专用"到"全端通用"。
八、为什么这件事很重要
前端生态是"多框架"的:
- 小程序:微信原生 / Taro / uni-app
- Web:React / Vue / Angular / Svelte
一个只支持"微信原生"的工具 ------只能覆盖 20% 的项目。
一个支持"全端"的工具 ------能覆盖 80% 的项目。
从 20% 到 80% ------只需要加几个 API 规则 + 修复 export default ------ROI 极高。
九、下一步
继续优化:
- 支持 Vue SFC 完整解析 (
.vue文件的<template>) - 支持 React JSX 解析
- 加更多规则(越权 / SDK 滥用 / CSRF)
让工具从"能扫"到"扫得准"。
十、开源地址
- GitHub : github.com/lathuninh/m...
- PyPI : pypi.org/project/min...
- Marketplace : github.com/marketplace...
- 第一篇 : 从零构建
- 第二篇 : 深入污点分析
- 第三篇 : 从 0 到 Marketplace
欢迎 Star / Issue / PR。
十一、总结
从 v0.5.0 到 v0.6.0 ------核心是"三个修复 + 两个规则" :
- 修
export default(覆盖 React / Vue / Taro) - 修
_resolve单层路径 (提升 DSL 表达能力) - 修
_handle_var_decl缩进 (修复箭头函数处理) - 加 Taro API 规则 (支持 Taro)
- 加 uni-app API 规则 (支持 uni-app)
每一个修复都让工具更通用 ------每一个规则都让工具更精准。
开源项目的演进 ------不是一次写完 ------是持续迭代 ------每次解决一个真实问题。
加油! 大家加油,努力复盘总结🚀