试试用这个扫描你的python代码?Bandit使用及源码解读

部署运行你感兴趣的模型镜像

如何使用(不难 简单略过)

  1. 下载

    
    

    python

    代码解读

    复制代码

    pip install bandit
  2. 开始扫描 

  3. 等待扫描结果 

  4. 也可以导出为json文件格式,方便查看

命令行可输入参数

以下是 Bandit CLI 命令行工具的参数及其说明,格式为表格形式:

参数作用描述示例
-h--help显示帮助信息并退出。bandit -h
-r--recursive递归查找并处理子目录中的文件。bandit -r /path/to/project
-a {file,vuln}--aggregate {file,vuln}聚合输出内容,可按漏洞类型 (vuln) 或文件名 (file) 聚合。bandit -r /path/to/project -a vuln
-n CONTEXT_LINES--number CONTEXT_LINES为每个问题输出的最大代码行数。bandit -r /path/to/project -n 5
-c CONFIG_FILE--configfile CONFIG_FILE指定用于选择插件和覆盖默认设置的可选配置文件。bandit -c /path/to/config.yaml -r /path/to/project
-p PROFILE--profile PROFILE指定分析配置文件(默认为执行所有测试)。bandit -p profile_name -r /path/to/project
-t TESTS--tests TESTS指定要运行的测试 ID,多个 ID 用逗号分隔。bandit -t B101,B102 -r /path/to/project
-s SKIPS--skip SKIPS指定要跳过的测试 ID,多个 ID 用逗号分隔。bandit -s B104 -r /path/to/project
-l--level报告指定严重性级别或更高的所有问题:-l(低)、-ll(中)、-lll(高)。bandit -r /path/to/project -ll
--severity-level {all,low,medium,high}报告指定的严重性级别及更高级别的问题。bandit -r /path/to/project --severity-level high
-i--confidence报告指定置信度级别或更高的所有问题:-i(低)、-ii(中)、-iii(高)。bandit -r /path/to/project -ii
--confidence-level {all,low,medium,high}报告指定的置信度级别及更高级别的问题。bandit -r /path/to/project --confidence-level high
-f {csv,custom,html,json,screen,txt,xml,yaml}--format {csv,custom,html,json,screen,txt,xml,yaml}指定输出格式。bandit -r /path/to/project -f json
--msg-template MSG_TEMPLATE指定输出消息模板,仅与 --format custom 配合使用。bandit -r /path/to/project --format custom --msg-template
-o [OUTPUT_FILE]--output [OUTPUT_FILE]将报告写入文件,默认输出到控制台。bandit -r /path/to/project -o report.txt
-v--verbose输出额外信息,如排除的文件。bandit -r /path/to/project -v
-d--debug启用调试模式,显示调试信息。bandit -r /path/to/project -d
-q--quiet--silent仅在发生错误时显示输出。bandit -r /path/to/project -q
--ignore-nosec不跳过带有 # nosec 注释的行。bandit -r /path/to/project --ignore-nosec
-x EXCLUDED_PATHS--exclude EXCLUDED_PATHS排除特定路径,支持逗号分隔和通配符。bandit -r /path/to/project -x /path/to/exclude1,/path2
-b BASELINE--baseline BASELINE指定基线报告路径(仅支持 JSON 格式文件),用于与当前扫描结果对比。bandit -r /path/to/project -b baseline.json
--ini INI_PATH指定 .bandit 文件路径,文件中包含命令行参数。bandit -r /path/to/project --ini config.ini
--exit-zero即使找到问题,退出码也返回 0bandit -r /path/to/project --exit-zero
--version显示程序的版本号并退出。bandit --version

