静态分析是一个非常有用的工具,使用它可以帮助开发者或者安全人员在开发阶段就能发现代码中存在的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运行,可以让你项目的静态分析变得非常容易,小伙伴们,路过不要错过,都可以尝试一下。