浏览器跨域详解-很多人没有提到的坑

背景

最近公司组织了一场网络攻防演练,CSRF(跨站请求伪造攻击),XSS(跨站脚本攻击),SQL注入,cookie拦截修改,各种高大上的名词。最近专注于后台业务,前端知识都变得很模糊,在页面的提示下算是踉踉跄跄做完了。但做完还是一脸懵逼,为什么会存在这些漏洞?这些漏洞的根源在哪里?应对策略是什么?我想把他们全整个明白,特别是CSRF这东西,真的是神奇,就搜了几篇博客,看完又做亲自实验了一下,发现很多博客里并没有提到一个隐藏的比较深的坑,故记录下来供各位兄弟参考。

为什么说专注于后台会导致我对这些东西不甚理解?因为我仔细捋了一下,除了SQL注入是后台直接采用前端参数拼接SQL导致的,其实大部分漏洞都是前端和浏览器导致的。

PS: 其实在我的知识体系里,我是把后台直接与前端打交道的web应用也归为前端应用的。现在大部分网站都是采用前后端分离的架构,也就是所谓的微服务架构(这里不做扩展,有兴趣可以自己研究一下)。我现在手上的项目是分为前,中,后台(严格来说,我所在的部门是一个中台部门,我们只负责对外提供服务接口,从整个公司的层面来看,我所说的前,中,后台整个是一个中台项目)。前台都是一些controller负责分发http请求,中台服务层专注于业务逻辑的实现,一般使用分布式服务框架dubbo,Spring cloud等提供给前端调用。在这样的架构中,前台的controller中的一个个方法就称为一个个微服务。

这样的架构会带来什么问题?

1. 后台的服务接口域名可能和前端H5域名是完全不同的两个域名,那么在H5中访问中台服务接口就会出现跨域问题。

原谅我能想到的问题就这一个(逃~~)。真的是因为,微服务架构带来的利是远远大于弊的,高伸缩性,高可扩展性,高容错性等等等等不是这一篇文章能讲清楚的。


网络域和浏览器同源策略

广义上的域大概是单指一个网站域名,但在专业领域,我们把它叫源,所谓同源是指:

1.协议相同

2.域名相同

3.端口相同

三者同时成立才能叫同源。浏览器的同源策略从它诞生的那一刻就出现了,具体是指:

从域名A下的一个页面(一般是通过ajax请求)获取域名B下的一个资源,是不被浏览器允许的。

这句话需要注意四点:

1. A和B不同源

2. 同源策略是浏览器做的限制

3. 必须得是H5页面中发出的请求,因为是浏览器做的限制,这点也很好理解了,页面是在浏览器中显示,它发出的请求自然是要通过浏览器代为执行的,所以只要你的请求经过了浏览器这一关,是很容易通过浏览器做手脚的。

PS: 所以一开始我想,如果能开发出一个独立的网络请求工具(自己封装和解析http协议),通过脚本直接调用发送网络请求而不经过浏览器,是不是就可以绕过这个限制?但是转念一想,脚本本身就是要通过浏览器来解释执行的啊喂!!!但是又一转念,脚本本身通过浏览器解释执行跟能不能通过脚本调用自己的网络请求工具有矛盾吗?浏览器又不知道我要调用的工具是要发送请求的,除非他能像wireshark那样神通广大拦截所有请求(不过也有它拦截不到的报文,猜猜是发给谁的报文~~),但是这样做是很不讲道理的。哎,越想越多停不下来了,还是打住吧,我又不懂浏览器编程,鬼知道它还会有什么别的稀奇古怪的限制......

4. 不被允许的意思是,浏览器还是会发出这个请求,但是它会拦截响应内容,如果发现响应header中"Access-Control-Allow-Origin"设置的允许访问的源没有包含当前源,则拒绝将数据返回给当前源。(这就是很多博客中没有提到的一个坑)

另:页面中的一些标签是不做同源限制的,比如<img> <script> <style>等标签,这些标签里的src地址可以与当前页不同源


绕过同源策略的四种方案

根据以上分析,我们可以得出,H5页面绕过同源策略有如下几种办法:

1. 服务器端做手脚,在响应头header中添加"Access-Control-Allow-Origin",指定允许访问的源。(强烈推荐)

2. 利用浏览器对<script>的法外开恩,通过把不同源的请求伪装成一个脚本请求,服务器端返回数据后,脚本会自动回调,这就是所谓的JSONP跨域。(个人不太喜欢这种,但它可能是最流行的一种方式,因为它被绝大部分浏览器支持)这种方式的缺点很明显,只适用GET方式请求。