Bandit 扫描流程概述

  1. main函数入口

    1. bandit\bandit__main__.py

      
      

      css

      代码解读

      复制代码

      from bandit.cli import main ​ # main方法入口 main.main()
    2. 跳转进bandit\bandit\cli\main.py

      
      

      python

      代码解读

      复制代码

      def main():    """Bandit CLI."""  # 主函数,启动 Bandit 命令行工具 ​    # 初始化日志设置,优先根据命令行参数确定日志级别    debug = (        logging.DEBUG  # 如果传入了 -d 或 --debug,则设置为 DEBUG 级别        if "-d" in sys.argv or "--debug" in sys.argv        else logging.INFO  # 否则设置为 INFO 级别   )    # 剩余代码逻辑
  2. 文件发现与加载:位于bandit\bandit\cli\main.py

    • Bandit 首先会使用文件发现机制来确定哪些文件需要进行扫描。通常,它会扫描指定目录下的 Python 文件(.py)。这一步通过函数 discover_files() 来实现,它会递归查找符合条件的 Python 文件。
    
    

    ini

    代码解读

    复制代码

    def discover_files(self, targets, recursive=False, excluded_paths=""):    # 遍历目标文件夹并找到所有 Python 文件,忽略排除的文件
  3. 执行 Bandit 文件扫描:

    • 执行bandit测试:

      P:bandit\bandit\cli\main.py

    
    

    bash

    代码解读

    复制代码

    # 执行 Bandit 测试 b_mgr.run_tests()
    • 准备解析数据:

      P:bandit\bandit\core\manager.py

    
    

    ruby

    代码解读

    复制代码

    def run_tests(self): # 代码逻辑...       # 解析标准输入的数据    self._parse_file("<stdin>", fdata, new_files_list)
  4. AST 解析

    • 准备进行 AST 分析:

      P:bandit\bandit\core\manager.py

    
    

    ruby

    代码解读

    复制代码

    def _parse_file(self, fname, fdata, new_files_list):    # 业务逻辑...        # 执行 AST 访问者,进行静态分析    score = self._execute_ast_visitor(fname, fdata, data, nosec_lines)
    • 处理文件数据,开始解析并执行 AST 访问,获取风险分数:

      P:bandit\bandit\core\manager.py

    
    

    ruby

    代码解读

    复制代码

    def _execute_ast_visitor(self, fname, fdata, data, nosec_lines):    # 业务逻辑... ​    # 处理文件数据,开始解析并执行 AST 访问    score = res.process(data)
    • Bandit 使用 Python 内置的 ast 模块来解析代码文件并生成其 AST(抽象语法树) 。这一步主要在 BanditNodeVisitor 中完成。AST 是用于语法分析和代码遍历的核心数据结构。

      P:bandit\bandit\core\manager.py

    
    

    ruby

    代码解读

    复制代码

    def process(self, data): # 业务逻辑...     # 将给定的代码数据解析为 AST    f_ast = ast.parse(data)

    上面这行代码直接将读取到的 Python 源文件内容传入 ast.parse() 函数,生成对应的 AST 树。

  5. AST 遍历与插件执行

    • 生成了 AST 之后,Bandit 会遍历 AST 树,查找潜在的安全问题。这一步通过 generic_visit() 和 visit() 方法来实现。这些方法会遍历 AST 节点,并在发现指定类型的节点时调用相应的测试函数。
    
    

    scss

    代码解读

    复制代码

    def generic_visit(self, node):    for _, value in ast.iter_fields(node):        if isinstance(value, ast.AST):            self.visit(value)
  6. 插件的加载和执行

    • Bandit 使用插件机制来定义具体的安全规则,这些规则通过特定的 AST 节点类型来匹配代码中的安全问题。插件的注册和执行通过装饰器 @test.checks 和 @test.test_id 来实现。
    
    

    less

    代码解读

    复制代码

    @test.test_id("B324") @test.checks("Call") def hashlib(context):    # 匹配 hashlib 模块中的弱哈希函数,如 MD5 和 SHA1

    当遍历到 Call 类型的 AST 节点时,Bandit 会调用上面的 hashlib() 函数来检查是否使用了不安全的哈希函数。

  7. 扫描结果的生成与报告

    • 当某个测试函数检测到潜在的安全问题时,它会生成一个 Issue 对象,并将其添加到结果列表中。Bandit 最终会汇总这些问题,并生成扫描报告。

