用 Bandit 做 Python 代码静态安全分析
Bandit 是什么?
Bandit 是一个用来检查 Python 代码中常见安全问题的工具,它会处理各个源代码文件,解析出 AST(抽象语法树),然后对 AST 节点执行一组对应的插件。当 Bandit 完成检查之后,它能生成一封安全报告。
安装说明:参见 GitHub 项目主页。
编写自定义的检查
1 |
|
@bandit.checks('Call')
: 仅仅检查类型为Call
的 AST Nodereturn bandit.Issue(...)
: 返回一个 Security Issue
源码分析
入口在 cli/main.py
的 main()
先初始化了一堆参数,然后在这里创建了一个关键的 BanditManager 对象,之后的事情都是由它来完成的:
1 | b_mgr = b_manager.BanditManager(b_conf, args.agg_type, args.debug, |
扫描文件
紧接着就能看到这行代码:
1 | # initiate file discovery step within Bandit Manager |
让我们跟进去,然后看看 discover_files()
都做了些什么:(代码太长就不贴了)
- 处理 include/exclude 参数
- 如果有 include 就只看这里面的文件,否则扫描所有文件
- 如果有 exclude,之后扫描的时候要去掉这些文件
- 对所有指定的目标进行扫描
- 如果设置了 recursive 选项,就递归地遍历子目录。
最后把遍历的结果排序并以列表的形式存放在 self.files_list
中。
运行 Tests
回到 main()
函数中,再往下看一点
1 | # initiate execution of tests within Bandit Manager |
看来这里是核心的一步,当然要走进去看看:(代码不贴了)
- 枚举刚刚列表中的所有文件,读出来、并调用
_parse_file()
处理之。 - 如果处理失败了,也记下来,最后汇总输出会用到。
继续跟进 _parse_file()
,发现只是个包装,进入
_execute_ast_visitor()
1 | def _execute_ast_visitor(self, fname, data, nosec_lines): |
这个 BanditNodeVisitor 虽然没有继承标准库里的 ast.NodeVisitor 但实际上做的工作就是那样的——遍历所有 AST Node,同时对各个类型的 Node 执行对应的函数。
在 BanditNodeVisitor 中定义了很多类似 visit_Call
,
visit_FunctionDef
, visit_Str
这样奇怪名字的函数,顾名思义就是对各个类型的 Node
所运行的函数。遍历的逻辑看 visit
函数。
以 visit_Call
为例:
1 | def visit_Call(self, node): |
其实很简单,把对应的一些上下文信息 extract 出来并存到
self.context
,然后用 tester.run_tests
执行所有对应 Call Node 的检查。
所以 run_tests()
的逻辑你应该能猜到个大概了:
- 拿到所有类型为
checktype
的检查 - 对每个检查,以当前的
context
作为参数做检查,如果检查出问题就存起来
输出结果
再回到 main()
函数中,再往下就是输出:
1 | # trigger output of results by Bandit Manager |
Severity 和 Confidence 都是用来过滤的。最后输出到指定的形式。