前端跨域安全

零、前言

在Web安全中有一条很重要的同源策略,规定了前端安全的基本原则。前端开发中为了能够在不同页面中进行数据传递,设计了多种跨域的数据传递方式,但是数据跨域传递在方便了开发的同时也带来了一些安全问题。

注:本文已首发合天,禁止转载!

一、同源策略

1.1 定义

同源是一种约定,它定义了从一个源加载的文档或脚本如何与来自另一个源的资源进行交互,是一个用于隔离潜在恶意文件的重要安全机制,也是浏览器最核心最基本的安全功能。

1.2 源的划分

如果两个页面的协议、端口、域都相同,则两个页面具有相同的源。如下表所示:

注意:源的划分中“域“并不是指资源对象所在的域,而是加载资源对象的域。例如a.com通过通过以下代码

<script src=http://b.com/b.js></script>

加载了b.com上的b.js,但是b.js是运行在a.com上的,所以b.js的源是a.com而不是b.com。

二、window.name

Windows对象是浏览器的窗体,很多时候它不受同源策略的限制,利用这个对象可以实现跨域跨页面传递数据。其中,Windows的name属性是在一个窗体的生命周期内所有页面所共享的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。比如如下代码保存为list.html

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
window.name = "window's name";
alert(window.name);
setTimeout(function(){
    window.location.href = "http://127.0.0.1/test.html";
},1000)
</script>
</body>
</html>

打开它之后程序会弹出window.name的值,然后跳转到http://127.0.0.1/test.html,

在test.html中的代码为

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
alert(window.name);
</script>
</body>
</html>

仅仅是一个弹窗,弹出来的window.name值域上一个页面相同。

这说明window.name成功的从一个域传递到了另外一个域。这个地方怎么进行漏洞利用呢?其中一个思路就是借助这个特性缩短XSS Payload的长度辅助XSS进行攻击。比如在一个可控的页面先构造好XSS Payload,如下所示

<script type="text/javascript">
window.name = "alert(document.cookie)";
location.href = "http://www.xssedsite.com/xssed.php";
</script>

那么在XSS的漏洞站点只需执行以下代码即可

Eval(name);

极其简短。

三、postMessage

postMessage允许每一个Window(包括当前窗口、弹出窗口、iframe等)对象往其他的窗口发送文本消息,从而实现跨窗口消息传递,且这个功能不受同源策略影响。发送窗口

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
window.onload = function() {  
    var ifr = window.open("http://127.0.0.1/test.html",'myWindow');
    var targetOrigin = "http://127.0.0.1/test.html";  
    ifr.contentWindow.postMessage('hello world!', targetOrigin);  
};
</script>
</body>
</html>

监听窗口

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
window.addEventListener('message',function(event) {
  if(event.origin !== 'http://127.0.0.1') return;
  alert(event.data);
},false);
</script>
</body>
</html>

发送窗口负责发送消息,监听窗口绑定message时间,监听其他窗口的消息,这里。这里的安全问题对于监听窗口来说的:

  • 始终使用origin和source属性验证发件人的身份
  • 要对接受到的message进行检查

对发送窗口来说,发送的目标地址要提供一个有确切值的targetOrigin,而不是*,不提供确切的目标将导致数据泄露可能。

四、Flash跨域

4.1 简介

flash在跨域时唯一的限制策略就是crossdomain.xml文件,该文件限制了flash是否可以跨域读写数据以及允许从什么地方跨域读写数据。位于www.a.com域中的SWF文件要访问www.b.com的文件时,SWF首先会检查www.b.com服务器目录下是否有crossdomain.xml文件,如果没有,则访问不成功;若crossdomain.xml文件存在,且里边设置了允许www.a.com域访问,那么通信正常。所以要使Flash可以跨域传输数据,其关键就是crossdomain.xml。

4.2 位置

自flash 10以后,如有跨域访问需求,必须在目标域的根目录下放置crossdomain.xml文件,且该根目录下的配置文件称为“主策略文件”。若不存在主策略文件,则该域将禁止任何第三方域的flash跨域请求。主策略文件对全站的跨域访问起控制作用。也可以单独在某路径下放置仅对该路径及其子路径生效的crossdomain.xml配置文件,这需要在flash的AS脚本中使用如下语句来加载该配置文件:

Security.loadPolicyFile(“http://www.xxx.com/subdir/crossdomain.xml”)

4.3 配置

cross-domain-policy

cross-domain-policycross-domain-policy元素是跨域策略文件crossdomain.xml的根元素。它只是一个策略定义的容器,没有自己的属性。子元素有:

  • site-control
  • allow-access-from
  • allow-access-from-identity
  • allow-http-request-headers-from

site-control

通过检查该节点的属性值,确认是否可以允许加载其他策略文件(如果该策略文件并非主策略文件,则此节点被自动忽略)。每个site-control标签有且仅有属性permitted-cross-domain-policies,该属性指定相对于非主策略文件的其他策略文件的加载策略。permitted-cross-domain-policies属性值有如下情况:

  • none: 不允许使用loadPolicyFile方法加载任何策略文件,包括此主策略文件。
  • master-only: 只允许使用主策略文件(默认值)。
  • by-content-type:只允许使用loadPolicyFile方法加载HTTP/HTTPS协议下Content-Type 为text/x-cross-domain-policy的文件作为跨域策略文件。
  • by-ftp-filename:只允许使用loadPolicyFile方法加载FTP协议下文件名为      xml的文件作为跨域策略文件。
  • all: 可使用loadPolicyFile方法加载目标域上的任何文件作为跨域策略文件,甚至是一 个JPG也可被加载为策略文件!

使用all属性极其危险,因为可以上传一个恶意crossdomain.xml然后再通过loadPolicyFile加载它,导致原有的crossdomain策略失效。

allow-access-from

通过检查该节点的属性值,确认能够读取本域内容的flash文件来源域。allow-access-from标签有三个属性:

  • domain:该属性指定一个确切的 IP 地址、一个确切的域或一个通配符域(任何域)。只有domain中指定的域,才有权限通过flash读取本域中的内容。可采用下列两种方式之一来表示通配符域:
    • 1)单个星号(*),如:<allow-access-fromdomain=”*” />,表示匹配所有域和所有IP地址,此时任何域均可跨域访问本域上的内容。
    • 2)后接后缀的星号,表示只匹配那些以指定后缀结尾的域,如*.qq.com可匹配qq.com、qq.com。形如www.q*.com或www.qq.*的为无效配置。
    • Tips:当domain被指定为IP地址时,只接受使用该IP作为网址来访问的来源请求(此时ip地址也就相当于一个域名而已),如domain被设置为168.1.100时,使用http://192.168.1.100/flash.swf 来请求该域内容是允许的,但是使用指向192.168.1.100的域名www.a.com来访问时(http://www.a.com/flash.swf)将会被拒绝,因为flash不懂得dns解析。
  • to-ports:该属性值表明允许访问读取本域内容的socket连接端口范围。可使用to-ports=”1100,1120-1125″这样的形式来限定端口范围,也可使用通配符(*)表示允许所有端口。
  • secure:该属性值指明信息是否经加密传输。当xml文件使用https加载时,secure默认设为true。此时将不允许flash传输非https加密内容。若手工设置为false则允许flash传输非https加密内容。

allow-access-from-identity

该节点配置跨域访问策略为允许有特定证书的来源跨域访问本域上的资源。也就是说allow-access-from看源;而allow-access-from-identity看证书(加密凭证)。

allow-http-request-headers-from

此节点授权第三方域flash向本域发送用户定义的http头。allow-access-from节点授权第三域提取本域中的数据,而 allow-http-request-headers-from 节点授权第三方域将数据以http头的形式发送到本域中。(简而言之,allow-access-from是控制读取权限,allow-http-request-headers-from是控制以http头形式的写入权限)包含三个属性

  • domain:作用及参数格式与allow-access-from节点中的domain类似。
  • headers:以逗号隔开的列表,表明允许发送的http头。可用通配符(*)表示全部http头。
  • secure:作用及用法与allow-access-from节点中的secure相同。

4.4 漏洞利用

Flash跨域进行信息读取能否利用成功,主要还是看crossdomain.xml文件的配置,例如网易某站的crossdomain.xml文件

这样就可以进行跨域进行信息读取,我们利用github上的项目:

https://github.com/nccgroup/CrossSiteContentHijacking

来进行漏洞利用

输入target点击Retrieve Contents在下方可以看到成功读取到个人信息页面的内容。

五、CORS

5.1 简介

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

5.2 简单请求

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。若请求满足所有下述条件,则该请求可视为“简单请求”:

使用下列HTTP方法之一:

  • GET
  • HEAD
  • POST

HTTP头信息不能超过以下几种字段类型:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

其中Content-Type的值只能是以下三种:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