代码传入并进行匹配的细节(篇幅较长)

  1. 传入代码的时刻

    • 在 run_tests() 方法中,Bandit 会从发现的文件列表中读取每个文件的内容,并调用 _parse_file() 方法对文件进行解析。在 _parse_file() 中,文件数据会被传递给 ast.parse(),从而生成 AST。

      P:bandit\bandit\core\manager.py

    
    

    ruby

    代码解读

    复制代码

    def _parse_file(self, fname, fdata, new_files_list):    # 读取文件内容并解析为 AST    f_ast = ast.parse(data)
  2. AST 节点与插件匹配

    • AST 树生成后,Bandit 会通过 BanditNodeVisitor 遍历 AST 节点,并调用注册的插件来执行具体的安全检查。关键是 generic_visit() 和 visit() 函数,它们会根据节点类型决定调用哪个测试插件。

      P:bandit\bandit\core\node_visitor.py

    
    

    python

    代码解读

    复制代码

    def visit(self, node):    name = node.__class__.__name__  # 获取当前 AST 节点的类型(如 "Call"、"Import" 等)    method = "visit_" + name  # 动态选择访问方法 ( visit_Call...位于node_visitor.py)    visitor = getattr(self, method, None)  # 动态获取方法, 在本类获取visit_Call,visit_Bytes等方法 ​    if visitor is not None:        if self.debug:            LOG.debug("%s called (%s)", method, ast.dump(node))        visitor(node)  # 调用特定的访问方法    else:        # 没有特定访问方法时,直接运行测试。        self.update_scores(self.tester.run_tests(self.context, name))

    例如,当 AST 中有一个 Call 类型的节点(表示函数调用)时,Bandit 会调用 hashlib() 插件,检查这个函数调用是否涉及不安全的哈希算法。

  3. visitor 匹配逻辑(举其一visit_Call 为例):

    • visit_Call 方法,用于处理 AST 中的函数调用节点 (Call 类型)。它提取函数的全限定名 (qualname) 和简短名称 (name),将这些信息记录在上下文中,然后执行与函数调用相关的测试,更新评分。

      P:bandit\bandit\core\node_visitor.py

    
    

    python

    代码解读

    复制代码

       def visit_Call(self, node):        # 处理函数调用节点,并记录其上下文信息。        self.context["call"] = node        qualname = b_utils.get_call_name(node, self.import_aliases)        name = qualname.split(".")[-1] ​        self.context["qualname"] = qualname        self.context["name"] = name ​        # 运行与 Call 相关的测试        self.update_scores(self.tester.run_tests(self.context, "Call")) ​
  4. tester.run_tests()逻辑

    1. tester解释

      • Bandit 中的测试单位,由BanditNodeVisitor创建

        P:bandit\bandit\core\node_visitor.py

        
        

        ruby

        代码解读

        复制代码

        class BanditNodeVisitor:    def __init__(            self, fname, fdata, metaast, testset, debug, nosec_lines, metrics   ):        # 业务逻辑...           # 创建 BanditTester 实例,用于执行测试        self.tester = b_tester.BanditTester(            self.testset, self.debug, nosec_lines, metrics       )
    2. testset测试集(上方 self.testset, self.debug, nosec_lines, metrics 位置),由BanditTestSet进行创建

      P:bandit\bandit\core\test_set.py

      
      

      ruby

      代码解读

      复制代码

      class BanditTestSet:    def __init__(self, config, profile=None):        # 初始化 BanditTestSet 类的实例        if not profile:            profile = {}  # 如果未提供 profile,则初始化为空字典        extman = extension_loader.MANAGER  # 获取扩展管理器        filtering = self._get_filter(config, profile)  # 获取过滤后的测试 ID        # 根据过滤后的测试 ID,筛选出插件        self.plugins = [            p for p in extman.plugins if p.plugin._test_id in filtering       ]        # 加载内置插件并添加到插件列表        self.plugins.extend(self._load_builtins(filtering, profile))        # 加载测试配置        self._load_tests(config, self.plugins)
      • MANAGER(extman = extension_loader.MANAGER 位置)

        用于管理 Bandit 的格式化器、插件和黑名单测试。构造函数 __init__ 初始化时调用 load_formattersload_plugins 和 load_blacklists 方法,读取 Bandit 目录下的相关配置文件进行加载

        P:bandit\bandit\core\extension_loader.py

        
        

        ini

        代码解读

        复制代码

        class Manager:  # 这些是 Bandit 内置测试的 ID  builtin = ["B001"]  # 内置黑名单测试 ​  def __init__(          self,     # formatters:存储输出格式相关文件(如json, txt...)          formatters_namespace="bandit.formatters",     # plugins:存储模板文件,动态分析,更为复杂的扫描,上下文扫描(如检测硬编码, 注入问题...)          plugins_namespace="bandit.plugins",     # blacklists:黑名单扩展,只检测是否有特殊模块,不做复杂分析,直接上报(如是否导入“pickle”, “subprocess”包...)          blacklists_namespace="bandit.blacklists", ):      # 加载格式化器、插件和黑名单的扩展      self.load_formatters(formatters_namespace)      self.load_plugins(plugins_namespace)      self.load_blacklists(blacklists_namespace) ​
      • _load_tests(self._load_tests(config, self.plugins)位置)

        用于注册 tests 空字典集,在tests.get(checktype)中可获取tests中对应测试方法

        
        

        python

        代码解读

        复制代码

        def _load_tests(self, config, plugins):    """Builds a dict mapping tests to node types."""    self.tests = {}  # 初始化一个空字典,用于存储测试与节点类型的映射 ​    for plugin in plugins:  # 遍历传入的插件列表        if hasattr(plugin.plugin, "_takes_config"):            # 检查插件是否有 _takes_config 属性,用于确定是否需要配置            cfg = config.get_option(plugin.plugin._takes_config)  # 从配置中获取相应的配置选项 ​            if cfg is None:  # 如果配置未找到                genner = importlib.import_module(plugin.plugin.__module__)  # 动态导入插件模块                cfg = genner.gen_config(plugin.plugin._takes_config)  # 生成默认配置 ​            plugin.plugin._config = cfg  # 将配置赋值给插件 ​        for check in plugin.plugin._checks:  # 遍历插件的所有检查项 (_checks:"Files", "Call"...)            self.tests.setdefault(check, []).append(plugin.plugin)  # 将插件添加到对应的检查项列表中            LOG.debug(                "added function %s (%s) targeting %s",                plugin.name,                plugin.plugin._test_id,                check,           )  # 记录调试信息,显示添加的插件信息
    3. run_tests(self.context, "Call")

      主要执行分析逻辑的函数,下面只显示重要代码

      
      

      python

      代码解读

      复制代码

      def run_tests(self, raw_context, checktype):    # 获取指定类型的测试    tests = self.testset.get_tests(checktype) #checktype为"Call", "Str"等,tests则为从testset中获取一系列归属checktype的测试集    for test in tests:        name = test.__name__  # 获取测试的名称        # 复制原始上下文以供测试使用        temp_context = copy.copy(raw_context)        # 将原文件内容temp_context转化为新的上下文对象Context,Context内部封装了许多函数(如获取函数名,获取与上下文关联的原始 AST 节点...)        context = b_context.Context(temp_context) ​        try:            # 如果测试具有配置,则使用该配置进行测试            if hasattr(test, "_config"):                # 将context传进每一个test中处理分析(test:hardcoded_password_string(context)......,plugins或blacklists目录下)                result = test(context, test._config)            else:                result = test(context)  # 执行测试 ​            if result is not None:  # 如果结果有效                nosec_tests_to_skip = self._get_nosecs_from_contexts(                    temp_context, test_result=result               ) # 检查代码里是否有# nosec 注释,若有,则跳过                # 检查是否跳过测试                if nosec_tests_to_skip is not None:                    if not nosec_tests_to_skip:                        self.metrics.note_nosec()  # 记录 nosec 计数                        continue                    if result.test_id in nosec_tests_to_skip:                        self.metrics.note_skipped_test()  # 记录跳过的测试                        continue                self.results.append(result)  # 将结果添加到结果列表            else:                # 处理没有结果的情况                nosec_tests_to_skip = self._get_nosecs_from_contexts(temp_context)                if nosec_tests_to_skip and test._test_id in nosec_tests_to_skip:                    LOG.warning(                        f"nosec encountered ({test._test_id}), but no "                        f"failed test on line {temp_context['lineno']}"                   )    return scores  # 返回分数

