最近遇到电商网站突然流量激增,流量穿透 nginx 到后端,导致后端服务器 502,本以为是突发流量没太重视,经过日志分析发现是单个 ip 在1分钟内高并发请求 1200 次所致。

这种行为已经不是正常的爬虫,而是恶意攻击行为,那么 nginx 可否对这种攻击行为进行限制呢?

一、限速模块

Nginx 有三种类型的模块用来限速,且各有其特点:

  1. limit_conn_zone 模块 - 限制同一 IP 地址并发连接数;
  2. limit_request 模块 - 限制同一 IP 某段时间的访问量;
  3. core 模块提供 - limit_rate 限制同一 IP 流量。

以上三个功能都是 Nginx 的内置模块,配置简单,开箱即用,今天主要说下第二点,limit_request, 通过限制单个 ip 的请求次数来达到防止单 ip 的高频词访问行为。

二、配置频率限制

频率限制主要有2个主要指令,limit_req_zone 和limit_req, 示例如下:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/sm;
server {
    location /login/ {
        limit_req zone=mylimit;
        proxy_pass http://my_upstream;
    }
}

limit_req_zone指令定义了速度限制的参数,同时在出现的上下文中启用速率限制,通常定义在HTTP块中,这样可以用于多个上下文,它包含3个参数:

Key - 定义应用限制的请求特征。 在这个例子中,它是 Nginx 变量 $binary_remote_addr ,它保存着客户端 IP 地址的二进制表示。 这意味着我们将每个唯一的IP地址限制为由第三个参数定义的请求速率。

Zone - 定义用于存储每个IP地址状态的共享内存区域以及访问请求受限URL的频率。 将信息保存在共享内存中意味着它可以在Nginx工作进程之间共享。

定义有两个部分: zone=keyword 标识的区域名称和冒号后面的大小。 大约16,000个IP地址的状态信息需要1兆字节,所以我们的区域可以存储大约160,000个地址。 如果Nginx需要添加一个新条目时,存储空间将被耗尽,它将删除最旧的条目。

如果释放的空间不足以容纳新记录,则 Nginx返回状态码503 (Temporarily Unavailable) 。 此外,为了防止内存耗尽,每当Nginx创建一个新条目时,最多可以删除两个在前60秒内没有使用的条目。

Rate - 设置最大请求率。 在这个例子中,速率不能超过每秒 10 个请求。 Nginx实际上以毫秒粒度跟踪请求,所以这个限制对应于每 100 毫秒 1 个请求。 由于我们不允许爆发,这意味着如果请求在前一个允许的时间之后小于 100 毫秒时被拒绝。

limit_req_zone 指令为速率限制和共享内存区域设置参数,但实际上并不限制请求速率。

因此,需要通过在其中包含 limit_req 指令来将限制应用于特定 location 或 server 块。 在这个例子中,我们是对 /login/ 的URI速率限制请求。

现在每个唯一的IP地址被限制,/login/每秒10个请求 - 或者更确切地说,在前一个100毫秒内不能请求该URL。

三、处理并发

如果我们在 100 毫秒内得到两个请求会怎么样? 对于第二个请求,Nginx将状态码503返回给客户端。 这可能不是我们想要的,因为应用程序本质上是突发性的。

相反,我们想要缓冲任何多余的请求并及时提供服务。 这时我们使用 burst 参数 limit_req,在这个更新的配置:

location /login/ {
limit_req zone=mylimit burst=20;
proxy_pass http://my_upstream;
}

burst 参数定义了客户端可以超过区域指定的速率(使用我们的示例mylimit区域,速率限制为每秒10个请求,或每100毫秒1个)可以产生多少个请求。

在前一个请求到达100毫秒后的请求被放入一个队列中,这里我们将队列大小设置为20。

这意味着如果 21 个请求同时从一个给定的IP地址到达,Nginx 立即将第一个请求转发到上游服务器组,并将剩下的20个放入队列中。 然后,它每 100 毫秒转发一个排队的请求,并且只有当传入的请求使排队请求的数量超过 20 时才返回 503 给客户端。

四、无延迟队列

具有 burst 的配置会导致流量畅通,但不是很实用,因为它可能会使您的网站显得很慢。

在我们的例子中,队列中的第 20 个数据包等待 2 秒钟被转发,此时对其的响应可能对客户端不再有用。 要解决这种情况,请将nodelay 参数与 burst 参数一起添加:

location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream;
}

通过nodelay参数,Nginx 仍然根据 burst 参数在队列中分配时隙,并且强加配置的速率限制,但是不排除转发排队的请求。 相反,当请求到达“太快”时,Nginx 会立即转发,只要队列中有一个可用的时隙。 它将该插槽标记为“已占用”,并且不会将其释放以供其他请求使用,直到经过适当的时间(在本例中为100毫秒之后)。

假设像以前一样,20 个时隙的队列是空的,21 个请求同时从给定的 IP 地址到达。 Nginx 立即转发所有 21 个请求,并将队列中的 20 个插槽标记为已占用,然后每 100 毫秒释放 1 个插槽(如果有 25 个请求,Nginx会立即转发 21 个插槽,标记20个插槽,拒绝4个请求状态503 )。

