入口
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.FileSet,Syntax []*ast.File,TypesInfo *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会去匹配fmt和Println是否在规则中,之前添加了几个。
如果在规则中的话,回到Match中,45行判断是否为exec.CommandContext,如果是,参数后推一个,然后进入分析参数环节。
对参数进行遍历,调用gosec.TryResolve进行一个简单的数据流传播,判断最终是否为非BasicLit,如果条件成立,则判断为漏洞,记录各种信息。
传播方式在\resolve.go中,是非常典型的递归下降。
输出结果
与扫描方式关联不大,鸽了。




















