使用bandit对目标python代码进行安全函数扫描

使用bandit对目标python代码进行安全函数扫描,第1张

概述本文介绍了python安全危险函数扫描工具bandit的数种使用方法与技巧,同时也分析了bandit在实际项目中的性能表现,给予了读者是否在python开发项目中引入bandit的启发思考。 技术背景

在一些对python开源库代码的安全扫描中,我们有可能需要分析库中所使用到的函数是否会对代码的执行环境造成一些非预期的影响。典型的例如python的沙箱逃逸问题,通过一些python的第三方库可以执行系统shell命令,而这就不在python的沙箱防护范围之内了。关于python的沙箱逃逸问题,这里不作展开,这也是困扰业界多年的一个问题,连python官方也提过python的沙箱是没有完美的防护方案的,这里仅作为一个背景案例使用:

# subprocess_Popen.pyimport subprocessimport uuIDsubprocess.Popen('touch ' + str(uuID.uuID1()) +'.txt',shell = True)

这里演示的功能是使用subprocess函数库开启一个系统shell,并执行一个touch的指令,可以生成一个指定文件名的文件,类似于mkdir产生一个文件夹。我们可以看到这个文件成功执行后会在当前的目录下生成一个uuID随机命名的txt文件:

[dechin@dechin-manjaro bandit_test]$ python3 subprocess_Popen.py [dechin@dechin-manjaro bandit_test]$ ll总用量 4-rw-r--r-- 1 dechin dechin   0  1月 26 23:03 b7aa0fc8-5fe7-11eb-b5d3-058313e110e4.txt-rw-r--r-- 1 dechin dechin 123  1月 26 23:03 subprocess_Popen.py

然而,本次的关注点并不在与这个函数执行了什么功能,而是这个函数中用到了subprocess这个函数库。按照python的语言特点,当你的系统中如果存在这样的一个模块引用了subprocess库,那么任何可以调用该功能模块的函数,都可以调用到subprocess这个函数,以下是另外一个恶意用户的python代码

# bad.pyfrom subprocess_Popen import subprocess as subprocesssubprocess.Popen('touch bad.txt',shell = True)

该代码的目的是在不直接import subprocess的条件下,通过前面创建好的subprocess_Popen.py来进行搭桥调用subprocess的功能函数。这个脚本的执行结果如下:

[dechin@dechin-manjaro bandit_test]$ python3 bad.py [dechin@dechin-manjaro bandit_test]$ ll总用量 12-rw-r--r-- 1 dechin dechin    0  1月 26 23:13 0fda7ede-5fe9-11eb-80a8-ad279ab4e0a6.txt-rw-r--r-- 1 dechin dechin    0  1月 26 23:03 b7aa0fc8-5fe7-11eb-b5d3-058313e110e4.txt-rw-r--r-- 1 dechin dechin  113  1月 26 23:13 bad.py-rw-r--r-- 1 dechin dechin    0  1月 26 23:13 bad.txtdrwxr-xr-x 2 dechin dechin 4096  1月 26 23:13 __pycache__-rw-r--r-- 1 dechin dechin  123  1月 26 23:03 subprocess_Popen.py

这个结果意味着,我们成功的使用bad.py调用了subprocess_Popen.py中所引用的subprocess,成功touch了一个bad.txt的文件。

到这里我们的背景案例演示结束,但我们需要重新梳理这些案例中所包含的逻辑:我们原本是希望在自己的系统中不引入python的沙箱逃逸问题,我们会对其他人传递过来的代码进行扫描,如使用下文中将要介绍的bandit工具来屏蔽subprocess等"危险函数"。而如果我们在自己写的python库或者引入的第三方python库中存在类似于subprocess的引用,这就会导致我们的屏蔽失效,用户可以任意的通过这些引用的搭桥直接调用subprocess的函数功能。因此,在特殊的条件要求下,我们需要对自己的代码进行安全函数扫描,以免为其他人的系统带来不可预期的安全风险。bandit只是其中的一种安全函数扫描的工具,接下来我们介绍一下其基本安装和使用方法。

用pip安装bandit