现在假设在第一组请求之后 101 毫秒被转发,另外 20 个请求同时到达。 队列中只有1个插槽被释放,所以 Nginx 转发1个请求,并拒绝其他 19 个状态为 503 的队列。 如果在 20 个新请求到达之前经过了 501 毫秒,那么 5 个空闲空间,所以 Nginx 立即转发 5 个请求,拒绝 15 个请求。

效果相当于每秒10个请求的速率限制。 如果您希望在不限制请求之间的允许间隔的情况下施加速率限制,则 nodelay 选项非常有用。

注意:对于大多数部署,我们建议将 burst 和 nodelay 参数包含到 limit_req 指令中。

在 mac 下有一个由图片组成的 pdf 文件,需要修改其中的某图片,如何转换 pdf 到 image 呢?
处理好图片了,又如何将图片重新生成一个 pdf 文件呢?

1. pdf to images

convert -background none -density 412 -quality 100 xxx.pdf xxx.png

其中有三个参数:

background: 无背景色
density:dpi,图片的像素数量,值一般为 72, 144, 216,值越大,图片的尺寸越大,分辨率越高
quality:图片的质量

执行之后,将会生成若干张图片,如 xxx-1.png,xxx-2.png

2. images to pdf

这个地方用到一个 shell 脚本:

https://github.com/initiummedia/images-to-single-pdf

使用该脚本即可,该脚本依赖一个包,pdftk,需要在 mac 下安装一个包。

之前一直是在本地环境跑 mitmproxy 抓包做分析工作,近期需要将 mitmproxy 结合脚本进行数据抓取,为了便利性所以考虑将服务部署到服务器上,期间遇到不少问题,走了不少弯路,这里简要的记录一下。

一、安装 mitmproxy

安装是非常简单的,直接 apt-get install mitmproxy 即可,安装好之后,运行 mitmproxy 就可以抓包了,但是 http 可以,https 不行,参考网上的说法,需要在 ubuntu 服务器上配置一下证书。

二、安装证书到 ubuntu 服务器(不一定必要)

这一步应该不需要,理论上是用于 ubuntu上客户端浏览器的。病急乱投医,参考网上的说法做了下操作,当时并未解决我的问题。

安装好 mitmproxy 之后,mitmproxy 会在用户的家目录下建立 .mitmproxy 目录,并将证书生成在该目录中。

mkdir /usr/share/ca-certificates/extra
cp ./mitmproxy/mitmproxy-ca-cert.cer /usr/share/ca-certificates/extra/mitmproxy-ca-cert.crt
update-ca-certificates # 添加证书

三、测试 mitmproxy

mitmdump --listen-port=8880 --set block_global=false --set proxy_debug -vvv

这个命令比较关键,如果要监听外网流量,block_global 必须设置为 false。
如果遇到抓包异常,开启 --set proxy_debug -vvv 把详细的日志打印出来,非常关键。

我这边当时 https 连接失败,从日志上看就是连接反复被断开,从现象上看就是 https 连接失败,打开 debug 日志就可以看到详细情况了:

36.106.224.20:19884: clientconnect
::ffff:36.106.224.20:19884: Set new server address: mp.weixin.qq.com:443
::ffff:36.106.224.20:19884: serverconnect
  -> ('mp.weixin.qq.com', 443)
::ffff:36.106.224.20:19884: Establish TLS with server
::ffff:36.106.224.20:19884: ALPN selected by server: h2
::ffff:36.106.224.20:19884: Establish TLS with client
::ffff:36.106.224.20:19884: ALPN for client: b'h2'
36.106.224.20:19884: CONNECT mp.weixin.qq.com:443
    Host: mp.weixin.qq.com
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) MicroMessenger/6.8.0(0x16080000) MacWechat/3.2.1(0x13020110) Chrome/39.0.2171.95 Safari/537.36 NetType/WIFI WindowsWechat
    Connection: keep-alive
    Proxy-Connection: keep-alive
 << Cannot establish TLS with client (sni: mp.weixin.qq.com): TlsException("SSL handshake error: SysCallError(-1, 'Unexpected EOF')",)
::ffff:36.106.224.20:19884: serverdisconnect

通过在 google 搜索,找到原因是客户端配置的证书问题(由于之前在本地抓 https 数据包都正常,所以一直认为本地证书是正常的,早已经安装过 mitmproxy 的证书)。

四、本地安装 mitmproxy 证书

本地设置代理之后,访问 mitm.it 之后,下载客户端相应的证书,并双击安装证书,并设置证书的使用范围

Double-click the PEM file to open the Keychain Access application.
Locate the new certificate "mitmproxy" in the list and double-click it.
Change Secure Socket Layer (SSL) to Always Trust.
Close the dialog window and enter your password if prompted.

image-20211127141253298

设置好之后,就能愉快的抓包了。

概述

由于默认的python版本为2.7,执行命令时需要改为python3,比较麻烦。所以将python3设置为默认版本。(执行apt-get install python3-pip安装python3)

配置

原理:使用update-alternatives进行配置

查看python位置

ls /usr/bin/python*
一般是/usr/bin/python2和/usr/bin/python3

查询当前配置

update-alternatives --list python
没有配置过的话会返回update-alternatives: error: no alternatives for python

配置默认版本

通过设置优先级来实现默认使用python3

sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 2

测试

查询默认版本号
python --version

注意
有些脚本需要手动修改python版本(如yum),将/usr/bin/python改为/usr/bin/python2