入口
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
中,是非常典型的递归下降。
输出结果
与扫描方式关联不大,鸽了。