Semgrep代碼審計體驗
最近有一些代碼審計需要,研究了一下自動化審計工具
考慮了一些備選工具
-
LoRexxar大佬的審計工具,文檔似乎比較老舊,學習成本比較高,試用了一下就放着了
-
GitHub搞得審計工具,QL逐漸流行起來,很多師傅寫過文章
-
很早之前看過介紹文章,簡單的規則編寫讓我印象非常深刻
剛好有需求,決定研究學習一下Semgrep
官方提供了非常棒的教程,強烈推薦學習一下 http://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當然也是支持的
官網例子 http://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
模式,也有例子文檔 http://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編譯,混淆,調用關係複雜,中大型項目,團隊合作就沒法用了
如果能解決上面的問題,規則編寫的簡單將是巨大的優勢