译者:Serene
原文:Six Security Vulnerabilities from a Year of HackerOne
一年前,我们推出了在 HackerOne 上的赏金计划,以提高 Flexport 的安全性。 HackerOne 让我们为业余爱好者和专业渗透测试人员提供赏金来鼓励他们发现漏洞。 于是,我们收到了近 200 份报告,包括将服务器 token 从 nginx header 中删除 到 XSS 漏洞。 以下是 200 个报告中最有趣的 6 个漏洞。
当发起赏金计划时,我们没想到会收到有关 XSS 的有效报告,毕竟 React 中内置了防范这种漏洞的保护措施,不幸的是,我们收到的第一份不同寻常的报告就是关于存储型 XSS 漏洞的。
当时我们在使用 Bootbox 来显示错误消息并创建确认对话框。 Bootbox 独立于 React 管理 DOM 元素,因此不受 React 的 XSS 保护措施的影响。 所以,当将用户输入直接展示在确认对话框中时,就触发了攻击。
短期的修复方案是在用户输入传递给 Bootbox 展示之前,将所有可能和 XSS 相关的标签删除(JSXSS 提供了一个节点模块让这部分变得很简单)。正在筹备长期的解决方案是,从 Bootbox 转移到一个基于 React 的确认模块。
React 阻止了 XSS 不代表所有代码都是安全的。对所有在 React 之外工作的库都不能信任,并且要尽可能地避免使用它们。
在修复了 Bootbox 并检查了我们其它类似的库之后,我们收到了第二个 XSS 漏洞报告——这次存在于我们的 Markdown 渲染中。
我们的文本框中以 <div dangerouslySetInnerHtml={{__html: marked(text)}} />
方式支持 Markdown,回想起来,这是一个坏主意。
将所有传递到 dangerouslySetInnerHtml
的文本都使用 XSS 过滤器,并创建一个 Lint 规则以在将来执行此操作。
使用任何带有 dangerous
的功能时,都要严肃对待……
在所有从 HackerOne 中收到的报告中,最令人惊讶的是标准 HTML 标签的正常使用。
当你用新标签页打开一个链接(<a target="_blank">
),新打开的标签页可以利用 window.opener 属性访问初始标签并改变它的 location 对象。攻击者可以将原始页面设置为登录页面或其他任何内容。只能将 rel="noopener noreferrer"
添加到 a
标签中,来减轻这一类问题。
通过在使用 target="_blank"
时增加 rel="nofollow me noopener noreferrer"
,我们修复了该问题,这样新窗口就不能改变原始窗口的内容。另外,我们 向 ESLint 提交了一个 Lint 规则,防止以后大家犯同样的错误。
这个漏洞的关键点是,安全是很难的。我们很容易信任像 HTML 这样的准则,但保持警惕和怀疑同样重要。
修复完上述漏洞,我们没有收到更多与前端相关的漏洞,然而我们在 HackerOne 的赏金计划仍然在博客中延续。我们公司的博客在 Wordpress 上运行,也因此收到了各种各样基于此的漏洞报告。
每个博客漏洞都归结于同样的问题:过时的库很容易受攻击。 例如,JetPack 是一种广泛使用(300万次安装)和推荐的插件,它承诺“保护所有 WordPress 网站的安全,增加流量,吸引读者”,但在过去几年中已经有许多 XSS 和其他漏洞。
和所有软件一样,最不容易受攻击的是不存在的代码,其次是最新的代码。 我们删除了绝大多数的 Wordpress 插件(其中大部分都不知道何时安装过),更新了其余部分插件,并订阅 https://wpvulndb.com/ 以得到最新的报告。
转到我们的 Ruby on Rails 后端,我们收到了两份值得注意的报告,都涉及了我们的双重认证。一开始,我们收到一份报告,展示了如何通过暴力攻击来获得已泄露用户的访问权限。
我们使用 Authy 作为我们的 2FA 合作伙伴,他们的 rails gem 不包括任何内置的速率限制。
修复方法很简单:我们添加了速率限制,即在多次错误尝试后锁定账户。
最后,我们收到了一份报告,展示了对我们 2FA 的完全绕过,这使得第二重认证完全没有起作用。攻击者所要做的就是忽略 2FA 页面并导航到另一个链接。
在本文中所有的 bug 中,这一个是最难找到的。Authy rails gem hook 住 Devise (一个受欢迎的 rails 认证/用户管理库),并在登录后使用以下代码要求 2FA:
def check_request_and_redirect_to_verify_token ... id = warden.session(resource_name)[:id] warden.logout warden.reset_session! session["#{resource_name}_id"] = id ... redirect_to verify_authy_path_for(resource_name) end
理论上说,这个代码在用户成功登录后会将其登出,并重新定向到第二重身份验证页面。 然而实际上,Devise 调用 authenticate?
检查用户是否进行了身份验证(在此处的代码之后运行):
def authenticate?(*args) result = !!authenticate(*args) # Try to log the user in yield if result && block_given? result end
这会让用户重新登录。
将 warden.logout
行更改为 sign_out
可以解决这个问题,因为 sign_out
有其他代码来清除登录。 我们在本地解决了这个问题,并向 Authy 提出了一项请求,以便能帮助大家解决问题。
连信誉良好的安全公司有时也会出错,并且渗透测试也没有好的替代品。 对我们来说,最经济有效的方法仍然是 HackerOne。 我们发现这些报告对 Flexport 和我们的安全都具有很高的价值。