我是靠谱客的博主 独特刺猬,最近开发中收集的这篇文章主要介绍nodejs的http慢请求攻击问题,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

本文介绍关于nodejs中的http慢请求攻击问题。
首先我们写一个测试服务器

const http = require('http');
const server = http.createServer((req,res) => {}).listen(80);
// 3秒还没有解析完http头则关闭连接
server.headersTimeout = 3000

接着我们写个测试客户端

const net = require('net');
const socket = net.connect({port: 80});
socket.write('POST / HTTP/1.1rncontent-length:1rn');
const timer = setInterval(() => {
socket.write('1rn')
}, 4000);
const start = Date.now();
socket.on('end', () => {
clearInterval(timer);
console.log(Date.now() - start)
})

客户端先发送一部分http报文,4秒后再继续发。我们发现,虽然我们服务器设置了3秒超时,但是无效。连接会在4秒后断开。我们看看源码(node14.6.0)为什么。
node_http_parser.cc的OnStreamRead

// 开始解析http的时间,0说明解析完了。如果正在解析且设置了timeout则判断是否超时(server.setTimeout())
if (header_parsing_start_time_ != 0 && headers_timeout_ != 0) {
uint64_t now = uv_hrtime();
// 算出已经解析的时间间隔
uint64_t parsing_time = (now - header_parsing_start_time_) / 1e6;
// 是否过期了
if (parsing_time > headers_timeout_) {
Local<Value> cb =
object()->Get(env()->context(), kOnTimeout).ToLocalChecked();
if (!cb->IsFunction())
return;
MakeCallback(cb.As<Function>(), 0, nullptr);
return;
}
}

以上是判断解析http头过期的逻辑,看起来没问题,nodejs处理了这个问题,那为什么在我们设定的时间内没有触发断开连接呢?问题在于这段逻辑(OnStreamRead函数)是有数据的时候执行的回调,所以这依赖于客户端发送数据才能触发,否则就不会进入这个逻辑,也就无法断开连接。我们顺便看一下触发这段逻辑的时候会怎样。

function onParserTimeout(server, socket) {
const serverTimeout = server.emit('timeout', socket);
if (!serverTimeout)
socket.destroy();
}

默认是断开连接,如果我们监听了server的timeout事件,则需要自己处理连接问题。至此我们明白了为什么服务器的设置无效了。
我们改一下客户端代码再测试一下。

const net = require('net');
const socket = net.connect({port: 80});
socket.write('POST / HTTP/1.1rncontent-length:1rn');
const start = Date.now();
socket.on('end', () => {
console.log(Date.now() - start)
})

这次我们只发送一部分。超时后也不再发送数据了,这时候服务器会怎样呢?我们发现服务器会一直保持这个连接,经过上面的分析我们知道,当客户端不再发送数据的时候,是无法触发断连的逻辑的,这就会带来http慢请求攻击。那我们如何解决这个问题呢?
nodejs中还有另外一个配置可以帮我们解决这个问题。那就是server.setTimeout();我们看看代码。

Server.prototype.setTimeout = function setTimeout(msecs, callback) {
this.timeout = msecs;
if (callback)
this.on('timeout', callback);
return this;
};

设置了一个值。这个值有什么用呢?我们接着看

function connectionListenerInternal(server, socket) {
if (server.timeout && typeof socket.setTimeout === 'function')
socket.setTimeout(server.timeout);
socket.on('timeout', socketOnTimeout);
}

以上代码是建立tcp连接成功的回调,nodejs会设置socket的空闲超时时间。回调是socketOnTimeout

function socketOnTimeout() {
const req = this.parser && this.parser.incoming;
const reqTimeout = req && !req.complete && req.emit('timeout', this);
const res = this._httpMessage;
const resTimeout = res && res.emit('timeout', this);
const serverTimeout = this.server.emit('timeout', this);
if (!reqTimeout && !resTimeout && !serverTimeout)
this.destroy();
}

超时回调的时候,默认会关闭连接。所以我们可以通过这个配置解决慢请求攻击。

这个问题不是nodejs一直存在的,在早期的版本(比如12.20.0)的时候是没有这个问题的。

 uint64_t http_server_default_timeout = 120000;
const kDefaultHttpServerTimeout =
getOptionValue('--http-server-default-timeout');
function Server(options, requestListener) {
this.timeout = kDefaultHttpServerTimeout;
}

我们看到nodejs早期版本里timeout的默认值是两分钟,这意味着nodejs默认处理了这个问题,而v14.6.0中则是0。具体可以参考(https://github.com/nodejs/node/commit/c30ef3cbd2e42ac1d600f6bd78a601a5496b0877 ),这时候我们可能想到keepalive机制,但是linux下keepalive默认是不开启的,而且因为nodejs还没解析完http头,没有回调我们,我们也拿不到socket对象,从而也无法开启keepalive机制。

最后

以上就是独特刺猬为你收集整理的nodejs的http慢请求攻击问题的全部内容,希望文章能够帮你解决nodejs的http慢请求攻击问题所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(35)

评论列表共有 0 条评论

立即
投稿
返回
顶部