WEB安全

DDOS导致Nginx 499状态码成因分析

Nginx 499状态码的成因分析

27 Apr 2017

Reading time ~1 minute

工作时遇到一个疑似被DDOS的情况,查看网站日志发现Nginx有大量的499状态码,但是正常用浏览器访问网站却能访问,没有任何问题。所以搜索了一些资料,找到了原因并复现了该现象。

猜想

499 状态码是 Nginx 自己定义的特殊返回码,查看资料,Nginx源码中是如下规定的:

字面上的意思是客户端关闭了连接导致的。我的理解是客户端在发送HTTP请求后,不接收返回包,也不等服务器响应,立即关闭HTTP通道,或者强行关闭tcp连接。这样就会导致服务器在想发送response包给客户端时,找不到客户端的连接,所以提示499的状态码。

测试

针对以上的猜想,我对我自己的博客网站做了测试。用sock和httplib分别写了两段代码如下:

sock:

httplib:

第一段sock的代码全用的多进程,第二段httplib的用多进程和协程结合,然而这两种方法对于我的博客并发4-5千没有任何影响,没有一个499状态码出现。按照网络上文章的解释,这里我猜测是无论是sock还是http,在关闭请求时都没有关闭TCP连接,所以导致了客户端其实没有断开连接,所以服务端依然返回200。所以抓包分析了一下在做sock请求时的代码:

客户端: 

服务端:

服务端的图就不截了,基本就是客户端的反向,基本没有差别。

从TCP流分析,首先开始是TCP三次握手,然后服务端向客户端发送HTTP请求,发送完HTTP请求之后,立刻发送FIN包,请求关闭TCP连接,开始四次挥手的流程。差不多60ms之后,客户端收到了服务端的发来的ACK包,代表服务端进入CLOSE-WAIT状态,此时TCP四次握手并没有完全关闭,服务端依然会将HTTP请求的数据发送给客户端。所以我们看到了NO.504包和NO.561包,length均为1514,这个包就是服务端把HTTP的返回包发给客户端的包,但是客户端接收到这个包之后,由于客户端本身已经关闭连接不接收数据,所以就会发RST包给服务端,代表接收到了异常数据,连接异常关闭。这也就导致了TCP四次挥手没有全部做完,而是异常关闭的。附一张TCP的连接图如下:

回到我们的问题上,我们的猜想也可以合理解释了: 即使我们的SOCK连接是发送了http请求后立马就请求关闭TCP连接,但是TCP四次挥手需要时间,在TCP挥手结束之前,服务端就把数据传输了过来,所以返回包依然是200。

二次测试

这个时候我们还无法验证我们的猜想是否正确,所以我找了一个PHP的站点,又一次进行了上述实验,在Nginx代理PHP的环境下,上述两个测试脚本均造成了大量499状态码的出现。

抓下包,截图如下: 

这是跑第一个sock脚本的结果,这个TCP流就简单多了,先三次握手,再发送HTTP请求包,再四次挥手,可以看到在此时服务端并没有返回数据给客户端,也就是说明服务端在TCP连接关闭之前,没有生成完网页内容并把内容返回给客户端。此时再看一下服务端的状态,Nginx日志中出现了大量499状态。

结论分析

经过两次测试,初步可以得出以下结论:

  1. 499状态码产生的原因是在服务端准备好返回内容并发送之前,客户端已经关闭了和服务端的 TCP 连接。而之所以我的博客不会产生是因为静态页面,Nginx处理的很快,而Nginx主要用作反向代理,准备返回包的内容肯定会有一定的延迟,所以499状态码的出现屡见不鲜。
  2. 经抓包分析,python的httplib模块,在发送完HTTP请求,接受完数据后就会主动关闭TCP连接,下次请求时会再新建一个TCP连接,而不是一直保持一个HTTP长连接不断。
  3. 网上流传的解决措施: proxy_ignore_client_abort on; ,经测试没有任何效果。

Nginx 499状态码的成因分析

27 Apr 2017

Reading time ~1 minute

