:从 `redirect_to` 源码到 `:only_path` 防护实战)
SAST应用安全开发工具【免费下载链接】brakemanA static analysis security vulnerability scanner for Ruby on Rails applications项目地址https://gitcode.com/gh_mirrors/br/brakeman点击查看免费下载本文以 Brakeman 官方警告类型文档 docs/warning_types/redirect/index.markdown 为核心结合仓库中 CheckRedirect 检查器的源码实现与 test/apps 下的真实测试应用系统讲解 Rails 中「未验证重定向Unvalidated Redirect / Open Redirect」的风险成因、Brakeman 的告警触发逻辑以及可落地的多种修复方案。读完本文你将掌握如何阅读该类告警、判断其可信度并能在自己的 Rails 项目中正确使用:only_path、显式:host、URI.parse(...).path等加固手段。一、威胁背景OWASP Top Ten 中的 A10未验证的重定向与转发Unvalidated Redirects and Forwards位列 OWASP Top Ten 2010 第 10 位。它的危害集中在两点钓鱼与伪装依赖用户输入值构造的重定向可以被攻击者用来伪造网站或把恶意链接隐藏在一个看似无害的 URL 中。受害者以为在访问可信站点实际被带往攻击者控制的页面。越权访问如果重定向目标未经校验攻击者还可能借机把用户导向站点内受限制的区域例如管理后台、内网资源造成未授权访问。从 Rails 的视角看redirect_to是控制器中最常见的跳转手段。当它的目标参数来自params、request等用户可控来源且没有限制目标主机host时就构成了典型开放重定向漏洞。二、Brakeman 的告警规则什么时候报 Redirect 警告Brakeman 在每当redirect_to看起来与用户提供的值一起使用、且该值可能允许攻击者改变:host选项时会发出 Redirect 类型警告。这一点在 CheckRedirect 的类注释与描述中写得非常明确#Reports any calls to redirect_to which include parameters in the arguments. # #For example: # # redirect_to params.merge(:action :elsewhere) class Brakeman::CheckRedirect Brakeman::BaseCheck description Looks for calls to redirect_to with user input as arguments也就是说该检查器的核心关注点只有一个redirect_to的第一个参数中是否混入了用户输入且用户能否借此控制重定向目标主机。检查覆盖的方法也随 Rails 版本演进不断扩展源码 check_redirect.rb 中为methods [:redirect_to, :redirect_back, :redirect_back_or_to]其中redirect_back与redirect_back_or_to是 Rails 5 引入的回退跳转APIBrakeman 会额外校验其fallback_location:选项见下文。2.1 典型触发示例原文档给出的示例是redirect_to params.merge(:action :home)这段代码会产生类似如下告警Possible unprotected redirect near line 46: redirect_to(params)原因在于params可能被攻击者注入:host evilsite.com从而把用户从你的站点重定向到恶意站点。值得注意的是Brakeman 在告警信息中会把params.merge(:action :home)简写为redirect_to(params)因为整个参数哈希都源自params。在仓库测试应用中同样存在完全一致的真实样例例如 test/apps/rails3/app/controllers/home_controller.rb#L43-L46def test_redirect params[:action] :index redirect_to params end以及 test/apps/rails2/app/controllers/home_controller.rb#L45 中的redirect_to params。这些样例正是测试套件如 test/tests/rails2.rb用来断言告警消息/^Possible unprotected redirect/的靶子。2.2 告警输出中的关键元数据从 process_result 的源码可以看到一条完整的 Redirect 告警包含warning_type: Redirectwarning_code: :open_redirect对应数字码 18见 lib/brakeman/warning_codes.rb#L21message: Possible unprotected redirectcwe_id: [601]即 CWE-601「URL Redirection to Untrusted Site」user_input指向被判定为可控的具体表达式如params[:redirect_url]置信度confidence的判定逻辑是当用户输入直接出现在重定向参数中res.type :immediate且没有显式allow_other_host: true时置信度为高high否则为弱weak例如redirect_to params[:x], allow_other_host: true只会产生低置信度提示。这一细节在 test/apps/rails7/app/controllers/users_controller.rb#L20-L26 中有对应的正反用例def redirect_with_allow_host redirect_to params[:x], allow_other_host: true # low confidence warning end def redirect_with_explicit_not_allow redirect_to params[:x], allow_other_host: false # no warning end三、告警的豁免逻辑哪些写法不会触发Brakeman 的检查器并非见用户输入就报它内置了多道豁免判断见 process_result 的守卫条件理解这些条件能帮你更准确地区分真实漏洞与误报only_path?参数哈希中含有:only_path true重定向被限制在当前主机内详见下文第四节。explicit_host?参数中显式指定了:host且该值不是直接的用户输入见 explicit_host?。例如 test/apps/rails4/app/controllers/friendly_controller.rb#L57-L63 中的三个用例redirect_to params.merge(:host example.com) # Should not warn redirect_to params.merge(:host User.canonical_url) # Should not warn redirect_to params.merge(:host params[:host]) # Should warn注意第三条host值本身来自params攻击者仍可控制因此照常告警。slice_call?参数是对params.slice(...)的调用Brakeman 假定开发者已明确挑选了安全键见 slice_call?。但测试 test/tests/rails5.rb#L212-L223 显示redirect_to params.slice(:back_to)仍会被视为告警code: params.slice(:back_to)因为:back_to的值本身仍可控。safe_permit?params.permit(...)白名单中不包含危险键时视为安全。危险键定义在 DANGEROUS_KEYSDANGEROUS_KEYS [:host, :subdomain, :domain, :port]对照 test/apps/rails5/app/controllers/mixed_controller.rb#L25-L27redirect_to params.permit(:domain) # should warn白名单包含 :domain redirect_to params.permit(:page, :sort) # should not warnprotected_by_raise?/disallow_other_host?Rails 7 起若配置了config.action_controller.raise_on_open_redirects true框架本身会拦截跨主机重定向显式传入allow_other_host: false也同理不再告警。相关配置读取逻辑见 raise_on_redirects?行为验证见 test/tests/rails7_redirect.rb。redirect_back的特殊处理redirect_back本身不含目标位置只有显式传入fallback_location:时才需要检查check_redirect.rb#L41-L50redirect_back fallback_location: params[:x] # 告警 redirect_back_or_to params[:x], allow_other_host: false # 不告警对应样例见 test/apps/rails7/app/controllers/users_controller.rb#L28-L38。模型实例与命名路由重定向目标是模型实例如redirect_to user、redirect_to User.find(1)或以_path/_url结尾的路由辅助方法时不会告警——Rails 会用参数构建同主机路径见 include_user_input?。四、修复方案一哈希参数加:only_path true原文档明确指出如果redirect_to的第一个参数是哈希那么添加:only_path true会把重定向限制在当前主机。这是因为only_path会让 Rails 只使用请求路径而忽略协议与主机redirect_to params.merge(:only_path true)即使params中混入了:host evilsite.com由于只取路径部分攻击者无法再把用户导向外部站点。Brakeman 的 only_path? 检查器会解析该选项并放行此类调用测试应用 test/apps/rails3/app/controllers/home_controller.rb#L95-L98 中的正确用法为def test_only_path_correct params.merge! :only_path true redirect_to params end需要注意的反例only_path必须作用于整个参数哈希才有效。原文档配套的测试用例 test/apps/rails3/app/controllers/home_controller.rb#L76-L78 专门演示了错误姿势def test_only_path_wrong redirect_to params[:user], :only_path true #This should still warn end这里only_path只是redirect_to的第二参数而不是哈希参数的一部分因此仍然会触发告警。Rails 4 的测试样例也验证了通过params.to_unsafe_hash/params.to_unsafe_h取出的哈希在合并:only_path true后是安全的见 test/apps/rails4/app/controllers/application_controller.rb#L33-L43。此外url_for默认就是only_path true的所以redirect_to url_for(params)通常安全但若有人显式把only_path设为false则会重新引入风险见 check_url_for 与 test/apps/rails3/app/controllers/home_controller.rb#L80-L84 的test_url_for_only_path。五、修复方案二显式指定:host另一种做法是在哈希参数中显式指定主机把跳转目标锁定到固定域名redirect_to params.merge(:host myhost.com)这样即使params里带了:host键也会被代码中硬编码的值覆盖。Brakeman 的 explicit_host? 会识别这种情况并放行。但务必注意前文第三点host的值本身不能来自用户输入。即下面的写法依旧会被告警redirect_to params.merge(:host params[:host]) # Should warn如果你想彻底收紧还可以在强参数层面对键做白名单只允许非危险键:page、:sort等并拒绝:host、:subdomain、:domain、:port这四个危险键DANGEROUS_KEYS。六、修复方案三字符串参数解析出路径如果redirect_to的第一个参数是字符串则可以对字符串做解析、只取出其中的路径部分redirect_to URI.parse(some_url).path原文档特别提醒了此方案的一个陷阱如果该 URL 不包含协议如http://你很可能会得到意想不到的结果。原因在于redirect_to遇到相对路径/无协议地址时会自行在前面拼接当前主机名和协议。换句话说带协议的完整 URLURI.parse(http://evilsite.com/path).path得到/path安全无协议的字符串如//evilsite.com/path或纯相对路径解析出的.path可能与预期不符且 Rails 的前置拼接行为可能掩盖实际跳转目标需要格外验证。更稳妥的做法是先校验解析结果确实是路径而非完整 URL再交给redirect_to。如果项目运行在 Rails 7还可以直接在 config/application.rb 中开启全局防线config.action_controller.raise_on_open_redirects true开启后Rails 会在运行时直接抛出ActionController::Redirecting::UnsafeRedirectError阻止跨主机重定向发生Brakeman 检测到该配置后也不再重复告警验证见 test/tests/rails7_redirect.rb#L12-L54其中 3 个告警在该配置开启后被断言为修复。七、总结从告警到修复的决策路径当 Brakeman 报告Possible unprotected redirect时可按以下顺序排查看参数来源user_input是params、request还是模型查询结果只有用户可控来源才构成风险。看参数形态是哈希用:only_path true或显式:host加固、字符串用URI.parse(...).path提取路径并验证协议还是命名路由/模型实例天然安全。看豁免条件是否已包含only_path、显式安全host、安全的permit白名单、allow_other_host: false或已开启raise_on_open_redirects。看置信度直接输入为高置信度间接传播为弱置信度——弱置信度告警可结合业务上下文判断是否误报。最后给出一个完整的修复对照示例覆盖原文档的全部三种方案# 脆弱写法告警params 可携带 :host 键 redirect_to params.merge(:action :home) # 修复 A哈希参数 —— 只取路径限制在当前主机 redirect_to params.merge(:only_path true) # 修复 B哈希参数 —— 显式固定主机host 值不可来自用户输入 redirect_to params.merge(:host myhost.com) # 修复 C字符串参数 —— 解析出路径部分注意无协议 URL 的边界行为 redirect_to URI.parse(some_url).path通过 CheckRedirect 的源码、test/apps 系列测试应用与 test/tests 中的断言可以确认上述每一种写法在 Brakeman 中的实际判定结果。建议在项目集成 Brakeman 时把本文涉及的redirect_to、redirect_back、redirect_back_or_to调用点纳入安全编码规范从源头杜绝开放重定向。赞分享SAST应用安全开发工具【免费下载链接】brakemanA static analysis security vulnerability scanner for Ruby on Rails applications项目地址https://gitcode.com/gh_mirrors/br/brakeman点击查看免费下载相关推荐Spring 源码精读SimpleAliasRegistry 别名的注册、解析与循环检测机制Spring 源码精读SimpleAliasRegistry 别名的注册、解析与循环检测机制 导读 别名Alias是 Spring IoC 容器中同一SAST应用安全开发工具Vue Router 2 重定向与别名完全指南redirect 与 alias 的配置、源码原理与实战验证Vue Router 2 重定向与别名完全指南redirect 与 alias 的配置、源码原理与实战验证 导读 本指南以 vue routerVue 2前端路由Vue Router 2 指南Redirect 重定向与 Alias 别名 —— 从配置语法到源码实现Vue Router 2 指南Redirect 重定向与 Alias 别名 —— 从配置语法到源码实现 本文围绕 Vue RouterVue 2 官方路由前端路由上一篇使用 Certora 对 OpenZeppelin Contracts 进行形式化验证WTF-Solidity 仓库实战指南下一篇Kedro 与 Jupyter 笔记本在 Notebook 中加载 Catalog、Context、Pipelines 与 Session创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考