注意:本文分享给安全从业人员、网站开发人员以及运维人员在日常工作防范恶意攻击,请勿恶意使用下面介绍技术进行非法攻击操作。。
[TOC]
0x00 前言介绍 什么是CORS?为什么要使用CORS机制? 答:CORS是一个W3C标准机制全称是”跨域资源共享”(Cross-origin resource sharing)
它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
那什么是同源? 答:如果看不懂下面英文解释的您可以这样理解,不同协议不同端口不同域名满足其中一个则是不同源的;
注意:本文分享给安全从业人员、网站开发人员以及运维人员在日常工作防范恶意攻击,请勿恶意使用下面介绍技术进行非法攻击操作。。
[TOC]
0x00 前言介绍 什么是CORS?为什么要使用CORS机制? 答:CORS是一个W3C标准机制全称是”跨域资源共享”(Cross-origin resource sharing)
它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
它允许浏览器向跨源(协议 + 域名 + 端口)服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制
那什么是同源? 答:如果看不懂下面英文解释的您可以这样理解,不同协议不同端口不同域名满足其中一个则是不同源的;1 2 3 4 5 6 Two websites are said to have same origin if both have following in common: Scheme (http, https) Host name (google.com, facebook.com, securelayer7.net) Port number (80, 4567, 7777) So, sites http://example.com and http://example.com/settings have same origin. But https://example.com:4657 and http://example.com:8080/settings have different origins
同源策略(same-origin policy)限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
使用CORS好处:
必要条件:
CORS需要浏览器和服务器同时支持,IE浏览器不能低于IE10。
0x01 CORS 请求原理 浏览器对于CORS两大请求处理是不一样的:
1.简单请求(simple request)
2.非简单请求(not-so-simple request)
以简单请求为例,凡是不同时满足下面面两个条件,就属于非简单请求。1 2 3 4 5 6 7 8 9 10 (1) 请求方法是以下三种方法之一: HEAD GET POST (2) HTTP的请求头信息不超出以下几种字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
CORS 6个基本字段:
Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,可利用XMLHttpRequest对象.getResponseHeader(‘FooBar’)获取设置的字段;1 2 3 4 5 Access-Control-Allow-Origin Access-Control-Allow-Credentials Access-Control-Allow-Methods Access-Control-Allow-Headers Access-Control-Max-Age
例子:浏览器发现这次跨源AJAX请求是一般请求,就自动在头信息之中添加一个Origin字段。1 2 3 4 5 6 7 GET /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0
如果在B站请求A站,浏览器是不允许跨域获取数据的,如果在A站返回的数据加上一个Access-Control-Allow-Origin:* 的HTTP的头这时所有网站都能访问(但是有的浏览器默认会进行过滤))。 但是这并不是我们想要的,只需把 Access-Control-Allow-Origin: api.bob.com 修改成需要给权限的网站即可。
simple request (1)简单请求直接发送CORS请求重要就是Origin头与返回的Access-Control-Allow-Origin消息头
:1 2 3 4 5 6 7 8 9 10 11 12 GET /cors HTTP/1.1 Host: api.alice.com Origin: http://api.bob.com Connection: keep-alive User-Agent: Mozilla/5.0... Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
withCredentials 属性的作用: 描述:CORS请求默认不发送Cookie和HTTP认证信息,如果要把Cookie发到服务器,一方面要服务器同意,另一方面是在编写AJAX请求的时候加上发送cookie的头;1 xhr.withCredentials = true; //浏览器不同可能在未设置为true默认会上传cookie
同时Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下Cookie。
not-so-simple request 描述:非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE或者Content-Type字段的类型是application/json;
比如JAVASCRIPT测试ajax:1 2 3 4 5 var url = 'http://api.alice.com/cors' ;var xhr = new XMLHttpRequest();xhr.open('PUT' , url, true ); xhr.setRequestHeader('X-Custom-Header' , 'value' ); xhr.send();
非简单请求的CORS请求,会在正式通信之前增加一次HTTP查询请求,称为"预检"请求(preflight)
;如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段,可以采用XMLHttpRequest对象的onerror回调函数捕获
;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 OPTIONS /cors HTTP/1.1 Origin: http://api.bob.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: api.alice.com Connection: keep-alive User-Agent: Mozilla/5.0... HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Access-Control-Max-Age: 1728000 Content-Type: text/html; charset=utf-8 Content-Encoding: gzip Content-Length: 0 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
下面是”预检”请求之后浏览器的正常CORS请求,下面头信息中Access-Control-Allow-Origin字段是每次回应都必定包含的。1 2 3 4 5 6 7 8 9 10 11 12 PUT /cors HTTP/1.1 Origin: http://api.bob.com Host: api.alice.com X-Custom-Header: value Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0... Access-Control-Allow-Origin: http://api.bob.com Content-Type: text/html; charset=utf-8
问:与JSONP的比较?
1.CORS与JSONP的使用目的相同但是比JSONP更强大。
2.JSONP只支持GET请求,CORS支持所有类型的HTTP请求。
3.JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
0x02 CORS 安全问题 描述:CORS漏洞( Vulnerability)存在的原因主要在于Access-Control-Allow-Origin参数配置失误未严格验证,允许非同域站点访问本站资源从而造成跨域问题。
问题1:如果Access-Control-Allow-Origin可控且Access-Control-Allow-Credentials为true,那么就可以利用一个可控的网站来窃取一个人的个人隐私信息
(1)返回报文头部的Access-Control-Allow-Origin根据请求报文Origin ,Ps:只要页面产生跨域请求那浏览器就会在请求报文中自动;
(2)返回报文头部的Access-Control-Allow-Credentials为true,这表明Cookie可以包含在请求中,一起发给服务器;
问题2:CORS的规范中还提到了“NULL”源,触发这个源是为了网页跳转或者是来自本地HTML文件。 目标应用可能会接收“null”源,并且这个可能被测试者(或者攻击者)利用,任何网站很容易使用沙盒iframe来获取”null“源;
origin:null
1)如何监测CORS漏洞 答:可以采用BurpSuite进行测试CORS的origin返回响应头进行判断
(1)设置为启用该请求头:Proxy-Options-Match and Replace
(2)设置过滤请求:Proxy-HTTP history - Filter - Filter by seach term
weiyigeek.top-
对于请求页面响应如下则确认存在该漏洞:
weiyigeek.top-
weiyigeek.top-
补充知识点: 1.CORS漏洞与CSRF漏洞的共同点与不同点?
共同点:都要借助第三方网站,都要借助ajax的异步过程,一般都需要用户登陆。
不同点:利用CORS漏洞读取到受害者的敏感信息,可以利用csrf漏洞可以替受害者完成诸如转账等敏感操作,一般有CORS漏洞的地方都有csrf漏洞;
weiyigeek.top-
2.如果服务器配置下面响应头不能证明漏洞存在,因为浏览器会自动拦截掉非认证域的请求。 1 2 Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true
0x03 CORS 利用 描述:CORS漏洞常常与低风险的反射类型的XSS漏洞联合使用(组合拳打法),来增加其鸡肋漏洞的危害性使之变废为宝;
CORS 常见漏洞点:
1.互联网厂商的api接口;
2.聊天的程序的api接口;
3.app的api <不过有一些请求需要带有一些额外的请求头,利用起来比较困难>;
4.区块链厂商;
案例1:
攻击者拥有一个包含用于跨域交互的恶意脚本的网站:http://127.0.0.1:4567
,受害者即内部网网站的管理员访问攻击者的网站访问后将会触发攻击载荷。
weiyigeek.top-
一旦加载了web页面,就会调用“makeRequest”方法。该方法发起一个跨域请求来捕获该秘密,发送到位于 http://127.0.0.1:80/bwapp/secret-cors-1.php
的脆弱内部网应用程序。进入翻译页面
案例2:CORS 利用过程采用一个案例演示 描述:通过子域名的XSS结合CORS利用
weiyigeek.top-
重点:但是有时候需要注意到referer源 头对CORS的利用影响;
weiyigeek.top-
案例3:利用特殊符号和浏览器的结合去绕过子域名的检查 描述:这个API端点返回用户的私有信息比如全名、电子邮件地址要滥用这种错误配置,以便我们可以执行攻击,比如泄漏用户的私有信息我们需要声明一个废弃的子域(子域接管),或者在现有的子域中找到一个XSS。
这个漏洞的利用条件需要一个XSS或者子域名接管https://banques.redacted.com/choice-quiz?form_banque="><script>alert(document.domain)</script>&form_cartes=73&iframestat=1
1 2 3 4 5 6 7 8 9 10 11 12 13 function cors ( ) {var xhttp = new XMLHttpRequest();xhttp.onreadystatechange = function ( ) { if (this .status == 200 ) { alert(this .responseText); document .getElementById("demo" ).innerHTML = this .responseText; } }; xhttp.open("GET" , "https://www.redacted.com/api/return" , true ); xhttp.withCredentials = true ; xhttp.send(); } cors();
关键点:主要是需要使用了FUZZ去尝试通过特殊字符来绕过验证了您的origin来源的网站,也就是说我们可以通过访问*.target.com.特殊字符.youdomain.com来绕过正则1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 , & ' " ; ! $ ^ * ( ) + = ` ~ - _ = | { } % #最终如下域名可以绕过 *.ubnt.com!.evil.com *.ubnt.com".evil.com *.ubnt.com$.evil.com *.ubnt.com%0b.evil.com *.ubnt.com%60.evil.com *.ubnt.com&.evil.com *.ubnt.com' .evil.com*.ubnt.com(.evil.com *.ubnt.com).evil.com *.ubnt.com*.evil.com *.ubnt.com,.evil.com *.ubnt.com;.evil.com *.ubnt.com=.evil.com *.ubnt.com^.evil.com *.ubnt.com`.evil.com *.ubnt.com{.evil.com *.ubnt.com|.evil.com *.ubnt.com}.evil.com *.ubnt.com~.evil.com
用safari访问一下看支不支持之后注册这个子域名然后就可以利用了 https://zzzz.ubnt.com=.evil.com/cors-poc
0x04 安全防御 1)不要配置“Access-Control-Allow-Origin”为通配符“*”,而且更重要的是,要严格效验来自请求数据包中的“Origin”的值。 当收到跨域请求的时候,要检查“Origin”的值是否是一个可信的源,还要检查是否为null 2)避免使用“Access-Control-Allow-Credentials: true” 3)减少Access-Control-Allow-Methods所允许的方法
问:怎么才能允许多域名跨域访问呢? 由于origin-list-or-null在产品实际中多用来做限制,不是一个空格分隔的origin的列表,而只能是单个origin或字符串”null”。
weiyigeek.top-
解决办法1 结合CORS on Nginx写成Nginx配置片段enable-cors.conf,使用的时候,只需如下这样:1 2 3 4 location / { ... other config ... include enable-cors.conf; }
完整的enable-cors.conf配置片段如下:(能解决很大一部分企业安全问题哟)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 set $ACAO 'http://www.test.com http://user.test.com' ;if ($http_origin ~* ^https?://(www|user)\.test \.com$) { set $ACAO $http_origin ; } if ($request_method = 'OPTIONS' ) { add_header 'Access-Control-Allow-Origin' '$ACAO' ; add_header 'Access-Control-Allow-Credentials' 'true' ; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' ; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' ; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8' ; add_header 'Content-Length' 0; return 204; } if ($request_method = 'POST' ) { add_header 'Access-Control-Allow-Origin' '$ACAO' ; add_header 'Access-Control-Allow-Credentials' 'true' ; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' ; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' ; } if ($request_method = 'GET' ) { add_header 'Access-Control-Allow-Origin' '$ACAO' ; add_header 'Access-Control-Allow-Credentials' 'true' ; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' ; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type' ; }
解决办法2: 设置多个可跨域域名数组通过request的getHeader(“Origin”)获取origin 请求域名属于可跨域域名数组,将所取的orgin值设给Access-Control-Allow-Origin;1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static final String[] ALLOW_DOMAIN = { "http://localhost:8000" ,"http://192.168.0.100" };HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; String originHeader = req.getHeader("Origin" ); if (Arrays.asList(Constants.ALLOW_DOMAIN).contains(originHeader)) { res.setHeader("Access-Control-Allow-Origin" , originHeader); res.setHeader("Allow" , "*" ); res.setHeader("Access-Control-Allow-Methods" , "POST, GET, PUT, DELETE, OPTIONS" ); res.setHeader("Access-Control-Allow-Headers" , "Origin, X-Requested-With, Content-Type, Accept, Referer, User-Agent, Authorization, X-Auth-Token" ); res.setHeader("Access-Control-Max-Age" , "3600" ); res.setHeader("Access-Control-Allow-Credentials" , "true" ); if ("IE" .equals(req.getParameter("type" ))) { ((HttpServletResponse) response).setHeader( "XDomainRequestAllowed" , "1" ); } if (req.getMethod().toLowerCase().equals("options" )) { res.setHeader("Content-type" , "text/html" ); res.getWriter().write("options OK" ); return ; } }
0x05 补充附录 总结
反射XSS和CORS配合成功获取到用户敏感数据,虽然同样需要用户点击才能触发;
附录