- SAST
- 应用安全
【免费下载链接】bandit
Bandit is a tool designed to find common security issues in Python code.
导读
本文围绕 Bandit 安全扫描器的 B704 测试(markupsafe_markup_xss)展开,介绍它如何识别markupsafe.Markup构造时传入动态内容(f-string、变量、插值字符串等)所导致的跨站脚本(XSS)风险,并详细讲解markupsafe_xss配置段中的extend_markup_names与allowed_calls两个扩展选项,以及对应的告警输出格式、绕过场景与功能测试验证。读完本文,你将掌握 B704 的检测原理、告警含义、如何在 bandit.yaml 中为自有框架定制检测范围,以及如何结合源码准确判断哪些写法会被误报或漏报。
B704 检测的定位与引入版本
B704 是 Bandit 内置插件之一,源码位于 bandit/plugins/markupsafe_markup_xss.py,自 Bandit1.8.3 版本起引入(versionadded:: 1.8.3)。它针对markupsafe库的Markup类使用场景,检测"将动态、不可信内容直接传入Markup构造"这一潜在 XSS 风险,因此相关文档被收录在 doc/source/plugins/b704_markupsafe_markup_xss.rst。
核心原理:为什么 Markup() 是 XSS 风险点
markupsafe.Markup是 Python Web 框架(如 Flask、Jinja2)中用于标记"字符串已被转义、可直接安全输出到模板"的包装类型。其关键语义是:Markup构造本身不做任何转义,它只是把传入的字符串标记为安全。因此,若把动态内容——例如用户提交的数据、f-string 插值、"..." .format()或%格式化后的字符串——直接交给Markup,等于告诉模板引擎"这段内容无需转义",从而可能引入 XSS 漏洞。
正确的做法有两种:
- 将最终渲染结果交给
Markup的.format()方法,让Markup在插值时自动执行转义(例如Markup("safe {}").format(content)); - 或使用
markupsafe.escape对动态内容先做转义再包装。
检测逻辑与源码级判定规则
插件的检测函数通过@test.checks("Call")声明只检查 AST 中的函数调用节点,并使用@test.test_id("B704")注册测试 ID,同时以@test.takes_config("markupsafe_xss")声明读取名为markupsafe_xss的配置段(见 bandit/core/test_properties.py)。其判定流程如下:
- 识别目标调用:通过
context.call_function_name_qual取得调用限定名,命中markupsafe.Markup或flask.Markup(后者被默认视为Markup的别名)才继续;否则检查是否命中extend_markup_names配置中追加的别名。 - 参数安全性初筛:若调用无参数,或首个参数是常量(
ast.Constant,如字面量字符串、字节串),则判定安全,直接返回None。 - 白名单调用放行:若配置了
allowed_calls,且首个参数是一个函数调用(ast.Call),其调用名(经get_call_name结合 import 别名解析)命中白名单,则放行。 - 其余情况判定为问题:输出一条严重级别为Medium、置信度为High、CWE 编号为CWE-79(XSS)的告警。
qualname = context.call_function_name_qual if qualname not in ("markupsafe.Markup", "flask.Markup"): if qualname not in config.get("extend_markup_names", []): return None # not a Markup call args = context.node.args if not args or isinstance(args[0], ast.Constant): return None # no arguments and constant are fine allowed_calls = config.get("allowed_calls", []) if (allowed_calls and isinstance(args[0], ast.Call) and get_call_name(args[0], context.import_aliases) in allowed_calls): return None # argument contains a whitelisted call return bandit.Issue( severity=bandit.MEDIUM, confidence=bandit.HIGH, cwe=issue.Cwe.XSS, text=f"Potential XSS with ``{qualname}`` detected. " f"Do not use ``{context.call_function_name}`` on untrusted data.", )关键点说明:
get_call_name位于 bandit/core/utils.py,会结合context.import_aliases把import/from-import的别名解析回真实限定名,因此from bleach import clean后的clean(...)也能与配置中的bleach.clean正确匹配。- 由于插件注册在
setup.cfg的 entry_points 中(markupsafe_markup_xss = bandit.plugins.markupsafe_markup_xss:markupsafe_markup_xss,见 setup.cfg),运行 Bandit 时该测试默认启用。
配置markupsafe_xss段:扩展别名与白名单
B704 允许通过共享配置段markupsafe_xss自定义两类行为,默认值由gen_config提供:extend_markup_names与allowed_calls均默认为空列表。
extend_markup_names:登记更多"Markup 类"别名
默认仅识别markupsafe.Markup与flask.Markup。若你的代码库中存在Markup的其他子类或语义相似的自定义类(例如webhelpers.html.literal),可通过该选项让其接受同等检测:
markupsafe_xss: # Recognize additional aliases extend_markup_names: - webhelpers.html.literal - my_package.Markup对应示例文件 examples/markupsafe_markup_xss_extend_markup_names.py 中,Markup(f"unsafe {content}")与literal(f"unsafe {content}")都会被判定为 B704(MEDIUM 2 条、HIGH 置信度 2 条)。
allowed_calls:为安全的净化函数开白名单
有些 HTML 净化函数(如bleach.clean)本身不返回markupsafe.Markup,需要被Markup包裹后才能安全输出。这类"先净化、再包装"的写法可通过allowed_calls放行,避免误报:
markupsafe_xss: # Allow the output of these functions to pass into Markup allowed_calls: - bleach.clean - my_package.sanitize注意:官方文档明确警告——该白名单若使用不当会引入漏报(false negatives),因此只应登记真正具备净化/消毒语义的函数。
告警输出格式与示例
对不安全写法,Bandit 输出类似如下告警(Severity: Medium,Confidence: High,CWE-79):
>> Issue: [B704:markupsafe_markup_xss] Potential XSS with ``markupsafe.Markup`` detected. Do not use ``Markup`` on untrusted data. Severity: Medium Confidence: High CWE: CWE-79 (https://cwe.mitre.org/data/definitions/79.html) Location: ./examples/markupsafe_markup_xss.py:5:0 4 content = "<script>alert('Hello, world!')</script>" 5 Markup(f"unsafe {content}") 6 flask.Markup("unsafe {}".format(content))触发与放行场景全览(含边界情况)
官方示例 examples/markupsafe_markup_xss.py 完整覆盖了各类写法:
| 代码写法 | 结果 |
|---|---|
Markup(f"unsafe {content}") | B704 触发 |
flask.Markup("unsafe {}".format(content)) | B704 触发 |
Markup("safe {}").format(content) | 安全(Markup.format负责转义) |
flask.Markup(b"safe {}", encoding='utf-8').format(content) | 安全 |
escape(content) | 安全(显式转义) |
Markup(content) | B704 触发 |
flask.Markup("unsafe %s" % content) | B704 触发 |
Markup(object="safe") | 安全(无位置参数) |
Markup(object="unsafe {}".format(content)) | 当前版本不检测(已知局限) |
需要特别说明两个边界:
- 间接赋值不支持:
cleaned = clean(content); Markup(cleaned)这类先赋值再传入的写法,参数并非直接的ast.Call,因此白名单不会放行(见 examples/markupsafe_markup_xss_allowed_calls.py 中的注释"indirect assignments are currently not supported")。 - 关键字参数不作为判定依据:传入
Markup的关键字参数(object=...)当前不在检测范围内,示例文件最后一行明确标注 "Not currently detected"。
功能测试验证:与源码一一对应
Bandit 的功能测试位于 tests/functional/test_functional.py,通过check_example运行 Bandit 扫描示例文件并与期望的严重度/置信度计数比对:
test_markupsafe_markup_xss:对默认示例期望 MEDIUM 4 条、HIGH 置信度 4 条;test_markupsafe_markup_xss_extend_markup_names:注入b_conf.config["markupsafe_xss"] = {"extend_markup_names": ["webhelpers.html.literal"]}后期望 MEDIUM 2 条、HIGH 2 条;test_markupsafe_markup_xss_allowed_calls:注入{"allowed_calls": ["bleach.clean"]}后,Markup(clean(content))被放行,期望 MEDIUM 1 条、HIGH 1 条。
三个测试分别印证了默认规则、别名扩展与白名单放行的实际行为,可用于在修改配置后自行回归验证。
在真实项目中的落地建议
- 默认开启即可:只要目标代码使用了
markupsafe/flask,B704 就会生效;无需额外启用。 - 自定义 Markup 类时补配置:若团队封装了自己的 HTML 安全包装类,请在
markupsafe_xss.extend_markup_names中登记,保持检测覆盖。 - 白名单务必谨慎:只把"真正返回净化后安全内容"的函数加入
allowed_calls,并配合功能测试核对扫描结果。 - 修复方式:优先改用
Markup("模板 {}").format(动态内容)或escape(动态内容),从根上消除 XSS 隐患。 - 运行验证:修改 bandit.yaml 后,可通过
bandit -c bandit.yaml -r <目标目录>复扫,并参考 doc/source/config.rst 中关于插件配置段(section 名与插件方法名一致)的说明。
延伸阅读
- MarkupSafe 官方文档(含
markupsafe.Markup转义语义) - CWE-79(Improper Neutralization of Input During Web Page Generation,即 XSS)
- 本文相关源码与示例:插件实现、默认示例、白名单示例、别名扩展示例、功能测试
- SAST
- 应用安全
【免费下载链接】bandit
Bandit is a tool designed to find common security issues in Python code.
相关推荐
Bandit B202(tarfile_unsafe_members)检测指南:Python 解压路径遍历与不安全 tarfile.extractall 防护
Bandit B202(tarfile_unsafe_members)检测指南:Python 解压路径遍历与不安全 tarfile.extractall 防护
SAST应用安全提升 WordPress 主题代码质量:PHPCS、ESLint 与 wp-scripts 完整工作流指南
提升 WordPress 主题代码质量:PHPCS、ESLint 与 wp scripts 完整工作流指南 WordPress 主题 _s(Underscore
后端前端Name of Person常见问题解答:从安装到使用的10个实用技巧
Name of Person常见问题解答:从安装到使用的10个实用技巧 Name of Person是一款专为Ruby应用设计的轻量级姓名处理工具,它提供了简单
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考