前端跨域安全

JavaScript/前端
308
0
0
2023-07-28
标签   跨域请求

前言

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

同源策略

1.1 定义

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

1.2 源的划分

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

注意:源的划分中“域“并不是指资源对象所在的域,而是加载资源对象的域。

例如a.com通过通过以下代码

加载了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

打开它之后程序会弹出window.name的值,然后跳转到,

在test.html中的代码为

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

这说明window.name成功的从一个域传递到了另外一个域。这个地方怎么进行漏洞利用呢?其中一个思路就是借助这个特性缩短XSSPayload的长度辅助XSS进行攻击。

比如在一个可控的页面先构造好XSSPayload,如下所示

<script type=”text/javascript”>
window.name = “alert(document.cookie)”;
location.href = “”;
</script>

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

Eval(name);

极其简短。window.name的跨域本质上一种浏览器的特性,并不是安全漏洞,只是如果使用不当可能会有安全问题。在防御上,当在一个域去接收另一个域的window.name的值的时候要对其进行检测,此时它相当于一个输入点,而开发中要坚信任何输入都是不可信的原则。

post message

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

postMessage语法如下:

otherWindow.postMessage(message,targetOrigin, [transfer]);

  • otherWindow:其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象。
  • message:将要发送到其他 window的数据。
  • targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串”*”(表示无限制)或者一个URI。
  • transfer :是一串和message同时传递的Transferable对象

发送窗口

监听窗口(也就是test.html所在的页面)

发送窗口负责发送消息,监听窗口绑定message事件,监听其他窗口的消息。这里存在两个安全问题,一是对于监听窗口来说,监听窗口作为数据的接收方会将数据进行二次处理或显示,如果直接使用获得数据,就会如同SQL语句之前没有过滤导致注入一样,会导致一些不可控的后果(比如XSS),所以监听窗口在防御上至少要做到以下两点:

  1. 始终使用origin和source属性验证发件人的身份,防止攻击者伪造发送源发送恶意数据;
  2. 要对接受到的message进行检查,即使是许可的源也有可能发送错误或恶意数据的,在使用之前要检查其合法性。

二是对发送窗口来说,我们知道在发送数据之前需要声明一个window对象(在发送窗口的代码截图中可以看到),它指定了我们的数据发送到哪一个窗口,这里很多人认为既然已经指定了窗口,targetOrigin就可以不用设置。但实际上这里存在一个问题,比如当window所指定的窗口因为某种原因发生了跳转,前往了其他链接,如果不指定targetOrigin数据依然会被发送,这样就可能导致信息泄露。所以发送窗口在防御上要设置一个确切的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 位置

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

Security.loadPolicyFile(“”)

4.3 配置

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

  • site-control(确认是否可以允许加载其他策略文件)
  • allow-access-from(确认能够读取本域内容的flash文件来源域)
  • allow-access-from-identity(有特定证书的来源跨域访问本域上的资源)
  • allow-http-request-headers-from(授权第三方域flash向本域发送用户定义的http头)

4.4 漏洞利用

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

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

来进行漏洞利用,将源代码下载下来进行本地部署,类型选择Flash。(这里注意浏览器要支持Flash播放)

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

这个工具只能证明漏洞存在,真正的利用情境需要重新构造特定的恶意页面。就像 CSRF 需要构造一个页面诱导用户点击一样,Flash跨域也需要构造类似的情景,不过传统的CSRF是写入型的攻击,而利用Flash跨域可以进行读取操作(为什么一定要和CSRF对比呢?因为CSRF的利用是需要用户的登陆凭证的,用户在登陆状态下去访问恶意页面才能执行指定的操作,不过CSRF一般都是通过表单提交数据包,浏览器自动携带cookie,因为js是无法跨域的,这也是为什么CSRF只能进行写入操作。Flash跨域有着同样的道理,也需要用户的登陆凭证(Cookie)才能访问敏感页面,其中的不同点在于Flash脚本可以操作cookie发送表单,进而可以执行读取操作。以上工具跨域获得的数据是在测试账号已经登陆的状态下显示的数据,本身模拟了一个受害者在登陆状态下访问恶意页面的情景!)。其根本原因在于crossdomain.xml允许任意源的Flash发起请求,而Flash请求会携带Cookie,这样就可以在用户不知情的情景下利用用户的登陆凭证访问特定的敏感页面,再通过Flash脚本中的代码将获取的敏感数据发送的攻击者的服务器中。简单的一句话就是可以将Flash跨域理解成读取型CSRF。在防御上其实很简单,在设置crossdomain.xml文件的时候仅配置信任的源,一定不要使用domain=”*”这样的写法

CORS

5.1 简介

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-originresourcesharing)。CORS需要浏览器和服务器同时支持,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

5.2 简单请求

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

使用下列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的检验过程,示例如下

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

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

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

Access-Control-Allow-Origin:

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

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

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

返回结果

浏览器会根据返回结果决定是否进行正式通信,上述数据包表明服务器允许任意的源通过

POST、GET、OPTIONS方法携带X-PINGOTHER与Content-Type头字段进行通信,响应的有效时间为86400秒,也就是24小时,如下是正式的通信数据包

5.4 身份凭证

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

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

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

Access-Control-Allow-Credentials:true

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

身份凭证是CORS的跨域的条件之一,withCredentials打开后会降低攻击者的攻击成本。

5.5 漏洞利用

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

同样利用github上的项目:

来进行漏洞利用

Type选择CORSWindows,TargetPage选择想要获取信息的目标链接(我自己的Blog),如果需要发送POST数据在POSTData填上数据即可,然后点击RetrieveContents,页面会跳转到/ContentHijacking.html,可以看到跨域获取的信息

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

不过也不是完全这么鸡肋,有些内网资源,不需要携带身份凭证但是外网不可访问就可以利用这个方法尝试读取。这里举个例子,比如某企业内部搭建了一个私有的git服务器(不需要登陆),开发人员在上面进行代码管理。即使攻击者获取了git服务器的地址也无法访问,如果这时存在CORS漏洞,攻击者就可以构造特定的恶意页面,诱导企业人员去访问,企业人员会从内网发起这个请求并把结果返回到恶意页面上,再通过恶意页面的代码将数据发送到攻击者手中。

另外我们知道再CSRF的防御中在敏感页面设置Token(一个随机序列,跟随数据包发送,防止攻击者猜测数据包请求参数,从而无法进行CSRF发攻击)是常用的方法,一旦完成跨域访问即可获取页面源代码,那么这个随机序列Token就可以被我们获取了,从而可以构造CSRF所需要的请求参数,防御也就丧失了。

在防御上,数据接口在配置返回的头信息时要指定可信的Access-Control-Allow-Origin源,而不要设置成*,例如php中这样的写法是不安全的:

header(“Access-Control-Allow-Origin:*”);

总结

跨域即是前端开发的一种诉求也是也是前端防御的一个要点,除了以上的几种较为典型的跨域方法外还有其他的技巧,大家可以自己补充。它就像XSS、CSRF、钓鱼等前端攻击一样,主要目标是正常用户而不是服务器。跨域在攻击中造成的危害并不像SQl注入、命令执行那样巨大,其防御也不复杂,只需要在使用的时候采用了正确的方法即可避免这些问题。具体的点主要是:

  1. 配置文件:尽量使用白名单的思路设置允许跨域的源
  2. 代价编写:不要为了方便开发简化参数,严格按照说明文档编写
  3. 数据输入点进行检测
  4. 最小特权原则

但是并不能忽视这类安全问题,毕竟任何一个突破口都有撕开整条安全防线的可能。