入口

main函数存在于\cmd\gosec\main.go中,简化下基本分为五步

从上到下分别为: 加载规则、新建分析器、加载packages、进行分析、输出结果

加载规则

ruleDefinitions := loadRules(*flagRulesInclude, *flagRulesExclude) 传入两个参数,先不用急着去看这两个参数是从哪里来的,如果阅读过README,就会发现

ok,这两个参数就控制加载规则的种类,懂了。那就跟进函数定义

对于filter,不是扫描的重点,所以直接跟进rules.Generate\rules\rulelist.go

以两个字符串加一个函数作为一条规则,随机挑选一个规则,去看看他的函数做了什么事情,这里选取G204这条规则

\rules\subproc.go

callList中添加规则,rule.Add第一参数为package名,这里取的是import的字段,按ast来说就是ImportSpec的值。然后返回rule与断言类型,ast.Node是个接口,这里这样就确定了ast.Node的类型为ast.CallExpr也就是函数调用,这里的作用在后续规则匹配的时候会说。

然后把RuleDefinition加到ruleMap中,这里规则的构造函数还没执行。

这样规则的加载就完成了。

新建分析器

这里重点关注analyzer.LoadRules(ruleDefinitions.Builders())

简单点就是把RuleList里的RuleDefinition中的Description字段忽略,然后放入到builders里。

\analyzer.go

执行规则的构造函数。

ruleset中注册规则,规则构造函数的第二个返回值就在这里用上了。联系上下文,这里就是在ruleset中添加callexpr类型的点的规则为之前加载的rule

加载packages

直接关注这里,先考虑只扫一个目录。

\helpers.go

先判断输入的路径结尾是否为...,例如/tmp/testcase/... 如果不是,就直接返回;如果是,说明是目录递归,去除...后,进入目录遍历,找出所有扩展名为.go的文件,经filepath.Dir处理拿到目录后,判断是否在排除的目录中,如果不是,放入map中,最后处理成数组返回。

\main.go

\analyzer.go

利用go内置的golang.org/x/tools/go/packages库来加载,首先是构造一个config,更多的字段可以参考https://pkg.go.dev/golang.org/x/tools/go/packages#Config 然后调用gosec.load来加载package。

首先利用go/build库来获取pkgPath目录下的gofile和cgofile的路径,如果设置了test标志位,那么测试文件也将被包含。 然后就是调用packages.Load来加载由这些文件组成的packages.Package,其中包含的字段参考https://pkg.go.dev/golang.org/x/tools/go/packages#Package 例如Fset *token.FileSetSyntax []*ast.FileTypesInfo *types.Info等,对后续扫描比较重要。 通常来说,一个package的文件都在同一目录下,所以上面加载的就是众多package中的一个。 加载完一个package后,首先假设加载成功没有任何报错,那么就会进入gosec.Check进行扫描。

进行分析

遍历packages.Package中的pkg.Syntax,也就是[]*ast.File,各个文件的ast根节点,然后设置上下文,例如fset,typeinfo等信息,然后就开始遍历ast。 ast.walk就会去不断调用analyzer的visit函数,直到遍历完。

上面一大段都是处理忽略情况,重点关注347行。 \rule.go

之前加载规则时候,注册了callexpr对应的规则在ruleset中,这里就用到了。如果遍历到的ast节点类型为callexpr,就会取出之前注册的规则。当前这里只是简化了,真实情况gosec对不同类型的ast节点,注册了不同的规则。 取出规则后就会调用规则中的Match函数。

subproc match

这里简单说说命令执行是怎么检测的。

首先调用ContainsPkgCallExpr,判断进来的callexpr是否是规则需要的。

GetCallInfo获取callexpr的package名或者类型名以及函数名称,例如fmt.Println就会返回fmt和Println。 然后GetImportPath就会去判断这个fmt是否是当前package引入的。 最后c.Contains会去匹配fmtPrintln是否在规则中,之前添加了几个。

如果在规则中的话,回到Match中,45行判断是否为exec.CommandContext,如果是,参数后推一个,然后进入分析参数环节。 对参数进行遍历,调用gosec.TryResolve进行一个简单的数据流传播,判断最终是否为非BasicLit,如果条件成立,则判断为漏洞,记录各种信息。 传播方式在\resolve.go中,是非常典型的递归下降。

输出结果

与扫描方式关联不大,鸽了。