这里直接使用pip来安装bandit,有需要的也可以从源码直接安装。关于在pip的使用中配置国内镜像源的方法,可以参考这篇博客中对python安装第三方库的介绍。

[dechin@dechin-manjaro bandit_test]$ python3 -m pip install banditCollecting bandit  Downloading bandit-1.7.0-py3-none-any.whl (115 kB)     |████████████████████████████████| 115 kB 101 kB/s Requirement already satisfIEd: PyYAML>=5.3.1 in /home/dechin/anaconda3/lib/python3.8/site-packages (from bandit) (5.3.1)Collecting gitpython>=1.0.1  Downloading gitpython-3.1.12-py3-none-any.whl (159 kB)     |████████████████████████████████| 159 kB 28 kB/s Requirement already satisfIEd: six>=1.10.0 in /home/dechin/anaconda3/lib/python3.8/site-packages (from bandit) (1.15.0)Collecting stevedore>=1.20.0  Downloading stevedore-3.3.0-py3-none-any.whl (49 kB)     |████████████████████████████████| 49 kB 25 kB/s Collecting gitdb<5,>=4.0.1  Downloading gitdb-4.0.5-py3-none-any.whl (63 kB)     |████████████████████████████████| 63 kB 28 kB/s Collecting pbr!=2.1.0,>=2.0.0  Downloading pbr-5.5.1-py2.py3-none-any.whl (106 kB)     |████████████████████████████████| 106 kB 26 kB/s Collecting smmap<4,>=3.0.1  Downloading smmap-3.0.5-py2.py3-none-any.whl (25 kB)Installing collected packages: smmap,gitdb,gitpython,pbr,stevedore,banditSuccessfully installed gitpython-3.1.12 bandit-1.7.0 gitdb-4.0.5 pbr-5.5.1 smmap-3.0.5 stevedore-3.3.0

安装结束之后,可以通过以下指令验证是否安装成功:

[dechin@dechin-manjaro bandit_test]$ bandit -husage: bandit [-h] [-r] [-a {file,vuln}] [-n CONTEXT_lines] [-c CONfig_file] [-p PROfile] [-t TESTS] [-s SKIPS] [-l] [-i] [-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 ...]]Bandit - a Python source code security analyzerpositional arguments:  targets               source file(s) or directory(s) to be testedoptional arguments:  -h,--help            show this help message and exit  -r,--recursive       find and process files in subdirectorIEs  -a {file,vuln},--aggregate {file,vuln}                        aggregate output by vulnerability (default) or by filename  -n CONTEXT_lines,--number CONTEXT_lines                        maximum number of code lines to output for each issue  -c CONfig_file,--configfile CONfig_file                        optional config file to use for selecting plugins and overrIDing defaults  -p PROfile,--profile PROfile                        profile to use (defaults to executing all tests)  -t TESTS,--tests TESTS                        comma-separated List of test IDs to run  -s SKIPS,--skip SKIPS                        comma-separated List of test IDs to skip  -l,--level           report only issues of a given severity level or higher (-l for LOW,-ll for MEDIUM,-lll for HIGH)  -i,--confIDence      report only issues of a given confIDence level or higher (-i for LOW,-ii for MEDIUM,-iii for HIGH)  -f {csv,yaml},--format {csv,yaml}                        specify output format  --msg-template MSG_TEMPLATE                        specify output message template (only usable with --format custom),see CUSTOM FORMAT section for List of available values  -o [OUTPUT_file],--output [OUTPUT_file]                        write report to filename  -v,--verbose         output extra information like excluded and included files  -d,--deBUG           turn on deBUG mode  -q,--quIEt,--silent                        only show output in the case of an error  --ignore-nosec        do not skip lines with # nosec comments  -x EXCLUDED_PATHS,--exclude EXCLUDED_PATHS                        comma-separated List of paths (glob patterns supported) to exclude from scan (note that these are in addition to the excluded paths provIDed in the config file) (default:                        .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)  -b BASEliNE,--baseline BASEliNE                        path of a baseline report to compare against (only JsON-formatted files are accepted)  --ini INI_PATH        path to a .bandit file that supplIEs command line arguments  --exit-zero           exit with 0,even with results found  --version             show program's version number and exitCUSTOM FORMATTING-----------------Available Tags:    {abspath},{relpath},{line},{test_ID},{severity},{msg},{confIDence},{range}Example usage:    Default template:    bandit -r examples/ --format custom --msg-template \    "{abspath}:{line}: {test_ID}[bandit]: {severity}: {msg}"    ProvIDes same output as:    bandit -r examples/ --format custom    Tags can also be formatted in python string.format() style:    bandit -r examples/ --format custom --msg-template \    "{relpath:20.20s}: {line:03}: {test_ID:^8}: DEFECT: {msg:>20}"    See python documentation for more information about formatting style:    https://docs.python.org/3/library/string.HTMLThe following tests were discovered and loaded:-----------------------------------------------        B101    assert_used        B102    exec_used        B103    set_bad_file_permissions        B104    hardcoded_bind_all_interfaces        B105    hardcoded_password_string        B106    hardcoded_password_funcarg        B107    hardcoded_password_default        B108    hardcoded_tmp_directory        B110    try_except_pass        B112    try_except_continue        B201    flask_deBUG_true        B301    pickle        B302    marshal        B303    md5        B304    ciphers        B305    cipher_modes        B306    mktemp_q        B307    eval        B308    mark_safe        B309    httpsconnection        B310    urllib_urlopen        B311    random        B312    telnetlib        B313    xml_bad_cElementTree        B314    xml_bad_ElementTree        B315    xml_bad_expatreader        B316    xml_bad_expatbuilder        B317    xml_bad_sax        B318    xml_bad_minIDom        B319    xml_bad_pulldom        B320    xml_bad_etree        B321    ftplib        B323    unverifIEd_context        B324    hashlib_new_insecure_functions        B325    tempnam        B401    import_telnetlib        B402    import_ftplib        B403    import_pickle        B404    import_subprocess        B405    import_xml_etree        B406    import_xml_sax        B407    import_xml_expat        B408    import_xml_minIDom        B409    import_xml_pulldom        B410    import_lxml        B411    import_xmlrpclib        B412    import_httpoxy        B413    import_pycrypto        B501    request_with_no_cert_valIDation        B502    ssl_with_bad_version        B503    ssl_with_bad_defaults        B504    ssl_with_no_version        B505    weak_cryptographic_key        B506    yaml_load        B507    ssh_no_host_key_verification        B601    paramiko_calls        B602    subprocess_popen_with_shell_equals_true        B603    subprocess_without_shell_equals_true        B604    any_other_function_with_shell_equals_true        B605    start_process_with_a_shell        B606    start_process_with_no_shell        B607    start_process_with_partial_path        B608    hardcoded_sql_Expressions        B609    linux_commands_wildcard_injection        B610    django_extra_used        B611    django_rawsql_used        B701    jinja2_autoescape_false        B702    use_of_mako_templates        B703    django_mark_safe

从这个列表中的屏蔽函数我们可以看出所谓的"危险函数"到底都有哪些,比如常用的subprocessrandom都被包含在内。subprocess是因为其对shell的调用而被列为"危险函数",而random则是因为其伪随机数的性质(这里简单说明一下,现在一般推荐使用secrets中的所谓安全随机数,但是实际上只有量子叠加测量才能够真正实现真随机数)。

bandit常用使用方法直接对py文件进行扫描:
[dechin@dechin-manjaro bandit_test]$ bandit subprocess_Popen.py [main]  INFO    profile include tests: None[main]  INFO    profile exclude tests: None[main]  INFO    cli include tests: None[main]  INFO    cli exclude tests: None[main]  INFO    running on Python 3.8.5[node_visitor]  INFO    Unable to find qualifIEd name for module: subprocess_Popen.pyRun started:2021-01-26 15:31:00.425603Test results:>> Issue: [B404:blackList] ConsIDer possible security implications associated with subprocess module.   Severity: Low   ConfIDence: High   Location: subprocess_Popen.py:3   More Info: https://bandit.readthedocs.io/en/latest/blackLists/blackList_imports.HTML#b404-import-subprocess23       import subprocess4       import uuID-------------------------------------------------->> Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True IDentifIEd,security issue.   Severity: High   ConfIDence: High   Location: subprocess_Popen.py:6   More Info: https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.HTML56       subprocess.Popen('touch ' + str(uuID.uuID1()) +'.txt',shell = True)--------------------------------------------------Code scanned:        Total lines of code: 3        Total lines skipped (#nosec): 0Run metrics:        Total issues (by severity):                Undefined: 0.0                Low: 1.0                Medium: 0.0                High: 1.0        Total issues (by confIDence):                Undefined: 0.0                Low: 0.0                Medium: 0.0                High: 2.0files skipped (0):