AST树结构


ini

代码解读

复制代码

import ast import io ​ # 假设 'bandit' 是你的文件名 file_path = 'C:/Users/admin/Desktop/bandit/bandit/blacklists/utils.py'  # 替换为实际路径 ​ # 打开文件并读取内容 with open(file_path, "rb") as f:    fdata = io.BytesIO(f.read())  # 使用 BytesIO 处理文件数据 ​ # 读取数据并解析为 AST data = fdata.read() fdata.seek(0)  # 重置文件指针 parsed_ast = ast.parse(data)  # 解析 AST print(parsed_ast) # 打印 AST 的结构 print(ast.dump(parsed_ast, annotate_fields=True, include_attributes=True))

输入以上代码后,得到如下结果,可知ast树为python对象,bandit通过ast对象扫描结果来进行分析

  • parsed_ast 

  • ast.dump(parsed_ast) 

    具体结构分析

    
    

    ini

    代码解读

    复制代码

    Module(  # AST 树的根节点,表示一个 Python 模块    body=[  # 模块的主体部分,包含模块内的所有语句        Expr(  # 表达式节点,表示一行独立的表达式            value=Constant(  # 值节点,表示常量值                value='Utils module.',  # 常量的值                lineno=5,  # 该常量所在的代码行号(第5行)                col_offset=0,  # 该常量在行中的起始列(第0列)                end_lineno=5,  # 该常量结束的行号(与起始行号相同,都是第5行)                end_col_offset=20),  # 该常量在行中的结束列(第20列)            lineno=5,  # 表达式节点的行号            col_offset=0,  # 表达式节点的列偏移量            end_lineno=5,  # 表达式结束的行号            end_col_offset=20),  # 表达式结束的列号        FunctionDef(  # 函数定义节点,表示一个函数的定义            name='build_conf_dict',  # 函数的名字            args=arguments(  # 函数的参数列表                posonlyargs=[],  # 仅限位置的参数列表(在该例中为空)                args=[  # 函数的普通参数                    arg(  # 参数节点,表示一个参数                        arg='name',  # 参数名                        lineno=8,  # 参数定义的行号                        col_offset=20,  # 参数在行中的起始列                        end_lineno=8,  # 参数结束的行号                        end_col_offset=24),  # 参数结束的列号                    # .................                                        arg(  # 参数6                        arg='level',  # 参数名                        lineno=8,  # 参数定义的行号                        col_offset=56,  # 参数在行中的起始列                        end_lineno=8,  # 参数结束的行号                        end_col_offset=61)],  # 参数结束的列号                kwonlyargs=[],  # 关键字参数列表                kw_defaults=[],  # 关键字参数的默认值                defaults=[  # 参数的默认值列表                    Constant(  # 常量值,表示参数 'level' 的默认值                        value='MEDIUM',  # 常量的值                        lineno=8,  # 默认值的行号                        col_offset=62,  # 默认值在行中的起始列                        end_lineno=8,  # 默认值的结束行号                        end_col_offset=70)]),  # 默认值的结束列号            body=[  # 函数的主体部分,包含函数内部的所有语句                Expr(  # 表达式节点                    value=Constant(  # 常量值                        value='Build and return a blacklist configuration dict.',  # 常量值                        lineno=9,  # 常量值所在行号                        col_offset=4,  # 常量值在行中的起始列                        end_lineno=9,  # 常量值的结束行号                        end_col_offset=58),  # 常量值的结束列号                    lineno=9,  # 表达式的起始行号                    col_offset=4,  # 表达式的起始列                    end_lineno=9,  # 表达式的结束行号                    end_col_offset=58),  # 表达式的结束列号                Expr(  # 表示一个表达式语句节点                    value=Call(  # 表示一个函数调用节点                        func=Name(  # 表示函数名(这里是 'print')                            id='print',  # 函数名是 'print'                            ctx=Load(),  # 'Load' 表示变量在加载上下文中被使用(这里是函数调用)                            lineno=10,  # 函数名所在的行号                            col_offset=4,  # 函数名的起始列号                            end_lineno=10,  # 函数名所在的结束行号                            end_col_offset=9),  # 函数名的结束列号                        args=[  # 表示函数调用的参数列表                            Constant(  # 表示传递给函数的常量参数                                value='Hello',  # 该参数的值为 'Hello'                                lineno=10,  # 参数所在的行号                                col_offset=10,  # 参数在行中的起始列                                end_lineno=10,  # 参数的结束行号                                end_col_offset=15)],  # 参数的结束列号                        keywords=[],  # 表示没有传递关键字参数                        lineno=10,  # 函数调用的行号                        col_offset=4,  # 函数调用的起始列                        end_lineno=10,  # 函数调用的结束行号                        end_col_offset=16),  # 函数调用的结束列号                    lineno=10,  # 表达式所在的行号                    col_offset=4,  # 表达式的起始列                    end_lineno=10,  # 表达式的结束行号                    end_col_offset=16),  # 表达式的结束列号                Return(  # 返回值节点,表示函数的返回值                    value=Dict(  # 字典节点,表示返回的字典                        keys=[  # 字典中的键列表                            Constant(  # 键值,字典中的每个键                                value='name',  # 键的值                                lineno=11,  # 键的行号                                col_offset=8,  # 键的起始列                                end_lineno=11,  # 键的结束行号                                end_col_offset=14),  # 键的结束列号                            # ..............                                                        Constant(  # 键值6                                value='level',  # 键的值                                lineno=16,  # 键的行号                                col_offset=8,  # 键的起始列                                end_lineno=16,  # 键的结束行号                                end_col_offset=15)],  # 键的结束列号                        values=[  # 字典中的值列表                            Name(  # 值1,变量名                                id='name',  # 变量的名称                                ctx=Load(),  # 变量的上下文(读取)                                lineno=11,  # 变量所在的行号                                col_offset=16,  # 变量的起始列                                end_lineno=11,  # 变量的结束行号                                end_col_offset=20),  # 变量的结束列号                             # ............                                                        Name(  # 值6,变量名                                id='level',  # 变量名                                ctx=Load(),  # 变量上下文                                lineno=16,  # 变量的行号                                col_offset=17,  # 变量的起始列                                end_lineno=16,  # 变量的结束行号                                end_col_offset=22)],  # 变量的结束列号                        lineno=10,  # 返回的字典所在的行号                        col_offset=11,  # 字典的起始列                        end_lineno=17,  # 字典的结束行号                        end_col_offset=5),  # 字典的结束列号                    lineno=10,  # 返回节点的行号                    col_offset=4,  # 返回节点的起始列                    end_lineno=17,  # 返回节点的结束行号                    end_col_offset=5)],  # 返回节点的结束列号            decorator_list=[],  # 无装饰器            lineno=8,  # 函数定义的行号            col_offset=0,  # 函数定义的起始列            end_lineno=17,  # 函数定义的结束行号            end_col_offset=5)],  # 函数定义的结束列号    type_ignores=[]  # 忽略类型 ) ​

自定义匹配模板文件

模板文件位置

Bandit自带的模板文件都在bandit\bandit\plugins目录下,里面配有各种漏洞的匹配方式(多数为污点分析)

自动扫描逻辑
  • Bandit 自动扫描的核心机制依赖于 test 类,该类位于 bandit/bandit/core/test_properties.py 文件中。通过使用装饰器(类似于面向切面编程 - AOP)
  • Bandit 可以将这些装饰器放置在函数上方,使函数动态识别是否需要参与安全分析。这种方式类似于注解,能够灵活标记和管理需要检测的函数或规则。

less

代码解读

复制代码

@test.checks("Str") @test.test_id("B105") def hardcoded_password_string(context):    """**B105: 检测硬编码密码字符串**   # 具体代码逻辑

0. @test.checks("Str")

  • 作用: 该装饰器用于指定测试规则要检查的 AST 节点类型。Bandit 通过解析 Python 源代码生成 AST,@test.checks 装饰器告诉 Bandit 哪种类型的节点应该被此测试函数处理。
  • 参数"Str" 表示该测试函数专门检查 Str 类型的 AST 节点(即 Python 源代码中的字符串字面量)。其他常见的 AST 节点类型包括 Call(函数调用)、Assign(赋值语句)等。
  1. @test.test_id("B105")
  • 作用: 该装饰器为测试函数分配一个唯一的标识符(ID),方便标识和引用测试规则。这个 ID 用于在扫描报告中标识问题的来源,也用于启用、禁用特定测试规则。
  • 参数"B105" 是分配给该测试函数的唯一标识符。通常,Bandit 的测试 ID 以字母 B 开头,后面跟随三位数字。
自定义模板思路
  1. 将自定义的模板 py 文件放入 plugins 目录下或 blacklists 目录下

  2. plugins:在函数上加上 @test 相关注解,进行匹配逻辑编写

  3. 漏洞分类

    身份证描述
    B1xx 系列MISC 测试
    B2xx 系列应用程序/框架配置错误
    B3xx 系列黑名单(calls)
    B4xx 系列黑名单 (imports)
    B5xx 系列密码学
    B6xx 系列注入
    B7xx 系列XSS (XSS)
  4. 将新增的 py 文件具体信息加入 bandit\setup.cfg 文件对应位置下

您可能感兴趣的与本文相关的镜像

Python3.11

Python3.11

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值