安全注意事项

Web 应用通常会面临各种安全问题,很难把他们全都处理好。Flask 试图为你解决其中的一些问题,但还有很多需要你自己处理。

跨站脚本攻击(XSS)

跨站脚本攻击是指将恶意的 HTML(以及附带的 JavaScript)代码注入到网站的环境中。为了解决这个问题,开发人员必须正确地转义文本,使其无法包含恶意的 HTML 标签。更多信息请看维基百科上的文章 Cross-Site Scripting

Flask 配置了 Jinja2 自动转义所有值,除非显式指明不转义。这应该可以排除所有由模板引起的 XSS 问题,但仍有其他地方需要注意:

  • 不使用 Jinja2 生成 HTML

  • 在用户提交的数据上调用 Markup

  • 发送上传的 HTML 文件,千万不要这样做,应该使用 Content-Disposition: attachment 头部来避免这个问题。

  • 发送上传的文本文件。一些浏览器会基于文件开头的几个字节推测文件的内容类型,因此用户可以欺骗浏览器执行 HTML。

另外值得注意的是未经引号包裹的属性。虽然 Jinja2 可以通过转义 HTML 避免 XSS 问题,但无法避免通过属性注入的 XSS。为了对付这种可能存在的攻击手段,在属性中使用 Jinja 表达式时,请保证属性值始终被包括在双引号或单引号内:

<input value="{{ value }}">

为什么必须这样做?因为如果不这样做,攻击者可以轻易注入定制的 JavaScript 事件处理器。例如,攻击者可以注入以下 HTML + JavaScript 代码:

onmouseover=alert(document.cookie)

当用户将鼠标移到输入框时,cookie 信息就会显示在弹出的警告窗口中。 一个聪明的攻击者可能不会直接向用户显示 cookie 信息,而是执行其他的 JavaScript 代码。 结合 CSS 注入,攻击者甚至可能使该元素填满整个页面,这样用户将鼠标放在页面的任何位置都会触发攻击。

有一类 XSS 问题,Jinja 的转义并不能防止。a 标签的 href 属性可以包含一个 javascript: URI,如果没有采取合理的安全措施,在点击时浏览器就会执行。

<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>

为了防止这种问题发生,你需要设置 内容安全策略(CSP) 响应头部。

跨站请求伪造(CSRF)

另一个大问题是跨站请求伪造。这是一个很复杂的话题,细节不在这里赘述,只谈谈它是什么以及如何在理论上避免这个问题。

如果你的认证信息存储在 cookie 中,你就有了隐式状态管理。“登录”的状态是由一个 cookie 控制的,对每个页面的请求都会带上这个 cookie。不幸的是,由第三方网站发起的请求也遵循同样的机制。如果你不牢记这一点,有人可能会用社交工程欺骗应用程序的用户,让他们在不知情的情况下做一些愚蠢的事情。

假设你有一个特定的 URL,当你发送 POST 请求时会删除一个用户的资料(例如 http://example.com/user/delete)。 如果攻击者现在创建了一个页面可以通过一些 JavaScript 代码向你的特定 URL 发送 post 请求,只要欺骗用户来加载这个页面,用户资料就会被删除了。

想象一下有数百万并发用户的 Facebook,有人发出小猫咪图片的链接。 当用户访问该页面看着毛茸茸的猫片时,他们的资料就被删除了。

如何防止这种情况呢?基本思路是:每一个修改服务器内容的请求,必须使用一个一次性令牌,并将其存储在 cookie 中,传输表单数据时带上它。服务器再次接收到数据后,必须比较两个令牌,确保它们是相等的。

为什么 Flask 不替你完成这个呢?因为这是表单验证框架要做的事,而 Flask 自身不包含表单验证。

JSON 安全

在 Flask 0.10 以及更低版本中,因为 ECMAScript 4 中的安全漏洞,jsonify() 没有将顶层数组序列化为 JSON。

ECMAScript 5 关闭了这个漏洞,所以只有非常老的浏览器仍然容易受到攻击,这些浏览器还存在 其他更严重的漏洞,因而这个行为被改变了,jsonify() 现在支持序列化数组。

安全头部

为了控制安全性,浏览器识别各种响应头部。我们建议你在应用程序中检查以下每种头部,Flask-Talisman 扩展可用于管理 HTTPS 和安全头部。

HTTP 严格传输安全(HSTS)

指示浏览器把所有 HTTP 请求转为 HTTPS,防止中间人攻击(MITM)。

response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'

内容安全策略(CSP)

告诉浏览器可以从哪里加载各种类型的资源。应当尽可能使用这个头部,但需要为网站定义正确的策略。一个非常严格的策略是:

response.headers['Content-Security-Policy'] = "default-src 'self'"

X-Content-Type-Options

强制浏览器接受响应的内容类型,而不是尝试检测它,防止被滥用而产生跨站脚本攻击(XSS)。

response.headers['X-Content-Type-Options'] = 'nosniff'

X-Frame-Options

防止外部网站将你的网站嵌入 iframe 中,可以避免一类攻击:对外框的点击无形地转化为对你的页面元素的点击。这也被称为“点击劫持”。

response.headers['X-Frame-Options'] = 'SAMEORIGIN'

HTTP 公钥锁定(HPKP)

它告诉浏览器只使用特定的证书密钥与服务器进行验证,以防止 MITM 攻击。

警告

启用这个要小心,如果设置密钥或升级密钥不正确则难以撤销。

复制/粘贴到终端

隐藏的字符如退格符(\b, ^H)会导致文本在 HTML 中的显示方式与 粘贴到终端 的呈现方式不同。

例如,import y\bose\bm\bi\bt\be\b 在 HTML 中显示为 import yosemite,但在粘贴到终端时退格符生效,变成 import os.

如果用户有可能从你的网站上复制和粘贴不受信任的代码,比如在技术博客上发布的评论,要考虑使用更多的过滤条件,例如替换所有 \b 字符。

body = body.replace("\b", "")

大多数现代终端在粘贴时都会警告并删除隐藏的字符,所以严格来说这并不是必须的。还有以其他方式生成危险命令的可能,这些都是不可能完全过滤的。根据你的网站的使用情况来做决定,复制代码时显示一个一般警告也是可以的。