CORS处理跨域
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
只要同时满足以下两大条件,就属于简单请求。
(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请求。具体来说,就是在头信息之中,增加一个Origin字段。
下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。
- 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...
上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
- 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
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。
(1)Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。
withCredentials 属性
上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段。
- Access-Control-Allow-Credentials: true
另一方面,开发者必须在AJAX请求中打开withCredentials属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,如果省略withCredentials设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials。
- xhr.withCredentials = false;
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
响应码
http状态返回代码 1xx(临时响应)
表示临时响应并需要请求者继续执行操作的状态代码。
http状态返回代码 代码 说明
100 (继续)请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
101 (切换协议)请求者已要求服务器切换协议,服务器已确认并准备切换。
http状态返回代码 2xx (成功)
表示成功处理了请求的状态代码。
http状态返回代码 代码 说明
200 (成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
201 (已创建) 请求成功并且服务器创建了新的资源。
202 (已接受) 服务器已接受请求,但尚未处理。
203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。
204 (无内容) 服务器成功处理了请求,但没有返回任何内容。
205 (重置内容)服务器成功处理了请求,但没有返回任何内容。
206 (部分内容) 服务器成功处理了部分 GET 请求。
http状态返回代码 3xx (重定向)
表示要完成请求,需要进一步操作。通常,这些状态代码用来重定向。
http状态返回代码 代码 说明
300 (多种选择) 针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
301 (永久移动) 请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
303 (查看其他位置)请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
304 (未修改)自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。
305 (使用代理)请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。
307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
http状态返回代码 4xx(请求错误)
这些状态代码表示请求可能出错,妨碍了服务器的处理。
http状态返回代码 代码 说明
400 (错误请求)服务器不理解请求的语法。
401 (未授权)请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。
403 (禁止)服务器拒绝请求。
404 (未找到)服务器找不到请求的网页。
405 (方法禁用)禁用请求中指定的方法。
406 (不接受)无法使用请求的内容特性响应请求的网页。
407 (需要代理授权)此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
408 (请求超时) 服务器等候请求时发生超时。
409 (冲突) 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。
410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。
411 (需要有效长度)服务器不接受不含有效内容长度标头字段的请求。
412 (未满足前提条件)服务器未满足请求者在请求中设置的其中一个前提条件。
413 (请求实体过大)服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
414 (请求的 URI 过长)请求的 URI(通常为网址)过长,服务器无法处理。
415 (不支持的媒体类型)请求的格式不受请求页面的支持。
416 (请求范围不符合要求)如果页面无法提供请求的范围,则服务器会返回此状态代码。
417 (未满足期望值)服务器未满足"期望"请求标头字段的要求。
http状态返回代码 5xx(服务器错误)
这些状态代码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。
http状态返回代码 代码 说明
500 (服务器内部错误) 服务器遇到错误,无法完成请求。
501 (尚未实施)服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
502 (错误网关)服务器作为网关或代理,从上游服务器收到无效响应。
503 (服务不可用)服务器目前无法使用(由于超载或停机维护)。通常,这只是暂时状态。
504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
505 (HTTP 版本不受支持)服务器不支持请求中所用的 HTTP 协议版本。
一些常见的http状态返回代码为:
200 - 服务器成功返回网页
404 - 请求的网页不存在
503 - 服务不可用
非简单请求
预检请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
下面是一段浏览器的JavaScript脚本。
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。
浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
- 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
- Accept-Language: en-US
- Connection: keep-alive
- User-Agent: Mozilla/5.0...
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
- 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
- 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
上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示 http://api.bob.com 可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
- Access-Control-Allow-Origin: *
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器回应的其他CORS相关字段如下。
- Access-Control-Allow-Methods: GET, POST, PUT
- Access-Control-Allow-Headers: X-Custom-Header
- Access-Control-Allow-Credentials: true
- Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
- 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...
上面头信息的Origin字段是浏览器自动添加的。
下面是服务器正常的回应。
- Access-Control-Allow-Origin: http://api.bob.com
- Content-Type: text/html; charset=utf-8
上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。
分析ajax跨域
抓包请求数据
第一步当然是得知道我们的ajax请求发送了什么数据,接收了什么,做到这一步并不难,也不需要fiddler等工具,仅基于Chrome即可
- Chrome浏览器打开对应发生ajax的页面,F12打开Dev Tools
- 发送ajax请求
- 右侧面板->NetWork->XHR,然后找到刚才的ajax请求,点进去
示例一(正常的ajax请求)
上述请求是一个正确的请求,为了方便,我把每一个头域的意思都表明了,我们可以清晰的看到,接口返回的响应头域中,包括了
Access-Control-Allow-Headers: X-Requested-With,Content-Type,Accept
Access-Control-Allow-Methods: Get,Post,Put,OPTIONS
Access-Control-Allow-Origin: *
所以浏览器接收到响应时,判断的是正确的请求,自然不会报错,成功的拿到了响应数据。
示例二(跨域错误的ajax请求)
为了方便,我们仍然拿上面的错误表现示例举例。
这个请求中,接口Allow里面没有包括OPTIONS,所以请求出现了跨域、
这个请求中,Access-Control-Allow-Origin: *出现了两次,导致了跨域配置没有正确配置,出现了错误。
更多跨域错误基本都是类似的,就是以上三样没有满足(Headers,Allow,Origin),这里不再一一赘述。
示例三(与跨域无关的ajax请求)
当然,也并不是所有的ajax请求错误都与跨域有关,所以请不要混淆,比如以下:
比如这个请求,它的跨域配置没有一点问题,它出错仅仅是因为request的Accept和response的Content-Type不匹配而已。
更多
基本上都是这样去分析一个ajax请求,通过Chrome就可以知道了发送了什么数据,收到了什么数据,然后再一一比对就知道问题何在了。
CORS漏洞利用
同源策略
同源策略SOP是应用程序安全性中的一个重要概念涉及大量的客户端脚本语言例如JavaScript。
SOP规则允许脚本运行在来自同一站点同一源的页面访问方法和其他脚本的属性没有特别限制的页面中同时阻止访问大多数方法和来自不同站点的页面之间的属性不同的来源。
这种机制对于现代Web应用程序尤其重要因为它们广泛依赖HTTP cookie保持经过身份验证的用户会话服务器决定是否向客户端发送基于会话cookie的客户信息的机密信息。
客户端例如浏览器必须保证来自无关网站来源的内容之间的严格分离以防止数据完整性和机密性的丢失。
术语"源"使用以下定义
-域名
-应用协议
-TCP端口
当且仅当前面三个值完全相同时才认为两个资源具有同源基础。
为了更好地解释该概念下表显示了关于URL "http://www.example.com/dir/page.html" 的同源策略控制结果。
安全隐患
cors与csrf
cors与csrf相同点:
都要借助第三方网站
都要借助ajax的异步过程
一般都需要用户登陆
cors与csrf不同点:
第三方网站可以利用CORS漏洞读取到受害者的敏感信息
第三方网站可以利用csrf漏洞可以替受害者完成诸如转账等敏感操作
一般有CORS漏洞的地方都有csrf漏洞
检测漏洞
使用burpsute自动化检测漏洞小技巧
Access-Control-Allow-Origin: Wh0ale.com
常见漏洞点
如果Access-Control-Allow-Origin可控,且Access-Control-Allow-Credentials为true大概率有该漏洞
常见漏洞点:
互联网厂商的api接口
聊天的程序的api接口
app的api(不过有一些请求需要带有一些额外的请求头,利用起来比较困难)
区块链厂商
实战
① 利用某子域的xss绕过Referer的检查
②null源
CORS的规范中还提到了"NULL"源。触发这个源是为了网页跳转或者是来自本地HTML文件。 目标应用可能会接收"null"源,并且这个可能被测试者(或者攻击者)利用,任何网站很容易使用沙盒iframe来获取"null"源
为什么服务器端会有这样的漏洞
- 1.开发人员开发,调试,测试代码一般都在本地
- 2.有时候他们会调用线上服务器数据
- 3.所以这样的问题很隐蔽也很常见
③ poc
该poc上传到自己的服务器,如果一个已经登陆的用户访问我们的网站的话,那么就可以把他的个人信息导入到我的网站里面
- CURL这个指令来测试这个网站
1 | curl https://api.artsy.net -H "Origin: https://evil.com" -I |
- 返回结果
1 | Access-Control-Allow-Credentials: true |
- 发现一个接口可以查看已登录用户的详细信息
1 | https://api.artsy.net/api/user_details/ |
使用geekboy分享的exp来检测,看看能不能导出用户的id,注册日期,邮箱,手机号,用户凭证,重置密码凭证,收藏品,用户设备等用户信息。
geekboy:
1 |
|
new Image().src
1 |
|
CORS EXP跨域测试
1 |
|
1 |
|
使用github脚本
python corser.py -poc GET
python corser.py -poc POST
使用此代码攻击者可以通过接收响应的"日志"处理程序从易受攻击的域名窃取数据。
当受害者在目标应用程序"vulnerability.domain"上进行身份验证时访问包含该页面的页面在前面的代码中浏览器将以下请求发送到"vulnerability.domain"
1 |
|
1 | GET /api/private-data HTTP/1.1 |
应用程序响应如下
1 | HTTP/1.1 200 OK |
由于服务器发送了两个"Access-Control-Allow-*
"标头受害者的浏览器允许使用JavaScript
代码包含在恶意页面中以访问私有数据。
④服务器端高速缓存投毒
另一种潜在的攻击利用CORS错误配置来注入任意HTTP头这可能会保存在服务器端缓存中例如创建存储的XSS。
利用此攻击的先决条件如下
存在服务器端缓存
反射"Origin"标头
在"Origin"标头内没有检查"\ r"之类的非法字符
根据前面的先决条件James Kettle展示了利用HTTP头注入的针对IE / Edge受害者的漏洞的可能性。因为他们使用"\ r \ n"0x0d作为有效的HTTP标头终结符。
请求
1
2GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7IE解析的响应
1
2
3HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7前面的请求不能直接利用因为攻击者无法让受害者成为受害者浏览器发送前面的格式错误的标头。
如果攻击者使用格式错误的"Origin"标头发送前一个请求例如使用代理或命令行然后服务器可以缓存响应并将其提供给其他人。
在前面的示例中有效负载将页面的字符集更改为"UTF-7"众所周知这是对创建XSS漏洞很有用。
⑤客户端高速缓存中毒
此配置还可能允许攻击者利用其他可能不存在的漏洞利用。
例如考虑一个在响应内部反映"X-User
"自定义内容的应用程序标题不对其进行任何输入验证也不进行输出编码。
请求
1 | GET /login HTTP/1.1 |
响应请注意"Access-Control-Allow-Origin
"已设置但"Access-Control-Allow-Credentialstrue
"和"VaryOrigin
"不存在
1 | HTTP/1.1 200 OK |
攻击者可以利用此XSS在受控服务器上上载以下JavaScript代码然后诱使受害者在其上导航
1 | var req = new XMLHttpRequest(); |
如果未在响应中设置"VaryOrigin
"标头如前面的示例所示受害者的浏览器可以将响应存储在缓存中基于浏览器行为然后直接显示它。
当浏览器导航到相关的URL时这可以通过重定向立即完成如在"reqListener
"方法中显示。
⑥jsonp跨域
poc
1 | <!-- 引用一段如上请求为JS --> |
使用正常的账号(绑定过手机号)来测试下:
绕过
- 目标存在可控的子域名
现在假设目标应用程序实现以下正则表达式以验证"Origin
"标头
1 | ^https?:\/\/(.*\.)?target\.local$ |
它允许从"target.local
"和任何子域来自HTTP或HTTPS协议进行跨域访问。
在此如果攻击者能够控制目标的有效子域例如"subdomain.target.local
"
利用子域名接管的示例或者如果存在XSS漏洞攻击者可以使用它生成有效的CORS请求。
- 第三方域名
有时必须允许第三方域名发出请求。如果攻击者能够在这些域名上上传JavaScript脚本他们可以进行攻击。
Amazon S3存储服务有时允许这种情况发生。 如果是目标应用程序使用Amazon服务时可能允许S3存储服务发出请求。
在这种情况下攻击者可以在受控的S3存储服务器上托管恶意页面。
防御
1.不要配置"Access-Control-Allow-Origin"为通配符"*",而且更重要的是,要严格效验来自请求数据包中的"Origin"的值。 当收到跨域请求的时候,要检查"Origin"的值是否是一个可信的源,还要检查是否为null
2.避免使用"Access-Control-Allow-Credentials: true"
3.减少Access-Control-Allow-Methods所允许的方法
提示:
在tomcat的error-page中,只要配置了403错误,会导致OPTIONS方法请求,出现Allow:PUT、DELETE、OPTIONS、TRACE。这些HTTP方法,通过实际验证,还是被禁用阻断的
原本是为了屏蔽 403的错误信息(中间件版本信息泄露(中)),于是按照网上的方法配置error-page ,但是扫描发现又多了 开启了不安全的HTTP方法
之前在conf/web.xml 已经配置过这些方法禁用的;
参考:
圈子: https://www.secquan.org/Discuss/1068061
聂心明:https://blog.csdn.net/niexinming
Youtube视频:https://www.youtube.com/watch?v=mkv8aoJzGSo&feature=youtu.be
geekboy:http://www.geekboy.ninja/blog/