前言:

什么是HTTP 缓慢攻击?

​ http缓慢/慢速攻击,英文为 slow http attack,是一种dos攻击。

什么是dos攻击?

​ dos就是拒绝服务,所有能引起拒绝服务的攻击都被称为dos攻击。

HTTP缓慢攻击的原理是什么?

​ http慢速攻击目前来说有三种方式,分别是slow headers,slow body 和 slow read 攻击。

slow headers

​ 对于Nginx这样的web服务器来说,需要在接受完所有的HTTP头部之后再处理HTTP请求,而头部完全发送的标志是web服务器接收到连续的两个\r\n, 即\r\n\r\n。如果客户端没有发送这样的标志,服务器就会认为头部还没有发送完,从而持续的等待。当客户端持续的建立这样的连接,服务器的连接数会被占满,导致正常用户的访问就被拒绝。

slow body

​ slow body 也被称为 slow http post, 攻击者发起一个POST请求,通过设置给Content-Length一个很大的数值,让服务器以为该次传输的数据很大,但是攻击者每次只发送很少的数据,该链接会一直保持存活,持续多次建立这样的连接,消耗服务器的资源,从而最终导致拒绝服务。

slow read

​ 原理和slow body类似,只不过slow read 是攻击者发送一个完整正常的请求后,以极低的速度读取客户端的响应,同时又不会满足超时断开条件,这样就保持了一条连接,持续建立,消耗服务器资源。最终导致 拒绝服务。

修复策略

所谓dos攻击,本质上都是通过消耗服务器资源来让服务器无法正常服务器。

那么讲道理,只要我的服务器资源和性能远远大于攻击者的资源和性能,那么岂不是就没有问题了。

水滴会在乎你来的是2000艘联合舰队还是5000艘么?当然不在乎了。

但是这种方案明显只能想想而已。

既然不能无限提高自身的资源和性能,那么就要反过来限制请求者能使用的资源了。

比如配置安全策略,拒绝黑名单用户,

配置超时,强制断掉不正常的连接。

配置大小,如果发送传输的资源过大,就及时断开连接等等。

Nginx 修复

不同的web服务器对于缓慢攻击的应对思路都大同小异,但是配置方案略有差异。这里以Nginx的配置为例。

指令:keepalive_timeout
1
2
3
语法:		keepalive_timeout timeout [header_timeout];
默认值: keepalive_timeout 75s;
上下文: http, server, location

第一个参数设置客户端的长连接在服务器端保持的最长时间(在此时间客户端未发起新请求,则长连接关闭)。

第二个参数为可选项,设置“Keep-Alive: timeout=*time*”响应头的值。 可以为这两个参数设置不同的值。

默认超时时间为75秒,部分浏览器最长只能保持60s,所以可以设置为60s。

需要注意的是,由于慢速攻击并不是长时间不发送数据包,而是发送的极为缓慢,所以该配置并不能解决,配置上聊胜于无

指令:client_body_timeout
1
2
3
语法:	  client_body_timeout time;
默认值: client_body_timeout 60s;
上下文: http, server, location

定义读取客户端请求正文的超时。如果客户端在这段时间内没有传输任何数据,nginx将返回408 (Request Time-out)错误到客户端。

需要注意的是,超时是指相邻两次读操作之间的最大时间间隔,而不是整个请求正文完成传输的最大时间。 如果客户端在这段时间内没有传输任何数据,nginx将返回408 (Request Time-out)错误到客户端。

指令: client_header_timeout
1
2
3
语法:	     client_header_timeout time;
默认值: client_header_timeout 60s;
上下文: http, server

定义读取客户端请求头部的超时。如果客户端在这段时间内没有传送完整的头部到nginx, nginx将返回错误408 (Request Time-out)到客户端。

该配置可以一定程度上针对slow header攻击。

指令: send_timeout
1
2
3
语法:	     send_timeout time;
默认值: send_timeout 60s;
上下文: http, server, location

设置向客户端传输响应的超时。如果客户端在这段时间中没有收到任何数据,连接将关闭。