3. document.domain。不过这种只能适用于不同源页面的访问,像微服务架构这种,服务接口是独立的,所以你没法在页面上设置document.domain。

4. 服务器代理。分为正向代理和反向代理

正向代理:与你页面同源的服务器代你向不同源的服务器去请求数据然后转发给你,前端是向同源的服务器请求,这样就不会受同源策略的限制。

反向代理:反向代理是使用ngix地址映射。具体做法是,将你要请求的不同源的服务接口映射到同源的一个地址,然后你是向这个同源的地址请求数据。举个梨子:

源A上的一个页面:http://boy.com/index

源B上的一个资源:http://girl.com/accept

源A上的这个页面要请求源B上的这个资源,直接请求肯定是不行的,所以我们在源A的服务器上虚拟出这样一个服务地址,假装我们提供这样一个服务,http://boy.com/expresslove,

但是其实我们偷偷的把这个地址映射到 http://girl.com/accept上面去了

这时候,源A的页面是请求 http://boy.com/expresslove 这个服务,源A服务器实际是向 http://girl.com/accept 接口请求,然后把数据返回给前端。

这么一看,其实正向代理和反向代理不特么是一回事吗,都是让同源的服务器代为请求。搞不懂有些人非要变着花样的造名词,哎,世界好复杂~~

我能想到的浏览器跨域的方案大概就是这么多了。


同源策略的实验证明

首先写一个简单的H5页面test.htm

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>TEST CSRF</title>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script type="text/javascript">
var url="http://localhost:7001/account/login";
$.ajax({
    url:url,
    type:'POST',
    data:'userId=root&password=root',
    success:function (data) {
        alert(data);
    },
    error:function (xhr,status,error) {
        alert(status);
        alert(error);
    }
});
</script>
</head>
<body>
</body>
</html>
把这个页面放在tomcat上,启动tomcat,服务端口设为8080,使页面源与请求的资源源不同源。

http://localhost:7001/account/login是我本地的一个Spring cloud项目提供的服务接口。

@PostMapping("/account/login")
@ResponseBody
public ResultModel<String> login(
      HttpServletResponse response,
      HttpServletRequest request,
      @RequestParam String userId,
      @RequestParam String password ){
   //response.setHeader("Access-Control-Allow-Origin","*");
   return this.accountService.login(response, userId, password);
}
首先把response.setHeader("Access-Control-Allow-Origin","*");  注掉

两个应用同时启动,浏览器打开http://localhost:8080/test.htm,结果如下:

浏览器F12,控制台输出如下:



为了证明浏览器确实发出请求了,并且接收到数据了,用fidller抓包来分析一下,抓包结果如下:

请求报文


响应报文


真相大白,浏览器确实发出请求,并且接收到数据了。但是因为响应头没有"Access-Control-Allow-Origin",所以拒绝把数据返回给前端。

响应头



我们把注释解开,再请求一下,结果如下:


现在响应头多了一行,再看H5前端,成功拿到数据



以上,就是一场CSRF安全演练引发的血案。(突然发现全篇跟CSRF似乎只有半毛钱关系,以后有时间再单独写这个吧~~)可见,一个知识点可以引出非常多的关联知识,特别是在计算机这个及其庞大的领域,就看你愿不愿意不求甚解了。


后记: 很久没有写博客了,一直在积累知识点,都说厚积薄发,没有足够的知识积累强行ZB,是害己误人,现在看来,以前写的东西都如狗屎一般。但积累多了,我发现一个更严重的问题,就是光学习,不思考,不总结,会陷入一个永远出不来的死循环。你在不停的学习,却永远都觉得学不完,学不够,惯性会把你一步步拖向深渊。我之前就是这样一种状态,今天写完这篇博客,我觉得是时候改变了。其实这篇博客里面涉及到的很多东西以前都见过,也用过,当时只是纯粹把它们当作一个个独立的问题去解决,没有思考它们内部的原理和联系。这次把这些知识点都串起来了,一下子感觉豁然开朗。忽然想起了考研时候背的马原,事物是普遍联系的,事物是永恒发展的,真乃万年不变的真理啊。所以以后的学习思路应该是,不要孤立的看问题,要善于总结知识之间的联系,在大脑中构建一个庞大的知识网络。

没有更多推荐了,返回首页