Semgrep代码审计体验
最近有一些代码审计需要,研究了一下自动化审计工具
考虑了一些备选工具
-
LoRexxar大佬的审计工具,文档似乎比较老旧,学习成本比较高,试用了一下就放着了
-
GitHub搞得审计工具,QL逐渐流行起来,很多师傅写过文章
-
很早之前看过介绍文章,简单的规则编写让我印象非常深刻
刚好有需求,决定研究学习一下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
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
def route1(): data = get_user_input() data = sanitize_input(data) # ok: taint-example return html_output(data) def route2(): data = get_user_input() # ruleid: taint-example return html_output(data) // 检测到这里
看着非常简单,基本上定义好 source
, sink
和 sanitizers
就行
但是稍微变形一下就不行了
def route1(): data = get_user_input() data = sanitize_input(data) # ok: taint-example return html_output(data) def route2(): data = get_user_input() # ruleid: taint-example 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/
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一个变量的场景
// eval.yaml rules: - id: eval patterns: - pattern: eval($X) - pattern-not: eval("...") message: $X languages: - js - ts severity: WARNING
检测取location
// location.yaml rules: - id: location languages: - js - ts severity: INFO message: $VAR pattern: $VAR = location.$X
合并起来就是检测eval(location.$X),很显然会有xss,有以下代码
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
模式写规则
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
跑污点分析,都能检测出来
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编译,混淆,调用关系复杂,中大型项目,团队合作就没法用了
如果能解决上面的问题,规则编写的简单将是巨大的优势