需要注意的是, 超时仅指两次相邻写操作之间的时间间隔,而非整个响应的传输时间。

模块:ngx_http_limit_req_module

该模块可以通过定义的键值对来心智请求处理的频率。比如,我们可以限制IP的请求频率。

限制的原理是漏桶方法:固定单位时间的请求数,超过限定的推迟处理。

主要涉及到两条指令:limit_req_zonelimit_req.

这两条指令配合使用。

  1. limit_req_zone

    1
    2
    3
    语法:	limit_req_zone $variable zone=name:size rate=rate;
    默认值: —
    上下文: http

    设置一块共享内存限制域的参数,它可以用来保存键值的状态。 它特别保存了当前超出请求的数量。 键的值就是指定的变量(空值不会被计算)。 比如:

    1
    2
    limit_req_zone $binary_remote_addr zone=demo:10m rate=1r/s;
    # 状态命名为demo,最大共享内存是10m,限制了同一远程地址即ip得请求频率不能超过1s一次
  2. limit_req

    1
    2
    3
    语法:	limit_req zone=name [burst=number] [nodelay];
    默认值: —
    上下文: http, server, location

    设置对应的共享内存限制域和允许被处理的最大请求数阈值。 如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以 所有的请求都是以定义的频率被处理的。 超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阈值 这时,这个请求会被终止,并返回503 (Service Temporarily Unavailable) 错误。这个阈值的默认值等于0。

    比如:

    1
    2
    3
    4
    5
    6
    7
    limit_req_zone $binary_remote_addr zone=demo:10m rate=1r/s;

    server {
    location /search/ {
    limit_req zone=demo burst=5 nodelay;;
    # 限制平均每秒不超过一个请求,同时允许超过频率限制的请求数不多于5个, nodelay表示不延迟处理超出得请求,直接返回503
    }
模块: ngx_http_limit_conn_module

ngx_http_limit_conn_module 模块可以按照定义的键限定每个键值的连接数。比如,可以设定单一 IP 来源的连接数。

需要注意得是,并不是所有的连接都会被模块计数;只有那些正在被处理的请求(这些请求的头信息已被完全读入)所在的连接才会被计数。

主要涉及到两个指令: limit_conn_zonelimit_conn

  1. limit_conn_zone

    1
    2
    3
    语法:	limit_conn_zone $variable zone=name:size;
    默认值: —
    上下文: http

    设定保存各个键的状态的共享内存空间的参数。键的状态中保存了当前连接数。键的值可以是特定变量的任何非空值(空值将不会被考虑)。 比如根据ip进行限制:

    1
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    注意,这里使用的是$binary_remote_addr变量,而不是$remote_addr变量。$remote_addr变量的长度为7字节到15字节不等,而存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。而$binary_remote_addr变量的长度是固定的4字节,存储状态在32位平台中占用32字节或64字节,在64位平台中占用64字节。一兆字节的共享内存空间可以保存3.2万个32位的状态,1.6万个64位的状态。如果共享内存空间被耗尽,服务器将会对后续所有的请求返回 503 (Service Temporarily Unavailable) 错误。

  2. limit_conn

    1
    2
    3
    语法:	limit_conn zone number;
    默认值: —
    上下文: http, server, location

    指定一块已经设定的共享内存空间,以及每个给定键值的最大连接数。当连接数超过最大连接数时,服务器将会返回 503 (Service Temporarily Unavailable) 错误。比如:

    1
    2
    3
    4
    5
    6
    7
    limit_conn_zone $binary_remote_addr zone=addr:10m;

    server {
    location /download/ {
    limit_conn addr 1;
    # 同一 IP 同一时间只允许有一个连接。
    }

    当多个 limit_conn 指令被配置时,所有的连接数限制都会生效。比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    limit_conn_zone $binary_remote_addr zone=perip:10m;
    limit_conn_zone $server_name zone=perserver:10m;

    server {
    ...
    limit_conn perip 10;
    # 限制单一IP来源的连接数
    limit_conn perserver 100;
    # 限制单一虚拟服务器的总连接数
    }

    当前配置层级没有limit_conn指令,将会从更高层级继承连接限制配置。