工作时遇到一个疑似被DDOS的情况,查看网站日志发现Nginx有大量的499状态码,但是正常用浏览器访问网站却能访问,没有任何问题。所以搜索了一些资料,找到了原因并复现了该现象。

猜想

499 状态码是 Nginx 自己定义的特殊返回码,查看资料,Nginx源码中是如下规定的:

字面上的意思是客户端关闭了连接导致的。我的理解是客户端在发送HTTP请求后,不接收返回包,也不等服务器响应,立即关闭HTTP通道,或者强行关闭tcp连接。这样就会导致服务器在想发送response包给客户端时,找不到客户端的连接,所以提示499的状态码。

测试

针对以上的猜想,我对我自己的博客网站做了测试。用sock和httplib分别写了两段代码如下:

sock:

httplib:

第一段sock的代码全用的多进程,第二段httplib的用多进程和协程结合,然而这两种方法对于我的博客并发4-5千没有任何影响,没有一个499状态码出现。按照网络上文章的解释,这里我猜测是无论是sock还是http,在关闭请求时都没有关闭TCP连接,所以导致了客户端其实没有断开连接,所以服务端依然返回200。所以抓包分析了一下在做sock请求时的代码:

客户端: DDOS导致Nginx 499状态码成因分析

服务端:

服务端的图就不截了,基本就是客户端的反向,基本没有差别。

从TCP流分析,首先开始是TCP三次握手,然后服务端向客户端发送HTTP请求,发送完HTTP请求之后,立刻发送FIN包,请求关闭TCP连接,开始四次挥手的流程。差不多60ms之后,客户端收到了服务端的发来的ACK包,代表服务端进入CLOSE-WAIT状态,此时TCP四次握手并没有完全关闭,服务端依然会将HTTP请求的数据发送给客户端。所以我们看到了NO.504包和NO.561包,length均为1514,这个包就是服务端把HTTP的返回包发给客户端的包,但是客户端接收到这个包之后,由于客户端本身已经关闭连接不接收数据,所以就会发RST包给服务端,代表接收到了异常数据,连接异常关闭。这也就导致了TCP四次挥手没有全部做完,而是异常关闭的。附一张TCP的连接图如下:

DDOS导致Nginx 499状态码成因分析

回到我们的问题上,我们的猜想也可以合理解释了: 即使我们的SOCK连接是发送了http请求后立马就请求关闭TCP连接,但是TCP四次挥手需要时间,在TCP挥手结束之前,服务端就把数据传输了过来,所以返回包依然是200。

二次测试

这个时候我们还无法验证我们的猜想是否正确,所以我找了一个PHP的站点,又一次进行了上述实验,在Nginx代理PHP的环境下,上述两个测试脚本均造成了大量499状态码的出现。

抓下包,截图如下: DDOS导致Nginx 499状态码成因分析

这是跑第一个sock脚本的结果,这个TCP流就简单多了,先三次握手,再发送HTTP请求包,再四次挥手,可以看到在此时服务端并没有返回数据给客户端,也就是说明服务端在TCP连接关闭之前,没有生成完网页内容并把内容返回给客户端。此时再看一下服务端的状态,Nginx日志中出现了大量499状态。

DDOS导致Nginx 499状态码成因分析

结论分析

经过两次测试,初步可以得出以下结论:

  1. 499状态码产生的原因是在服务端准备好返回内容并发送之前,客户端已经关闭了和服务端的 TCP 连接。而之所以我的博客不会产生是因为静态页面,Nginx处理的很快,而Nginx主要用作反向代理,准备返回包的内容肯定会有一定的延迟,所以499状态码的出现屡见不鲜。
  2. 经抓包分析,python的httplib模块,在发送完HTTP请求,接受完数据后就会主动关闭TCP连接,下次请求时会再新建一个TCP连接,而不是一直保持一个HTTP长连接不断。
  3. 网上流传的解决措施: proxy_ignore_client_abort on; ,经测试没有任何效果。
(0)

本文由 安全周 作者:SecJack 发表,转载请注明来源!

关键词:

热评文章

发表评论