CORS和JSONP跨域漏洞
去年对同源策略进行过入门,里面介绍了同源策略和几种跨域传输资源的方法。但是都比较浅显,没有深入研究,这里进一步研究。
跨域
SOP,同源策略 (Same Origin Policy),该策略是浏览器的一个安全基石,如果没有同源策略,那么,你打开了一个合法网站,又打开了一个恶意网站。恶意网站的脚本能够随意的操作合法网站的任何可操作资源,没有任何限制。
同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的关键的安全机制.简单说就是浏览器的一种安全策略。
“同源”包括三个条件:
- 同协议
- 同域名
- 同端口
随着互联网的发展,同源策略越来越严格,目前,对于非同源的网站共有三种行为受到限制。
- Cookie. LocalStorage和IndexDB无法获取。
- DOM无法获得。
- Ajax请求不能发送。
example:
当attacker.me试图获取victim.me下的资源,浏览器会阻止返回该资源。
SOP是一个很好的策略,但是随着Web应用的发展,网站由于自身业务的需求,需要实现一些跨域的功能,能够让不同域的页面之间能够相互访问各自页面的内容。
跨域:指的是浏览器不能执行其它网站的脚本,它是由浏览器的同源策略造成的,是浏览器的安全限制!
跨域常见的两种方式,分别是JSONP和CORS。
还有很多标签可以跨域(如script,img,iframe,link等),主域下不同子域设置document.domain也可以跨域访问
CORS跨域
CORS,跨域资源共享(Cross-origin resource sharing),是H5提供的一种机制,WEB应用程序可以通过在HTTP增加字段来告诉浏览器,哪些不同来源的服务器是有权访问本站资源的,当不同域的请求发生时,就出现了跨域的现象。
跨域访问的一些场景
- 比如后端开发完一部分业务代码后,提供接口给前端用,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问的问题。
- 程序员在本地做开发,本地的文件夹并不是在一个域下面,当一个文件需要发送ajax请求,请求另外一个页面的内容的时候,就会跨域。
- 电商网站想通过用户浏览器加载第三方快递网站的物流信息。
- 子站域名希望调用主站域名的用户资料接口,并将数据显示出来。
CORS漏洞的攻击流程
那么CORS跨域导致用户信息泄漏是怎么发生的呢?
- 假设用户登陆一个含有CORS配置网站
vuln.com
,同时又访问了攻击者提供的一个链接evil.com
。 evil.com
的网站向vuln.com
这个网站发起请求获取敏感数据,浏览器能否接收信息取决于vuln.com
的配置。- 如果
vuln.com
配置了Access-Control-Allow-Origin
头且为预期,那么允许接收,否则浏览器会因为同源策略而不接收。
CORS错误配置
怎么才算是错误的配置能够让攻击者有利可图呢?一般需要以下的两个配置:
# 允许所有域传输数据
header("Access-Control-Allow-Origin: *");
# 允许其他域带上验证信息(cookie)进行请求
header("Access-Control-Allow-Credentials: true");
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。
Nginx 错误配置示例:
add_header "Access-Control-Allow-Origin" $http_origin;
add_header "Access-Control-Allow-Credentials" "true";
动态生成访问控制策略的方法:在Access-Control-Allow-Origin
中反射请求的Origin值。该配置可导致任意攻击者网站可以直接跨域读取其资源内容。
绕过
Origin校验绕过
target.domain
允许example.com
跨域,对example.com
进行校验
匹配方式 | 校验内容 | 绕过方式 |
---|---|---|
包含匹配 | 只校验是否包含example.com | 构造Origin: https://malicious.example.com |
前缀匹配 | 只校验前缀是否为example.com | 构造Origin: https://example.com.malicious.com. |
后缀匹配 | 只校验后缀是否为example.com | 构造Origin: https://maliciousexample.com |
子域名匹配 | 只校验是否为子域名 | 控制目标站点某一子域名或利用存在XSS漏洞的子域名 |
信任null源
开发者在网站上配置信任null
源,用于调试代码(页面跳转、与本地file页面共享数据)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
攻击者可从任意域下通过iframe sandbox
构造Origin
为null
的跨域请求
<iframe sandbox=”allow-scripts allow-top-navigation allow-forms” src=’data:text/html,’>
请求包
GET /handler
Host: target.local
Origin: null
响应包
HTTP/1.1 200 OK
Acess-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
HTTPS域信任HTTP域
HTTPS协议被设计用于在不安全的网络中进行安全通信。即使在中间人网络环境下,攻击者也应该无法读取HTTPS网站的内容。但是如果该HTTPS网站配置了CORS且信任HTTP域,那么中间人攻击者可以先劫持受信任HTTP域,然后通过这个域发送跨域请求到HTTPS网站,间接读取HTTPS域下的受保护内容。
信任自身全部子域
为了防止某个子域上XSS漏洞的危害其他子域,浏览器设计了Cookie
的httponly
标志,用于限制Javascript读取Cookie,因此某个子域XSS不能读取带有httponly
标记的Cookie
,难以窃取其他重要子域上的敏感内容。 但是如果这个域配置了CORS且信任全部子域,那么攻击者可以利用其他任意子域上XSS漏洞,发送跨域请求到目标重要域网站,从而获取敏感内容。
Origin:*与 Credentials:true 共用
浏览器报错
Access-Control-Allow-Origin:*
Access-Control-Allow-Credentials:true
为避免该配置产生浏览器报错,部分Web框架将Access-Control-Allow-Origin:*
与Access-Control-Allow-Credentials:true
转换为反射Origin,导致产生安全问题
缺少Vary:Origin头
当 Access-Control-Allow-Origin
是被动态生成的话,则需指定 Vary: Origin
标头,避免攻击者利用缓存进行攻击。该头部字段向客户端表明,服务器端的返回内容将根据请求中 Origin
的值发生变化
CORS防御
- 关闭不必要开启的CORS
- 白名单限制:定义“源”的白名单,避免使用正则表达式,不要配置
Access-Control-Allow-Origin
为通配符*
或null
,严格效验来自请求数据包中的Origin
的值 - 仅允许使用安全协议,避免中间人攻击
- 尽可能的返回
Vary: Origin
头部,以避免攻击者利用浏览器缓存进行攻击 - 避免将
Access-Control-Allow-Credentials
标头设置为默认值true
,跨域请求若不存在必要的凭证数据,则根据实际情况将其设置为false
- 限制跨域请求允许的方法,
Access-Control-Allow-Methods
最大限度地减少所涉及的方法,降低风险 - 限制浏览器缓存期限:建议通过
Access-Control-Allow-Methods
和Access-Control-Allow-Headers
头部,限制浏览器缓存信息的时间。通过配置Access-Control-Max-Age
标头来完成,该头部接收时间数作为输入,该数字是浏览器保存缓存的时间。配置相对较低的值,确保浏览器在短时间内可以更新策略 - 仅在接收到跨域请求时才配置有关于跨域的头部,并确保跨域请求是合法的源,以减少攻击者恶意利用的可能性。
JSONP跨域
JSONP(JSON with Padding) 是 json 的一种”使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。JSONP实现跨域请求的原理简单的说,就是动态创建<script>
标签,然后利用<script>
的 src 不受同源策略约束来跨域获取数据。
JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。
JSONP的语法和JSON很像,简单来说就是在JSON外部用一个函数包裹着。JSONP基本语法如下:
callback({ "name": "kwan" , "msg": "获取成功" });
常见的JSONP形式类似:http://www.test.com/index.html?jsonpcallback=callback
传过去的callback就是函数名,服务端返回的是一个函数调用,可以理解为:callback就是一个函数,([“customername1″,”customername2”])就是函数参数,网站前端只需要再编写代码处理函数返回的值即可。
以我简单的理解来说,就是传递一个函数名给JSONP接口参数,然后服务端返回函数({数据})
这样的结果,通过客户端页面的js函数(整个函数我们可以控制),将传回来的数据作为函数的参数,从而对返回的数据为所欲为。
JSONP 劫持的攻击流程
- 用户在网站B 注册并登录,网站B 包含了用户的id,name,email等信息;
- 用户通过浏览器向网站A发出URL请求;
- 网站A向用户返回响应页面,响应页面中注册了 JavaScript 的回调函数和向网站B请求的
<script>
标签,示例代码如下:
<script type="text/javascript">
function Callback(result)
{
alert(result.name);
}
</script>
<script type="text/javascript" src="http://B.com/user?jsonp=Callback"></script>
- 用户收到响应,解析 JS 代码,将回调函数作为参数向网站B发出请求;
- 网站 B 接收到请求后,解析请求的 URL,以 JSON 格式生成请求需要的数据,将封装的包含用户信息的 JSON 数据作为回调函数的参数返回给浏览器,网站B返回的数据实例如下:
Callback({"id":1,"name":"test","email":"test@test.com"})
- 网站B数据返回后,浏览器则自动执行 Callback 函数对步骤4返回的 JSON 格式数据进行处理,通过 alert 弹窗展示了用户在网站B的注册信息。另外也可将 JSON 数据回传到网站A的服务器,这样网站A利用网站B的JSONP漏洞便获取到了用户在网站B注册的信息。
绕过
Referer 过滤(正则)不严谨
比如 http://www.qq.com/login.php?calback=cb 输出数据时,使用了 Referer 过滤。但是可惜过滤的时候只过滤了 Referer 里是否存在 qq.com 这样的关键词,那么攻击者可以听过构造 URL:http://www.qq.com.attack.com/attack.htm 或者 http://www.attack.com/attack.htm?qq.com 这样的页面来发起攻击实现绕过 Referer 防御。
空 Referer
在很多情况下,开发者在部署过滤 Referer 来源时,忽视了一个空 Referer 的过滤。一般情况下浏览器直接访问某 URL 是不带 Referer 的,所以很多防御部署是允许空 Referer 的。恰恰也就是这个忽视,导致了整个防御的奔溃。因为在通过跨协议调用 js 时,发送的 http 请求里 Referer 为空! 跨协议调用的一个简单例子:
<iframe src="javascript:'<script>function JSON(o){alert(o.userinfo.userid);}</script><script src=http://www.qq.com/login.php?calback=JSON></script>'"></iframe>
代码里我们使用 <iframe>
调用 javscript 伪协议来实现空 Referer 调用 JSON 文件。
如果目标网站可以通过HTTP访问,也可以通过将我们的代码托管在一个HTTPS页面来避免发送HTTP Referer。如果我们从HTTPS页面发起一个HTTP请求,浏览器为了防止信息泄漏是不会发送Referer header。以上我们要将恶意代码托管在一个启用了HTTPS的站点。
JSONP防护
- 严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 等。
- 严格按照 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 )。
- 严格过滤 callback 函数名及 JSON 里数据的输出。
- 严格限制对 JSONP 输出 callback 函数名的长度(如防御上面 flash 输出的方法)。
- 其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,听过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用