通过对刚才所创建的调用了危险函数subprocess的py文件subprocess_Popen.py的扫描,我们识别出了其中的"危险函数",注意这里的Issue编号是602,定级是Severity: Low ConfIDence: High。但是如果我们用bandit去扫描利用了其他函数对危险函数的调用搭桥来二次调用的bad.py文件,我们发现是另外一种结果:

[dechin@dechin-manjaro bandit_test]$ bandit bad.py [main]  INFO    profile include tests: None[main]  INFO    profile exclude tests: None[main]  INFO    cli include tests: None[main]  INFO    cli exclude tests: None[main]  INFO    running on Python 3.8.5[node_visitor]  INFO    Unable to find qualifIEd name for module: bad.pyRun started:2021-01-26 15:30:47.370468Test results:                                                                                                                                                                                                                               >> Issue: [B404:blackList] ConsIDer possible security implications associated with subprocess module.   Severity: Low   ConfIDence: High                                                                                                                                                                                                            Location: bad.py:3                                                                                                                                                                                                                          More Info: https://bandit.readthedocs.io/en/latest/blackLists/blackList_imports.HTML#b404-import-subprocess                                                                                                                              23       from subprocess_Popen import subprocess as subprocess45       subprocess.Popen('touch bad.txt',shell = True)-------------------------------------------------->> Issue: [B604:any_other_function_with_shell_equals_true] Function call with shell=True parameter IDentifIEd,possible security issue.   Severity: Medium   ConfIDence: Low                                                                                                                                                                                                          Location: bad.py:5                                                                                                                                                                                                                          More Info: https://bandit.readthedocs.io/en/latest/plugins/b604_any_other_function_with_shell_equals_true.HTML                                                                                                                           45       subprocess.Popen('touch bad.txt',shell = True)--------------------------------------------------Code scanned:                                                                                                                                                                                                                                       Total lines of code: 2        Total lines skipped (#nosec): 0Run metrics:                                                                                                                                                                                                                                        Total issues (by severity):                Undefined: 0.0                Low: 1.0                Medium: 1.0                High: 0.0        Total issues (by confIDence):                Undefined: 0.0                Low: 1.0                Medium: 0.0                High: 1.0files skipped (0):

注意这里虽然实现的功能跟上面那个例子是一样的,但是这里的Issue编号为604,定级也变成了Severity: Medium ConfIDence: Low。这里的关键并不是定级变成了什么,而是定级被改变了,这是因为bandit是通过对字符串的处理来识别危险函数的,因此对于这种二次调用的特殊场景,bandit不一定都能够准确的识别出来对危险函数的调用,甚至可能出现二次调用后,完全无法识别风险函数的使用的可能性。

扫描一个目录下的所有py文件,并将结果写入txt文件
[dechin@dechin-manjaro bandit_test]$ bandit *.py -o test_bandit.txt -f txt[main]  INFO    profile include tests: None[main]  INFO    profile exclude tests: None[main]  INFO    cli include tests: None[main]  INFO    cli exclude tests: None[main]  INFO    running on Python 3.8.5[node_visitor]  INFO    Unable to find qualifIEd name for module: bad.py[node_visitor]  INFO    Unable to find qualifIEd name for module: subprocess_Popen.py[text]  INFO    Text output written to file: test_bandit.txt

该案例就扫描了当前目录下的所有py文件,其实就是bad.pysubprocess_Popen.py这两个,并且将最终的扫描结果保存至test_bandit.txt文件中,这里我们就不展示txt文件的具体内容,大概就是将上一章节的两个执行结果进行了整合。

扫描一个目录下的多层文件夹中的py文件,并将结果写入HTML文件

