安全注意事项¶
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>
为了防止这种情况发生,需要设置 内容安全策略(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严格传输安全性(HSTS)¶
告诉浏览器将所有HTTP请求转换为HTTPS,以防止中间人man-in-the-middle (MITM)攻击。
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
内容安全策略(CSP)¶
告诉浏览器从何处可以加载各种类型的资源。只要可能,就应该使用这个头部,但是需要做一些工作来为你的站点定义正确的策略。一项非常严格的政策是:
response.headers['Content-Security-Policy'] = "default-src 'self'"
X内容类型选项¶
强制浏览器遵守内容类型,而不是尝试检测它,这可能会被滥用以生成跨站点脚本(XSS)攻击。
response.headers['X-Content-Type-Options'] = 'nosniff'
X帧选项¶
阻止外部网站将你的网站嵌入到 iframe
. 这样可以防止外部框架点击转化针对你的页面元素的隐藏点击,也称为“点击支持”。
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
X-XSS-保护¶
如果请求包含类似于javascript的内容,并且响应包含相同的数据,那么浏览器将尝试通过不加载页面来防止反射的XSS攻击。
response.headers['X-XSS-Protection'] = '1; mode=block'
设置cookie选项¶
这些选项可以添加到 Set-Cookie
来提高他们的安全性。Flask 具有将 其配置于会话 cookie上的配置选项。它们也可以配置在其他 cookie 上。
Secure限制cookies 仅用于HTTPS流量。
HttpOnly
防止javascript读取cookie的内容。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将被删除。
# 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
...
使用 itsdangerous.TimedSerializer
对其他cookie值(或任何需要安全签名的值)进行签名和验证。
复制/粘贴到终端¶
隐藏字符,如退格符 (\b
, ^H
) can cause text to render differently in HTML than how it is interpreted if pasted into a terminal .
例如, import y\bose\bm\bi\bt\be\b
呈现为 import yosemite
在HTML中,但是当粘贴到终端时会应用退格,并且 import os
.
如果您希望用户从您的站点复制并粘贴不可信的代码,例如用户在技术博客上发布的评论,请考虑应用额外的过滤,例如全部替换 \b
字符。
body = body.replace("\b", "")
大多数现代终端在粘贴时都会警告并删除隐藏字符,所以这不是严格必要的。也可以用其他无法过滤的方式来编写危险的命令。根据您的站点的用例,通常显示关于复制代码的警告可能是好的。