前面已经说过这些跨域请求与正常的请求在代码上没有太多的区别,浏览器会帮助我们完成CORS的检验过程,示例如下

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
  var XMLH = new XMLHttpRequest();
  var url = 'http://www.xmanblog.net/';
     
  function callOtherDomain() {
    if(XMLH) {    
      XMLH.open('GET', url, true);
      XMLH.send(); 
    }
  }
  callOtherDomain();
</script>
</body>
</html>

上述代码执行之后会跨域访问我的blog,这时浏览器会自动在http的头中加上Origin字段,表明请求的来源。

当返回包中携带了Access-Control-Allow-Origin字段,就表示完成了一次CORS访问控制。

本例中,服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://127.0.0.1 的访问,该首部字段的内容如下:

Access-Control-Allow-Origin: http://127.0.0.1

5.3 非简单请求

非简单请求使用了不同的HTTP方法与参数,并且非简单请求会在正式通信之前用OPTIONS方法发起一个“预检请求”,该“预检请求”会询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP方法和头信息字段。只有得到肯定答复,浏览器才会发出正式的请求,否则就报错。当请求满足下述任一条件时,即应首先发送“预检请求”:

使用下列HTTP方法之一:

  • PUT
  • DELETE
  • CONNECT
  • OPTIONS
  • TRACE
  • PATCH

HTTP头信息超过了以下几种字段类型:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type
  • DPR
  • Downlink
  • Save-Data
  • Viewport-Width
  • Width

其中Content-Type的值以下三种之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

如下代码会首先发起一个“预检请求”:

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
var XMLH = new XMLHttpRequest();
var url = 'http://www.xmanblog.net/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
    
function callOtherDomain(){
  if(XMLH)
    {
      XMLH.open('POST', url, true);
      XMLH.setRequestHeader('X-PINGOTHER', 'pingpong');
      XMLH.setRequestHeader('Content-Type', 'application/xml');
      XMLH.send(body); 
    }
}
callOtherDomain();
</script>
</body>
</html>

上面的代码使用 POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-PINGOTHER: pingpong)。另外,该请求的 Content-Type 为 application/xml。因此,该请求需要首先发起“预检请求”。

返回结果

浏览器会根据返回结果决定是否进行正式通信,上述数据包表明服务器允许任意的源通过POST、GET、OPTIONS方法携带X-PINGOTHER 与 Content-Type头字段进行通信,响应的有效时间为 86400 秒,也就是 24 小时,如下是正式的通信数据包

3.4 身份凭证

CORS请求默认不发送Cookie和HTTP认证信息,想要发送身份凭证跨源请求中必须打开withCredentials属性,如下所示

<!DOCTYPE html>
<html>
<head>
  <title>XMLHttpRequest Test</title>
</head>
<body>
<script type="text/javascript">
var XMLH = new XMLHttpRequest();
var url = 'http://www.xmanblog.net/';
    
function callOtherDomain(){
  if(XMLH) {
    XMLH.open('GET', url, true);
    XMLH.withCredentials = true;
    XMLH.send(); 
  }
}
callOtherDomain();
</script>
</body>
</html>

上述代码的XMLHttpRequest请求中在第14行打开了withCredentials属性,从而浏览器会向服务器发送cookie。

但是想要完成整个通信还需要服务器在头信息中返回:

Access-Control-Allow-Credentials: true

以及Access-Control-Allow-Origin的值不得为“*”,必须指定明确的、与请求网页一致的域名,否则浏览器会拒绝返回的数据,比如我们可以在burp的返回包中看到服务器返回的数据,但是浏览器却没有任何显示

5.5 漏洞利用

实际上,由于CORS的“身份凭证”的特性,使得很难绕过CORS进行跨域资源读取。想要通过CORS获取到敏感信息需要像CSRF那样诱导用户去访问构造的恶意页面,然后页面中的js代码发起跨域请求获取信息,再将信息发送到攻击者服务器,可以称之为读取型的CSRF。但是这里需要满足CORS的几个身份凭证的特性,否则只能获取一些不需要身份凭证的价值不大的信息。

我们利用github上的项目:

https://github.com/nccgroup/CrossSiteContentHijacking

来进行漏洞利用

Type选择CORS Windows,Target Page选择想要获取信息的目标链接,如果需要发送POST数据在POST Data填上数据即可,然后点击Retrieve Contents,页面会跳转到/ContentHijacking.html,可以看到跨域获取的信息

注意头信息需要返回这两个字段才能利用成功

不过也不是完全这么鸡肋,有些资源内网资源,不需要携带身份凭证但是外网不可访问就可以利用这个方法尝试读取;另外一旦完成跨域访问即可获取页面源代码,也就可以获取页面中的Token,那么CSRF的防御也就丧失了。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*