Semgrep代碼靜態分析工具:使用Docker進行簡單查詢
靜態分析是一個非常有用的工具,使用它可以幫助開發者或者安全人員在開發階段就能發現代碼中存在的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 =".$var
- eval(
- bar
- );
- # eval(foo)
- echo(eval("\$var = $arg;"));
- }
semgrep所有運行依賴于一個yml的配置文件config.yml,基本規則如下:
rules:
- - id: cc-1
- pattern: |
- exec(...)
- message: |
- severity: WARNING
- 我們可以在message部分增加警告的內容:
- rules:
- - id: cc-1
- pattern: |
- exec(...)
- message: |
- 使用了不安全的exec函數
- severity: WARNING
配置部分還要增加兩個規則對象中包括兩個鍵:mode和languages。
- rules:
- - id: my_pattern_id
- pattern: |
- exec(...)
- message: |
- severity: WARNING
- mode: search
- languages: ["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 main
- import "log"
- func main() {
- for i := 0; i < 10; i++ {
- log.Print(i)
- for j := 0; j < 100; j++ {
- c := i * j
- going := true
- k := 0
- for going {
- if k == c {
- break
- }
- k++
- log.Print(k)
- }
- }
- }
- }
如果要查找嵌套for循環,則需要搜索由任意語法包圍的循環。Semgrep的...語法,非常適合,該操作使。我們修改golang搜索配置go-config.yml為:
rules:
- - id: triple-nest-loop
- pattern: |
- for ... {
- ...
- for ... {
- ...
- for ... {
- ...
- }
- ...
- }
- ...
- }
- message: |
- 使用了三層嵌套for循環
- severity: WARNING
- mode: search
- languages: ["generic"]
運行semgrep:
- docker run -v "${PWD}:/src" returntocorp/semgrep --config=go-config.yml golang

靜態分析的局限性
我們將循環部分重構為函數調用,再試試(golang/loopy.go
):
- package main
- import "log"
- func inner(i, j int) {
- c := i * j
- going := true
- k := 0
- for 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運行,可以讓你項目的靜態分析變得非常容易,小伙伴們,路過不要錯過,都可以嘗試一下。

























