ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

nodejs的http慢请求攻击问题

2021-01-23 11:57:42  阅读:140  来源: 互联网

标签:const socket nodejs server timeout 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.1\r\ncontent-length:1\r\n');
const timer = setInterval(() => {
    socket.write('1\r\n')
}, 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.1\r\ncontent-length:1\r\n');
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机制。

标签:const,socket,nodejs,server,timeout,http,请求
来源: https://blog.csdn.net/THEANARKH/article/details/113035671

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有