假如我们有如下所示的一个目录结构需要进行扫描测试:

[dechin@dechin-manjaro bandit_test]$ tree.├── bad.py├── bad.txt├── level2│   └── test_random.py├── subprocess_Popen.py├── test_bandit.HTML└── test_bandit.txt1 directory,6 files[dechin@dechin-manjaro bandit_test]$ cat level2/test_random.py # test_bandit.pyimport randoma = random.random()

我们可以在当前目录下执行如下指令:

[dechin@dechin-manjaro bandit_test]$ bandit -r . -f HTML -o test_bandit.HTML[main]  INFO    profile include tests: None[main]  INFO    profile exclude tests: None[main]  INFO    cli include tests: None[main]  INFO    cli exclude tests: None[main]  INFO    running on Python 3.8.5[HTML]  INFO    HTML output written to file: test_bandit.HTML

这里我们得到的结果是一个test_bandit.HTML文件,文件内容如下图所示:

使用配置文件禁用部分Issue
在执行目录下创建一个.bandit文件,作如下配置就可以避免对B404的审查:
[bandit]skips: B404

执行的扫描结果如下图所示,我们可以看到B404相关的Issue已经不在列表中了:

py文件中直接逃避bandit审计
在待扫描的py文件的对应风险函数后加上如下注释,即可在bandit审计过程中自动忽略:
# bad.pyfrom subprocess_Popen import subprocess as sbsb.Popen('touch bad.txt',shell = 1) # nosec

这里我们可以看到最终的审计结果中,B604也随之而不见了,如下图所示。从这个案例中我们也可以知悉,bandit并不是一个用来作安全防护的工具,仅仅是用来做比较初步的python代码安全函数使用规范的审查工作,而扫描出来的问题是否处理,其实最终还是取决于开发者自己。

bandit简单性能测试

众所周知python语言的性能是极其受限的,因此bandit的性能也有可能十分的低下,这里让我们来定量的测试一下bandit的性能到底在什么水准。首先我们创建一个10000行的py文件,内容全部为危险函数的使用:

# gen.pyimport oswith open('test_bandit_power.py','w') as py_file:    py_file.write('import subprocess as sb\n')    for i in range(10000):        py_file.write('sb.Popen(\'whoami\',shell = 1)\n')

通过执行python3 gen.py就可以生成一个10000行的危险函数文件test_bandit_power.py,大约300KB的大小。此时我们针对这单个的文件进行bandit扫描测试,我们发现这个过程极为漫长,并且生成了大量的错误日志:

[dechin@dechin-manjaro bandit_test]$ time bandit test_bandit_power.py -f HTML -o test_power.HTML[main]  INFO    profile include tests: None[main]  INFO    profile exclude tests: None[main]  INFO    cli include tests: None[main]  INFO    cli exclude tests: None[main]  INFO    running on Python 3.8.5[node_visitor]  INFO    Unable to find qualifIEd name for module: test_bandit_power.py[HTML]  INFO    HTML output written to file: test_power.HTMLreal    0m6.239suser    0m6.082ssys     0m0.150s

我们可以简单估算,如果10000行的代码都需要6s的时间来进行扫描,那么对于比较大的项目的1000000+的代码的扫描时间,则有可能达到10min往上,这个时间虽然也不是特别长,但是对于大型的项目而言这绝对不是一个非常高效的选择。

总结概要

在一些对安全性要求较高的开发项目中,有可能会禁止使用危险函数,如subprocess等。而bandit的作用旨在通过对代码的扫描自动化的给出安全危险函数分析意见,至于是否采纳,还是以不同项目的管理者需求为准。同时经过我们的测试发现,bandit在实际使用场景下性能表现并不如意,因此在大型项目中我们并不推荐使用,如果一定要使用也可以考虑进行针对性的配置。

版权声明

本文首发链接为:https://www.cnblogs.com/dechinphy/p/bandit.html
作者ID:DechinPhy
更多原著文章请参考:https://www.cnblogs.com/dechinphy/

总结

以上是内存溢出为你收集整理的使用bandit对目标python代码进行安全函数扫描全部内容,希望文章能够帮你解决使用bandit对目标python代码进行安全函数扫描所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1189575.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存