- SAST
- 应用安全
- 开发工具
【免费下载链接】brakeman
A static analysis security vulnerability scanner for Ruby on Rails applications
导读
content_tag是 Rails 视图层最常用的 HTML 标签生成辅助方法之一,但由于它会把输出标记为 "HTML safe",历史上一直是 XSS(跨站脚本)的高发点:Rails 2 中内容不转义、属性名永不转义,甚至 Rails 5 时代还曝出属性值双引号不转义漏洞(CVE-2016-6316)。本文以 Brakeman 仓库中 content_tag 警告类型文档 为主线,结合 check_content_tag.rb 源码与多版本测试用例,讲清content_tag各位置的转义规则、Brakeman 的检测逻辑、对应警告形态以及升级修复路径,帮助你在编写模板和阅读扫描报告时准确识别并规避此类风险。
content_tag:一个"看似安全"的 HTML 生成器
content_tag是ActionView::Helpers::TagHelper提供的辅助方法,用于生成带内容的 HTML 标签:
>> content_tag :p, "Hi!" => "<p>Hi!</p>"它的危险之处在于两点叠加:输出内容是否被转义因 Rails 版本而异,且其返回值被标记为 "HTML safe",导致 Rails 3 的自动转义和rails_xss插件都不会再对它做二次转义。这意味着如果开发者误以为"框架会帮我转义"而直接传入用户可控值,XSS 就会穿透防线。
各版本转义行为差异(核心风险模型)
原文档给出的转义规则需要逐条落实,它们直接决定了 Brakeman 的检测分支:
| 位置 | Rails 2 | Rails 3+ |
|---|---|---|
| 标签内容(content) | 不转义 | 转义 |
| 属性值(attribute value) | 转义 | 转义 |
| 标签名(tag name) | 永不转义 | 永不转义 |
| 属性名(attribute key) | 永不转义 | 永不转义(至 Rails 6.1.6 修复为止) |
Rails 2 下的直接注入:
>> content_tag :p, "<script>alert(1)</script>" => "<p><script>alert(1)</script></p>"此时内容原样输出,与raw无异。Rails 3 起内容会被转义,但"转义"只覆盖内容和属性值,标签名、属性名始终不转义——把params[:tag]当作标签名、把cookies[:key]当作属性名,依然能构造出<script>之类的标签名或注入属性。
HTML safe 标记带来的额外风险:content_tag的返回值被标记为 safe,所以即使套在 Rails 3 自动转义环境中,输出也不会被再转义。文档明确提醒:当用户输入作为参数传入content_tag时必须格外小心。
escape 参数的真实语义:content_tag有第四个参数escape,但它只作用于属性值,且默认值为true。它不能保护标签内容、标签名和属性名——这是初学者最容易误解的一点,也是下文 Brakeman 检测逻辑区分"属性值是否检查"的依据。
Brakeman 如何检测 content_tag XSS
Brakeman 专门实现了 CheckContentTag,继承自通用 XSS 检查 CheckCrossSiteScripting,注册名为ContentTag,负责扫描模板与控制器的content_tag调用。
扫描入口与参数拆解
run_check通过tracker.find_call :target => false, :method => :content_tag收集所有无接收者的content_tag调用,然后逐条解析四个参数(对应 process_result):
tag_name(args[1]):标签名,任何版本都检查——文档中"标签名永不转义"的直接落地;content(args[2]):标签内容,仅当Rails 2 或内容经由raw()包裹时检查(源码unless @matched or (tracker.options[:rails3] and not raw? content));attributes(args[3]):属性哈希,其中**属性名(键)**在 Rails 6.1.6 之前一律检查,属性值仅在escape_attr为false或命中 CVE-2016-6316 时检查;escape_attr(args[4]):即escape参数,false会触发属性值检查。
判定与去噪逻辑
检测基于继承自父类的has_immediate_user_input?(参数、cookie、session 等直接用户输入)与has_immediate_model?(模型属性)两个数据源判定。同时用@ignore_methods白名单过滤已知安全的方法(如h、escape_once、url_encode、text_field、label、mail_to等),并合并用户在safe_methods配置中声明的白名单。
值得注意的是raw?处理:check_argument中若参数是raw(...)调用,会直接剥掉raw检查其内部表达式——因为raw强制关闭转义,即使 Rails 3 也拦不住。
警告形态与置信度
命中用户输入时输出warning_code :xss_content_tag(值为 53,见 warning_codes.rb),cwe_id为 79(XSS):
- 直接用户输入:消息形如
Unescaped parameter value in content_tag,confidence: :high; - 模型属性:消息形如
Unescaped model attribute in content_tag,若属性可确认来自模型则:high,否则:medium; - 间接匹配:
Unescaped ... in content_tag,:medium。
检测场景对照:从测试样例看"该不该报"
仓库测试应用中的模板是理解检测边界的绝佳教材,逐行标注了预期行为。
Rails 2 场景(test_content_tag.html.erb)
Rails 2 内容不转义,因此内容位置出现模型属性直接报高危:
content_tag :span, @user.name→ Should warn(Rails 2 内容不转义);content_tag :div, "Blah!", { :id => @user.name }, false→ 属性值显式关闭转义,报;content_tag :div, "Blah!", { cookies[:weird] => "bad idea" }→属性名是 cookie 值,报;- 用
h()、u()(URL 编码)包裹的内容 → Should not warn(白名单生效)。
Rails 3 场景(test_content_tag.html.erb)
Rails 3 内容自动转义,但以下几类仍会报警:
content_tag :span, raw(params[:blah])→raw强制不转义,报;content_tag :div, "Blah!", { cookies[:weird] => "bad idea" }→ 属性名用户可控,报;content_tag :div, "Blah!", { @user.something => "bad idea"}, true→ escape=true 也挡不住属性名,报;content_tag :div, "Blah!", { :id => @user.name }, false→ 属性值显式不转义,报;content_tag params[:whyyy], "Don't do this"与content_tag @user.preferred_markup, "Seriously"→标签名用户可控,报;content_tag :div, "Blah!", { :class => params[:class] }, true→ 属性值默认转义,不报。
对应断言分别记录在 test/tests/rails3.rb 与 test/tests/rails2.rb 中。
Rails 6.1.6+ 的行为变化
源码中version_between? '0.0.0', '6.1.5'的条件表明:Rails 6.1.6 起属性名也纳入转义范围,因此 test/tests/rails7.rb 中content_tag :b, cool_content, params[:stuff] => params[:things]这类写法断言为不报警(assert_no_warning)。这是"属性名永不转义"规则随版本演进的唯一例外。
CVE-2016-6316:属性值双引号注入
content_tag历史上最著名的安全事件是 CVE-2016-6316 关联的缺陷:受影响版本中content_tag不转义属性值中的双引号,可导致属性逃逸注入。Brakeman 的处理分两步:
受影响的版本区间
cve_2016_6316?与check_cve_2016_6316(check_content_tag.rb)判定:
- Rails
3.0.0–3.2.22.3 - Rails
4.0.0–4.2.7.0 - Rails
5.0.0–5.0.0.0
检测与修复版本建议
命中时输出独立警告:CVE_2016_6316(warning_code 102,见 warning_codes.rb),并给出对应修复版本:
| 受影响区间 | 修复版本 |
|---|---|
| 3.0.0 – 3.2.22.3 | 3.2.22.4 |
| 4.0.0 – 4.2.7.0 | 4.2.7.1 |
| 5.0.0 – 5.0.0.0 | 5.0.0.1 |
同时,只要命中 CVE 版本区间,即使属性哈希未显式关闭转义,Brakeman 也会检查属性值(源码中cve_2016_6316?与false? escape_attr是"或"关系)——因为默认转义在该版本下实际无效。修复版本升级后,Brakeman 会停止报警,这一行为由 test/tests/cves.rb 通过替换Gemfile.lock后重扫验证:升级到3.2.22.4、5.0.0.1后相关警告数量归零。
实战修复建议
结合文档与源码,规避content_tagXSS 的要点可归纳为:
- 永远不要让用户输入充当标签名或属性名(Rails 6.1.6 之前任何版本都不转义);
- 内容位置传入用户数据时,显式使用
h()转义,而不是依赖版本差异;绝不把raw()或.html_safe的结果直接传给content_tag(rails5 测试样例 正是params[:stuff].html_safe与sanitize(params[:stuff])两种高危写法); - 属性值位置若依赖第四个参数
escape,注意它只对属性值生效且默认才为true;显式传false时必须自行转义; - 尽快升级到 Rails 6.1.6+(修复属性名转义)或至少到各分支的 CVE-2016-6316 修复版本(3.2.22.4 / 4.2.7.1 / 5.0.0.1);
- 使用 Brakeman 时关注
warning_code53(xss_content_tag)与 102(CVE_2016_6316),并可在safe_methods中登记你确认安全的包装方法以减少误报。
延伸阅读
- Cross-Site Scripting 通用警告类型文档
- XSS 检查通用实现
- ContentTag 检查实现
- 警告码定义
- Rails 2 测试视图 / Rails 3 测试视图 / Rails 5 测试视图
- 对应测试断言 与 CVE 回归测试
- SAST
- 应用安全
- 开发工具
【免费下载链接】brakeman
A static analysis security vulnerability scanner for Ruby on Rails applications
相关推荐
Brakeman 跨站脚本(XSS)检测详解:原理、触发场景与配置选项
Brakeman 跨站脚本(XSS)检测详解:原理、触发场景与配置选项 跨站脚本(Cross site scripting,XSS)是 Web 应用最顽固的高频
SAST应用安全开发工具Brakeman Attribute Restriction 警告详解:attr_accessible 缺失与 attr_protected 风险的静态检测
Brakeman Attribute Restriction 警告详解:attr_accessible 缺失与 attr_protected 风险的静态检测 导
SAST应用安全开发工具Brakeman 命令注入(Command Injection)检测详解:原理、检测范围与修复方案
Brakeman 命令注入(Command Injection)检测详解:原理、检测范围与修复方案 命令注入(Command Injection)长期位列 OW
SAST应用安全开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考