CSRF攻击介绍
详细可看这篇文章:XSS攻击与CSRF攻击
HTTP cookies
cookie的作用域
domain和path定义了cookie的作用域:即允许cookie应该发给哪些URL。
Domain
domain指定了哪些主机可以接受cookie。如果没指定,默认为set-cookie的origin,不包含子域名。如果指定了domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。
Path
Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。
例如,设置 Path=/docs,则以下地址都会匹配:
- /docs
- /docs/Web/
- /docs/Web/HTTP
SameSite attribute
SameSite Cookie 允许服务器要求某个 cookie 在跨站请求时不会被发送,(其中 Site (en-US) 由可注册域定义),从而可以阻止跨站请求伪造攻击(CSRF)。
SameSite cookies 是相对较新的一个字段,所有主流浏览器都已经得到支持。
SameSite 可以有下面三种值:
- None。浏览器会在同站请求、跨站请求下继续发送 cookie,不区分大小写。(此时,
secure
属性必须设置为true
,即请求必须要支持https) - Strict。浏览器将只在访问相同站点时发送 cookie。
- Lax。与 Strict 类似,但用户从外部站点导航至URL时(例如通过链接)除外。
问题:
- chrome在80版本之后,更新了cookie的携带机制,把原来Cookie的
SameSite
属性默认值,由None
改成了Lax
,这就会导致一些需要第三方cookie的应用产生了异常。
- 部分浏览器不能加
SameSite=none
,比如IOS 12的Safari,以及一些老版本的chrome浏览器,它们会错误的把SameSite=none
识别成SameSite=strict
具体不兼容的浏览器可以见这里
概念介绍
首先我们要了解一些基本概念,有助于我们对整个流程的理解。MDN上介绍的很详细了,我就不赘述了。大家可点击链接仔细阅读:
浏览器的同源策略(Same-origin policy)
跨源资源访问
同源策略控制不同源之间的交互。这些交互通常分为三类:
- 跨域写操作一般是允许的。如链接,重定向和表单提交。特定少数的HTTP请求需要添加 preflight。
- 跨域资源嵌入一般是允许的。如:
<script src="..."></script>
标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。<link rel="stylesheet" href="...">
标签嵌入CSS。由于CSS的松散的语法规则,CSS的跨域需要一个设置正确的 HTTP 头部 Content-Type 。不同浏览器有不同的限制: IE, Firefox, Chrome, Safari (跳至CVE-2010-0051)部分 和 Opera。- 通过
<img>
展示的图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,... - 通过
<video>
和<audio>
播放的多媒体资源。 - 通过
<object>
、<embed>
和<applet>
嵌入的插件。 - 通过
@font-face
引入的字体。一些浏览器允许跨域字体(cross-origin fonts
),一些需要同源字体(same-origin fonts
)。 - 通过
<iframe>
载入的任何资源。站点可以使用X-Frame-Options
消息头来阻止这种形式的跨域交互。
- 跨域读操作一般是不被允许的
跨域数据存储访问
访问存储在浏览器中的数据,如 localStorage 和 IndexedDB,是以源进行分割。每个源都拥有自己单独的存储空间,一个源中的 JavaScript 脚本不能对属于其它源的数据进行读写操作。
Cookies 使用不同的源定义方式。一个页面可以为本域和其父域设置 cookie,只要是父域不是公共后缀(public suffix)即可。不管使用哪个协议(HTTP/HTTPS)或端口号,浏览器都允许给定的域以及其任何子域名(sub-domains) 访问 cookie。当你设置 cookie 时,你可以使用 Domain、Path、Secure、和 HttpOnly 标记来限定其可访问性。当你读取 cookie 时,你无法知道它是在哪里被设置的。 即使您只使用安全的 https 连接,您看到的任何 cookie 都有可能是使用不安全的连接进行设置的。
跨源资源共享(CORS)
内容安全策略(CSP)
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS (en-US)) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
为使CSP可用, 你需要配置你的网络服务器返回 Content-Security-Policy
HTTP头部 ( 有时你会看到一些关于X-Content-Security-Policy
头部的提法, 那是旧版本,你无须再如此指定它)。如下图:
除此之外, <meta>
元素也可以被用来配置该策略, 例如
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
CSP 的 default-src详细解析可参考链接,以下是对图片中出现的数据的解析:
- connect-src:用于控制允许通过脚本接口加载的链接地址
- img-src:图片资源
- script-src:JavaScript脚本资源
- frame-src:
<frame> <iframe>
资源 - font-src:字体资源
- media-src:多媒体资源
- style-src:css资源
'self'
指向与要保护的文件所在的源,包括相同的 URL scheme 与端口号。必须有单引号。一些浏览器会特意排除 blob 与 filesystem 。需要设定这两种内容类型的站点可以在 Data 属性中进行设定。
'unsafe-inline'
允许使用内联资源,例如内联 <script>
元素(javascript: URL)、内联事件处理器以及内联 <style>
元素。必须有单引号。
<scheme-source>
协议名如'http:' 或者 'https:'。必须带有冒号,不要有单引号。同时你还可以指定数据协议(data schema)(不推荐使用)。
- data: 允许 data: URIs 作为内容的源。这是不安全的。攻击者可以注入任意 data: URI 。不要轻易使用这种形式的源,尤其是脚本,绝对不要使用。
- mediastream: 允许 mediastream: URIs 作为内容的源。
- blob: 允许 blob: URIs 作为内容的源。
- filesystem: 允许 filesystem: URIs 作为内容的源。
另外,webpack也支持CSP内容安全策略的配置,感兴趣的可自己配置试一下。其中的nonce的解释如下:
- 'nonce-<base64值>' 特定使用一次性加密内联脚本的白名单。服务器必须在每一次传输政策时生成唯一的一次性值。否则将存在绕过资源政策的可能。
易混淆概念区分
对于这些文章,有些疑惑的点,我在这里进行了补充。
问题一: 如上图,描述的SCRF攻击。cookie不是说不能跨域吗?为什么CSRF攻击中,用户打开一个第三方网站Web(B),发送请求,能够携带Web(A)的cookie。
cookie不能跨域访问(除非是在cookie的domain允许的子域),但是可以跨站点共享,这两个概念要区分开来。
在同一浏览器,当前打开的多个Tab页网站,无论是否为同一站点,cookie都是共享可见的。这个共享不是说每个网站的脚本可以访问别的网站的cookie,而是说,你向同一服务器发送请求时,会带上浏览器保存的对于那个服务器的所有cookie,而不管你从哪个网站发起的请求。
以下,是实际举例说明:
-
下图是当前用户登陆的网站【a】的cookie,且用户没有关闭网站【a】
-
同时,用户打开一个新的tab标签【b】,请求网站【a】的用户登录信息。如下图可见,请求携带了网站【a】的cookie,并且接口正常返回了用户信息
-
而,我在另一个浏览器,发起同样的请求,则没有返回用户信息,也没有携带cookie,因为cookie没有存在这个浏览器。
因此,cookie不能跨域访问,但cookie可以跨站点共享
问题二:为什么form表单提交没有跨域问题,但ajax提交有跨域问题?
浏览器的同源策略的本质:一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。
form 提交(submit函数)之后,是不会有任何数据返回的。没机会读任何东西,所以可以认为是无害的,不在同源策略之内。
而 AJAX 是可以读取响应内容的,因此浏览器的同源策略不允许这样的行为。
所以同源策略会限制 Ajax读取响应内容 ,不会限制 Form发送请求。
- 对于跨域的AJAX请求,需要设置withCredentials = true,否则请求不会携带Cookie,同时,Access-Control-Allow-Origin不能设置为
*
,要设置成相应的域名
router.get('/api/data', (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , myheader');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
ctx.set('Access-Control-Allow-Credentials', 'true');
};
- 对于非跨域的请求,或者跨域的非AJAX请求--比如通过form表单提交,浏览器会自动携带上Cookie。
如何防止CSRF攻击
- 现在浏览器默认的Samesite就是lax,但是在简单请求的情况下,浏览器任然会自动带上cookie到服务器,所以,这个时候还是需要加上服务器token验证才保险。(如果要设置cookie跨域传输,这里有些要注意的地方,见文章:chrome 默认 cookie 的 SameSite=Lax,导致 http 模式的站点的第三方 cookie 无法进行跨域传输。)
- 给cookie设置SameSite属性为strict,浏览器将只在访问相同站点时发送 cookie,但是这样就没办法设置cookie跨域传输了。
- 校验referer和origin,referer是否是从不安全的域名跳转过来,origin是否不是安全的
- 使用CSP,详细如上问解释