安全注意事项
Web 应用常常会面对各种各样的安全问题,因此要把所有问题都解决是很难的。Flask 尝试为你解决许多安全问题,但是更多的还是只能靠你自己。
跨站脚本攻击(XSS)
跨站脚本攻击是指在一个网站的环境中注入恶任意的 HTML (包括附带的JavaScript )。要防防御这种攻击,开发者需要正确地转义文本,使其不能包含恶意的 HTML 标记。更多的相关信息请参维基百科上在文章: 跨站脚本 。
在 Flask 中,除非显式指明不转义, Jinja2 会自动转义所有值。这样可以排除所有模板导致的 XSS 问题,但是其它地方仍需小心:
不使用 Jinja2 生成 HTML 。
在用户提交的数据上调用了
Markup
。发送上传的 HTML ,永远不要这么做,使用
Content-Disposition: attachment
头部来避免这个问题。发送上传的文本文件。一些浏览器基于文件开头几个字节来猜测文件的content-type ,用户可以利用这个漏洞来欺骗浏览器,通过伪装文本文件来执行 HTML 。
另一件非常重要的漏洞是不用引号包裹的属性值。虽然 Jinja2 可以通过转义 HTML来保护你免受 XSS 问题,但是仍无法避免一种情况:属性注入的 XSS 。为了免受这种攻击,必须确保在属性中使用 Jinja 表达式时,始终用单引号或双引号包裹:
<input value="{{ value }}">
为什么必须这么做?因为如果不这么做,攻击者可以轻易地注入自制的 JavaScript处理器。例如一个攻击者可以注入以下 HTML+JavaScript 代码:
onmouseover=alert(document.cookie)
当用户鼠标停放在这个输入框上时,会在警告窗口里显示 cookie 信息。一个精明的攻击者可能还会执行其它的 JavaScript 代码,而不是把 cookie 显示给用户。结合CSS 注入,攻击者甚至可以把元素填满整个页面,这样用户把鼠标停放在页面上的任何地方都会触发攻击。
有一类 XSS 问题 Jinja 的转义无法阻止。 a
标记的 href
属性可以包含一个 javascript: URI 。如果没有正确保护,那么当点击它时浏览器将执行其代码。
<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>
为了防止发生这种问题,需要设置 Content Security Policy (CSP) 响应头部。
跨站请求伪造( CSRF )
另一个大问题是 CSRF 。这个问题非常复杂,因此我不会在此详细展开,只是介绍CSRF 是什么以及在理论上如何避免这个问题。
如果你的验证信息存储在 cookie 中,那么你就使用了隐式的状态管理。“已登入”这个状态由一个 cookie 控制,并且这个 cookie 在页面的每个请求中都会发送。不幸的是,在第三方站点发送的请求中也会发送这个 cookie 。如果你不注意这点,一些人可能会通过社交引擎来欺骗应用的用户在不知情的状态下做一些蠢事。
假设你有一个特定的 URL ,当你发送 POST
请求时会删除一个用户的资料(例如 http://example.com/user/delete
) 。如果一个攻击者现在创造一个页面并通过页面中的 JavaScript 发送这个 post 请求,只要诱骗用户加载该页面,那么用户的资料就会被删除。
设象在有数百万的并发用户的 Facebook 上,某人放出一些小猫图片的链接。当用户访问那个页面欣赏毛茸茸的小猫图片时,他们的资料就被删除了。
那么如何预防这个问题呢?基本思路是:对于每个要求修改服务器内容的请求,应该使用一次性令牌,并存储在 cookie 里, 并且 在发送表单数据的同时附上它。在服务器再次接收数据之后,需要比较两个令牌,并确保它们相等。
为什么 Flask 没有替你做这件事?因为这应该是表单验证框架做的事,而 Flask 不包括表单验证。
JSON 安全
Flask 0.10 版和更低版本中, jsonify()
没序列化顶层数组为JSON 。这是因为 ECMAScript 4 存在安全漏洞。
ECMAScript 5 关闭了这个漏洞,所以只有非常老的浏览器仍然脆弱,而且还有其他更严重的漏洞 。因此,这个行为被改变了,并且 jsonify()
现在支持了序列化数据。
安全头部
为了控件安全性,浏览器识别多种头部。我们推荐检查应用所使用的以下每种头部。Flask-Talisman 扩展可用于管理 HTTPS 和安全头部。
HTTP Strict Transport Security (HSTS)
告诉浏览器把所有 HTTP 请求转化为 HTTPS ,以防止man-in-the-middle (MITM) 攻击。
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
Content Security Policy (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'
X-XSS-Protection
如果请求包含类似于 JavaScript 的东西且响应的内容包含相同的数据时,浏览器将尝试通过不加载页面来防止反射的 XSS 攻击。:
response.headers['X-XSS-Protection'] = '1; mode=block'
Set-Cookie 选项
这些选项可以被添加到一个 Set-Cookie
头部以增强其安全性。 Flask 具有将其配置于会话 cookie 上的配置选项。它们也可以配置在其他 cookie 上。
Secure
限制 cookies 仅用于 HTTPS 流量。HttpOnly
保护 cookies 内容不被 JavaScript 读取。SameSite
限制如何从外部网站通过请求发送 cookie 。可以设置为'Lax'
(推荐)或者'Strict'
。Lax
防止从外部网站通过有 CSRF倾向请求(比如一个表单)发送 cookie 。Strict
防止通过所有外部请求发送 cookie ,包括常规连接。
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')
指定 Expires
或者 Max-Age
选项后,将会分别在给定时间后或者当前时间加上所定义存活期后删除 cookie 。如果两个参数都没有指定,则会在关闭浏览器时删除。
# cookie expires after 10 minutes
response.set_cookie('snakes', '3', max_age=600)
对于会话 cookie 来说,如果session.permanent
被设置了,那么PERMANENT_SESSION_LIFETIME
会被用于设置有效期。Flask 的缺省 cookie 实现会验证加密签名不会超过这个值。降低这个值有助于缓解重播攻击,可以在稍后发送被拦截的 cookie 。
app.config.update(
PERMANENT_SESSION_LIFETIME=600
)
@app.route('/login', methods=['POST'])
def login():
...
session.clear()
session['user_id'] = user.id
session.permanent = True
...
Use itsdangerous.TimedSerializer
to sign and validate other cookievalues (or any values that need secure signatures).
HTTP Public Key Pinning (HPKP)
告诉浏览器只使用指定的证书密钥进行服务器验证,以防止 MITM 攻击。
Warning
启用后请小心,如果密钥设置或者升级不正确则难以撤消。