靜態分析是一個非常有用的工具,使用它可以幫助開發者或者安全人員在開發階段就能發現程式碼中存在的bug和安全問題。靜態分析是一個綜合性和系統性的工程,對於每一個開發者和安全人員來說了解其原理,並能使用工具進行初步的分析很有必要。本文我們介紹一個開源的快速高效的多語言靜態分析工具Semgrep,透過在Docker中設定基本Semgrep環境,並用一些簡單的例子說明其用法。
概述諸如pylint的Python或eslint的JavaScript之類的linter非常適合通用的廣泛語言標準。但是程式碼審查中的常見問題呢,例如使用列印語句而不是記錄程式,或者在for迴圈(特定於Go)中使用defer語句,或者多層巢狀迴圈等。
大多數開發人員沒有使用語言解析的經驗。因此,在中小型團隊中看到自定義Lint規則並不常見。儘管沒有哪一種Linter或語言比其他Linter複雜得多(全都是AST操作),但是學習每種語言Linter的AST和框架要付出很小的代價。
semgrep規則的一個優點是,可以學習semgrep模式匹配語法(這非常簡單),然後可以為想要為其編寫規則的任何語言編寫規則。
Semgrep使用程式碼的標準表達進行模式匹配,而無需複雜的查詢或者正則。可用於在DevSecOps各個階段:程式碼編寫,程式碼提交或者CI執行時發現Bug和漏洞。其精確的規則看起來就像要搜尋的程式碼,無需遍歷抽象語法樹或與正則表示式死扛。與傳統的正則表示式(和傳統的grep)不同,它可以找到遞迴模式。這使其特別有用,可以作為學習查詢任何語言模式的工具。
Semgrep還支援容器化方式部署和執行,由emgrep官方登錄檔中,有Semgrep社群維護的包安全性,正確性,效能,程式碼質量和Bug等各方面的1000多規則可直接拿來使用。
Semgrep軟體安全公司r2c開發並提供商業支援。目前已經有大量的企業用於生產環境中,也有很多工具比如NodeJsScan之類底層支援引擎。
基本準備本文中我們所有的例子都需要執行docker,並基於semgrep基本映象returntocorp/semgrep。docker安裝和配置過程我們不在介紹,首先從docker官方拉一個最新的映象備用:
docker pull returntocorp/semgrep:latest
semgrep有應線上工具(semgrep.dev/editor/),如果沒有docker環境的同學,可以透過線上工具嘗試例子。
在PHP中發現eval語句假如希望指令碼在PHP中使用eval函式時候告警:
php/test.php
<?php$var = "var";if (isset($_GET["arg"])){$arg = $_GET["arg"];eval("\$var = $arg;");echo "\$var =".$vareval(bar);# eval(foo)echo(eval("\$var = $arg;"));}
semgrep所有執行依賴於一個yml的配置檔案config.yml,基本規則如下:
rules:
- id: cc-1pattern: |exec(...)message: |severity: WARNING我們可以在message部分增加警告的內容:rules:- id: cc-1pattern: |exec(...)message: |使用了不安全的exec函式severity: WARNING
配置部分還要增加兩個規則物件中包括兩個鍵:mode和languages。
rules:- id: my_pattern_idpattern: |exec(...)message: |severity: WARNINGmode: searchlanguages: ["generic"]
languages部分可以設定具體語言比如php或者用generic。如果設定了具體語言會對其做語法簡單,如果語法檢查不透過則不會執行搜尋。我們透過以下語句執行semgrep Docker映像:
docker run -v "${PWD}:/src" returntocorp/semgrep --config=config.yml php
發現4個語句中使用了eval,也包括我們註釋掉的語句。
對比language設定為php時候的執行:
有錯誤,我們增加引數—verbose,以獲得更詳細的錯誤資訊:
應該我們第7行少了個分號,導致語法錯誤。我們修改此語法錯誤,再執行:
發現了三個語句,註釋部分自動給去除了。
發現三重巢狀迴圈下一個例子,我們使用一個稍微負載點,在golang程式碼查詢一個三重巢狀的迴圈,程式碼(golang/test1.go):
package mainimport "log"func main() {for i := 0; i < 10; i++ {log.Print(i)for j := 0; j < 100; j++ {c := i * jgoing := truek := 0for going {if k == c {break}k++log.Print(k)}}}}
如果要查詢巢狀for迴圈,則需要搜尋由任意語法包圍的迴圈。Semgrep的...語法,非常適合,該操作使。我們修改golang搜尋配置go-config.yml為:
rules:
- id: triple-nest-looppattern: |for ... {...for ... {...for ... {...}...}...}message: |使用了三層巢狀for迴圈severity: WARNINGmode: searchlanguages: ["generic"]
執行semgrep:
docker run -v "${PWD}:/src" returntocorp/semgrep --config=go-config.yml golang
靜態分析的侷限性
我們將迴圈部分重構為函式呼叫,再試試(golang/loopy.go
):
package mainimport "log"func inner(i, j int) {c := i * jgoing := truek := 0for going {if k == c {break}k++log.Print(k)}}func main() {for i := 0; i < 10; i++ {log.Print(i)for j := 0; j < 100; j++ {inner(i, j)}}}
並再次執行semgrep:
docker run -v "${PWD}:/src" returntocorp/semgrep --config=go-config.yml golang
結果還跟上面的一樣,由於函式打包,語法上不再顯示為三層迴圈,所以semgrep匹配不了模式。
使用現有規則進行xss漏洞掃描我們前面也提到,除了一般掃描外semgrep官方登錄檔維護了大量的規則,包括基本語法、安全加強、程式碼質量的規則,這樣規則可以直接下載載入,使用方法:
semgrep --config "規則"
比如,我們上面第一部分的eval語句,在官方就有一個對應的規則r/php.lang.security.eval-use.eval-use
我們可以直接執行:
docker run --rm -v "${PWD}:/src" returntocorp/semgrep:latest --config=" r/php.lang.security.eval-use.eval-use
" php,其結果和第一步分的一樣:
對Web開發中,最常見的一個漏洞就是xss漏洞,semgrep也有個專門xss漏洞掃描的規則集合p/xss,包括多個語言的60條規則。
xss集合的掃碼可以用
semgrep --config "p/xss"
我們可以直接在docker中使用:
docker run --rm -v "${PWD}:/src" returntocorp/semgrep:latest --config="p/xss" golang
直接會從官方登錄檔下載規則,並按使用規則進行掃描,結果發現一個問題,同樣方法,可以利用現有規則對自己的程式碼進行掃描。
總結學習一種語言以高層編寫語法規則以強制執行程式碼行為仍然非常有用。semgrep使用通用的語法匹配器可幫助輕鬆編寫規則,可以用現有規則來對自己程式碼進行掃描。總之,基於Docker執行,可以讓你專案的靜態分析變得非常容易,小夥伴們,路過不要錯過,都可以嘗試一下。