Bandit 是安全使用 python 中的一个问题的分析,它会处理代码后续结果源代码安全工具文件,解析出 AST 分支树运行的,当 Bandit 扫描生成生成报告
项目地址:https://github.com/PyCQA/bandit
项目文档:https://bandit.readthedocs.io/en/latest/
安装使用
直接使用的话用pip下载智能
pip3 安装强盗
检测存在漏洞的烧瓶项目bandit -r ./
自定义漏洞检测
在bandit
利用漏洞库的内容被检测比,并来检测漏洞。编造的检测代码bandit/plugins
也可以在文件夹中扫描,用户可以自己的测试文件来检测漏洞,方便检测漏洞。bandit
的扩展。
现有的bandit漏洞库可以检查文件的隐私权限、硬件编码、硬件临时编码、密码未设置、编码SQL语句等类型的漏洞
可以在bandit -h
查看
用户可以通过完成方式完成 Bandit 的自定义漏洞
写自定义插件
以app_debug.py
插件为例,该插件检测flask服务器是否在生产环境开启debug
模式
从bandit.core导入bandit从 bandit.core 导入 问题import test_properties作为测试 @test.test_id ( "B201" ) @test.checks ( "Call" ) def flask_debug_true ( context ):
if context . is_module_imported_like ( "flask" ):
if context . call_function_name_qual 。以(“.run” )结尾:如果上下文。check_call_arg_value ( "debug" , "True" ):返回强盗。问题(严重性=强盗。
HIGH ,
信心=强盗。中等,
cwe =问题。周。CODE_INJECTION ,
text = "一个 Flask 应用程序似乎在 debug=True 的情况下运行,"
"这会暴露 Werkzeug 调试器并允许 "
"执行任意代码。" ,
lineno =上下文。get_lineno_for_call_arg (“调试” ),
)
@test.test_id("B201")
是编号的各个漏洞有特定的编号,Bandit 的库中结束,到B101,中结束了编号的类型,从编号的第二位进行了B101,B,编号的编号为70
@test.checks("Call")
是类型漏洞,这里的Call
表示漏洞是由函数调用引发的,除此之外还有、、Str
等类型AssertExec
在漏洞检测插件的正文,调用bandit的组合函数,利用这些组合函数来编写我们的多个文件和漏洞文件
Bandit 编写函数表
现在查看app_debug
插件正文就很容易理解了,表示当前节点存在的时间环境导入了flask
包,同时调用该节点的定义名后缀为.run
,表示参数名和参数值debug=True
,如果这些条件都满足,则漏洞漏洞
设置imports.py配置文件
imports.py
用于检测可能会引发危险的import语句,定义了bandit
里面B401->B415的漏洞。例如可能会导致python反序列化漏洞的相关库
个人这部分还可以再细分一点,像safety-db感觉检测漏洞的特定版本的库
设置calls.py配置文件使用calls.py
检测文件中可能存在漏洞的调用,定义了B301->B325的漏洞,需要检测到漏洞包的导入+漏洞包代码中的调用,需要检测的内容通常由几句部分组成,.
隔壁必须将每个部分都进行匹配之后才可以检测出来,以B303中的hashlib.md5
示例展开
当程序中同时出现import hashlib
和hashlib.md5()
时,bandit
能够检测出漏洞;当程序中出现import hashlib
和hashlib.md2()
时不能检测漏洞
根本分析
git clone https://github.com/PyCQA/bandit
安装的库文件
pip3 install -r requirements.txt
入口文件bandit/cli/main.py
的main()
使用方法如下
usage: main.py [-h] [-r] [-a {file,vuln}] [-n CONTEXT_LINES] [-c CONFIG_FILE]
[-p PROFILE] [-t TESTS] [-s SKIPS]
[-l | --severity-level {all,low,medium,high}]
[-i | --confidence-level {all,low,medium,high}]
[-f {csv,custom,html,json,screen,txt,xml,yaml}]
[--msg-template MSG_TEMPLATE] [-o [OUTPUT_FILE]] [-v] [-d] [-q]
[--ignore-nosec] [-x EXCLUDED_PATHS] [-b BASELINE]
[--ini INI_PATH] [--exit-zero] [--version]
[targets [targets ...]]
在接下来main
的方法中,在活动活动中进行了初始化、获取用户参数,例如前面的输入-r
获取参数
解析器。add_argument (
"-r" ,
"--recursive" ,
dest = "recursive" ,
action = "store_true" ,
help = "在子目录中查找和处理文件" ,
)
表示文件的搜索和处理该下的目录
plugin_info = [
f "{a[0]} t {a[1].name}" for a in extension_mgr 。plugins_by_id 。items ()
]
blacklist_info = []
for a in extension_mgr 。黑名单。items ():
for b in a [ 1 ]:
blacklist_info 。附加( "{} t {}" .格式( b [ "id" ], b[ “名称” ]))
plugin_list = " nt " 。加入(排序(设置(plugin_info + blacklist_info )))
创作的作品可以由 两个部分组成
pluginblacklist
plugin
即在plugins
文件夹下的插件列表
blacklist
由两部分组成,详情可见bandit/blacklists
文件夹下的calls.py
和imports.py
的代码中继续初始化项目参数,创建重要的输入对象BanditManager
b_mgr = b_manager 。BanditManager (
b_conf ,
args . agg_type ,
args . debug ,
profile = profile ,
verbose = args . verbose ,
quiet = args . quiet ,
ignore_nosec = args . ignore_nosec ,
)
来discover_files
方法
b_mgr 。discover_files ( args.targets , args.recursive , args.excluded_pa ths ) _ _ _ _ _
该方法三个参数
目标扫描文件或目录
递归是否逻辑扫描
exclude_paths 不能扫描的后缀、文件、目录
后续的活动 获取我们需要的扫描文件
for fname in targets :
# 如果这是一个目录并且设置了递归,
如果是 os则查找所有文件。路径。isdir ( fname ):
如果 递归:
new_files , new_excluded = _get_files_from_dir (
fname ,
included_globs = included_globs ,
excluded_pa th_strings = exclude_path_globs ,) files_list 。更新(new_files )excluded_files 。更新
(新排除)
files_list
作为集合存储需要扫描的目标文件列表
excluded_files
作为集合存储不需要扫描的文件列表
回到main.py
,再进入b_mgr.run_tests()
,开始检测漏洞
全部需要检测的文件并进一步操作
对于 count , fname in enumerate ( files ):
LOG 。调试(“正在处理文件:%s ” , fname )
尝试:
如果 fname == "-" :
open_fd = os 。fdopen ( sys . stdin . fileno (), "rb" , 0 )
fdata = io . BytesIO ( open_fd . read ())
new_files_list = [
"<stdin>" if x == "-" else x for x in new_files_list
]
self 。_parse_file ("<stdin>" , fdata , new_files_list )
else :
with open ( fname , "rb" ) as fdata :
self 。_parse_file ( fname , fdata , new_files_list )
除了 OSError 为 e :
self 。跳过。附加(( fname , e . strerror ))
new_files_list 。删除( fname )
进入self._parse_file(fname, fdata, new_files_list)
核心功能
该函数三个参数
fname 检测文件名
fdata文件内容
new_files_list 待检测文件列表
后续进入score = self._execute_ast_visitor(fname, fdata, data, nosec_lines)
def _execute_ast_visitor ( self , fname , fdata , data , nosec_lines ):
"""对每个文件执行 AST 解析 :param fname: 被解析的文件名 :param data: 原始文件内容 :param lines: 要处理的代码行数 :return: 累计测试分数 """
score = []
res = b_node_visitor . BanditNodeVisitor (
fname ,
fdata 、
self.b_ma 、self.b_ts 、
self.debug 、nosec_l ines 、
self.metrics 、) _ _ _ _
_ _ _
分数 = res 。处理(数据)
自我。结果。扩展(res.tester.results )返回分数_ _ _ _
BanditNodeVisitor
中定义的函数,例如、、等等,很多visit_Import
名思义义就是对特定类型的任务执行的 AST 节点执行visit_ImportFromvisit_Callvisit_FunctionDef
process
方法中f_ast = ast.parse(data)
解析源文件为AST抽象语法树
在generic_visit(f_ast)
方法中结束AST偏差并进行类型的检测
我们前面的import
示例,以这里的检测任务是visit_Import
def visit_Import ( self , node ):
用于 node 中的 节点名。名称:
如果 节点名。姓名:
自我。import_aliases [节点名。asname ] = 节点名。命名
自我。进口。添加(节点名。名称)
自我。上下文[ “模块” ] = 节点名。姓名
自我。update_scores ( self.tester.run_tests ( self.context , “ Import ” ) ) _ _ _
其实就是把进口的包名,以及该节点的一些环境执行出来保存的问题self.context
,然后用节点检查,如果查出就起来tester.run_tests
Import
结束所有的一切都需要检测的文件中AST节点后,最后是输出结果
日志。调试(b_mgr.b_ma )日志。_ _ 调试(b_mgr.metrics )_ _
# 由 Bandit Manager 触发结果输出
sev_level = constants . 排名[参数。严重性 -1 ] conf_level =常量。_ 排名[参数。信心- 1 ] b_mgr 。output_results ( args.context_lines , sev_level , conf_level , args . output_file , args . output_format , args . _ _
味精模板,
)
检测实战
当然要考虑到相关的操作情况,这里列出的列表github上开源项目代码
Github 搜索文档
想要搜索python这些写的cms,我们会出现下面的结果,访问链接为:
我们使用官方API进行请求,根据规则编写API访问链接:https://api.github.com/search/repositories?q=cms+language:python&per_page=10&page=1&sort=updated
-
page: 第几页,从1开始(如果小于1,则默认为第1页)
-
per_page : 每页多少个项
我们获取到仓库地址之后下载到本地进行扫描
Github API 还有访问速度的限制Github Rate Limit Docs
对于未经身份验证的请求,限制性限制允许您每分钟最多提出 10 个请求,考虑到本地对仓库代码进行解析和漏洞检测也需要时间,我们不进行身份验证,每分钟内完成当页内容的漏洞检测
编写一个调用Github API进行仓库下载,并使用bandit
检测的脚本如下
进口 时间import requests import json import os import datetime import logging MAX_NUM = 2 def getRepItem ( keyword , per_page = 10 ):
for i in range ( 1 , MAX_NUM ):
starttime = datetime 。日期时间。now ()
url = "https://api.github.com/search/repositories?q={}&per_page={}&page={}" 。格式(关键字,每页,我)
代表=请求。获取(网址,超时= 5 )
项目= json 。加载(rep.text )[ ' items ' ] for j in range (len (items )):rep_url = items [ j ][ 'html_url' ] cloneRsp (rep_url )文件名= rep_url 。
split ( '/' )[ 4 ]
callBandit (文件名)
endtime = datetime 。日期时间。now ()
checkTime (( endtime - starttime ) . seconds )
返回def cloneRsp ( url ):
日志记录。信息(“克隆{}” 。格式(网址))
操作系统。系统('git clone {}' 。格式(url ))def callBandit (文件名):
记录。info ( "bandit {}" .format ( filename ) )
os . system ( "bandit -r ./{} -f html -o ./{}/scan_{}.html" .格式(文件名,文件名,文件名))def checkTime (运行时):
记录。info ( "runtime is {}" .format ( runtime ) )
if runtime < 60 :
time . 睡眠(62 - int (运行时))def main ():
getRepItem ( "cms+language:python" )如果 __name__ == '__main__' :
main ()
MAX_NUM
爬取的页数,getRepItem
符合要求搜索的关键词
运行之后最在clone
的项目文件夹下生成scan_项目名.html
的漏洞检测报告了
参考链接
-
https://ericfu.me/bandit-the-python-static-analyzer/
-
Python的脚本脆弱性检测研究与实现_刘佩
https://blog.csdn.net/Next_Second/article/details/78238328
来源:先知(https://xz.aliyun.com/t/11341#toc-1)
注:如有侵权请联系删除
欢迎大家加群一起讨论学习和交流
快乐要懂得分享,
才能加倍的快乐。
原文始发于微信公众号(衡阳信安):强盗工具分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论