最近有一些代码审计需要,研究了一下自动化审计工具
考虑了一些备选工具
Kunlun-M
LoRexxar大佬的审计工具,文档似乎比较老旧,学习成本比较高,试用了一下就放着了
CodeQL
GitHub搞得审计工具,QL逐渐流行起来,很多师傅写过文章
Semgrep
很早之前看过介绍文章,简单的规则编写让我印象非常深刻
刚好有需求,决定研究学习一下Semgrep
官方提供了非常棒的教程,强烈推荐学习一下 https://semgrep.dev/learn
本文是在学习和尝试实践后,个人的一些体验分享(基于Semgrep 0.88.0
)
ltdr
可以学习,暂时不推荐使用,但未来可期 跳到总结部分
像写代码一样写规则
这个我觉得是最厉害的地方,官网上也提到了
Write rules that look like your code
No painful and complex DSL
没有痛苦和复杂的DSL完全就是在说CodeQL
Semgrep的“DSL”部分很少,也很容易记,非常符合编程思维,学习成本可以说极低,30分钟从入门到精通,比如
...
是任意代码段
"..."
是任意字符串
$x
是x
变量
学习完learn
教程真的就可以去实践,写自己的规则
不像CodeQL,看完教程好像是懂了,但想要去写检测一个新的洞,文档翻烂,才拼拼凑凑写一个规则出来(CodeQL大佬带带我有啥技巧吗)
数据流分析
代码审计逃不开数据流分析,Semgrep当然也是支持的
官网例子 https://semgrep.dev/s/P8oz
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| rules: - id: taint-example languages: - python message: Found dangerous HTML output mode: taint pattern-sanitizers: - pattern: sanitize_input(...) pattern-sinks: - pattern: html_output(...) - pattern: eval(...) pattern-sources: - pattern: get_user_input(...) severity: WARNING
|
1 2 3 4 5 6 7 8 9 10
| def route1(): data = get_user_input() data = sanitize_input(data) return html_output(data)
def route2(): data = get_user_input() return html_output(data) // 检测到这里
|
看着非常简单,基本上定义好source
,sink
和sanitizers
就行
但是稍微变形一下就不行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| def route1(): data = get_user_input() data = sanitize_input(data) return html_output(data)
def route2(): data = get_user_input() return html_output(data) // 检测到
def html_output_wrap(data): return html_output(data)
def route3(): data = get_user_input() return html_output_wrap(data) // 无法检测到
|
污点传播分析还是比较弱,另外还有一些case也检测不到,就不一一列举了
规则复用
这个在实践中还是非常重要的,规则复用更像是知识的积累
Semgrep当然也是支持的,叫join
模式,也有例子文档 https://semgrep.dev/docs/experiments/join-mode/overview/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| rules: - id: flask-likely-xss mode: join join: refs: - rule: flask-user-input.yaml as: user-input - rule: unescaped-template-extension.yaml as: unescaped-extensions - rule: any-template-var.yaml renames: - from: '$...EXPR' to: '$VAR' as: template-vars on: - 'user-input.$VAR == unescaped-extensions.$VALUE' - 'unescaped-extensions.$VAR == template-vars.$VAR' - 'unescaped-extensions.$PATH > template-vars.path' message: | Detected a XSS vulnerability: '$VAR' is rendered unsafely in '$PATH'. severity: ERROR
|
导入flask-user-input.yaml
获取用户输入(source
)
导入any-template-var.yaml
获取模板渲染(sink
)
最后用神奇的on
语法,把source
和sink
连起来
看起来很棒不是,但是这个on
语法没有污点分析,也就说虽然上面的污点跟踪有些弱,但这里甚至都没有
join
模式线上用不了,我本地写个例子,检测eval一个变量的场景
1 2 3 4 5 6 7 8 9 10 11
| // eval.yaml rules: - id: eval patterns: - pattern: eval($X) - pattern-not: eval("...") message: $X languages: - js - ts severity: WARNING
|
检测取location
1 2 3 4 5 6 7 8 9
| // location.yaml rules: - id: location languages: - js - ts severity: INFO message: $VAR pattern: $VAR = location.$X
|
合并起来就是检测eval(location.$X),很显然会有xss,有以下代码
1 2 3 4 5 6 7 8 9 10 11 12
| let p1 = location.hash; let p2 = p1;
function param_wrap(param) { return param; }
let p3 = param_wrap(p2);
eval(p1); eval(p2); eval(p3);
|
使用join
模式写规则
1 2 3 4 5 6 7 8 9 10 11 12 13
| rules: - id: eval-join mode: join join: refs: - rule: location.yaml as: user-input - rule: eval.yaml as: eval on: - user-input.$VAR == eval.$X message: eval-join severity: ERROR
|
只能检测到eval(p1)
,但是如果使用taint
跑污点分析,都能检测出来
1 2 3 4 5 6 7 8 9 10 11 12
| rules: - id: eval-taint mode: taint pattern-sources: - pattern: location.$X pattern-sinks: - pattern: eval(...) message: eval-taint severity: ERROR languages: - js - ts
|
总结
Semgrep是一个非常年轻的开源项目(似乎20年才出现的),功能还不是很完善
污点分析弱,漏报率太高,意味着不能很好的挖洞
规则复用弱,知识不好积累,意味着难以形成工程
这两个连起来,可能适合比较小型,调用关系不复杂的源码场景,以及个人使用
比如js编译,混淆,调用关系复杂,中大型项目,团队合作就没法用了
如果能解决上面的问题,规则编写的简单将是巨大的优势