经过 2 天的研究和了解 Modsecurity 的工作原理,我终于做到了。仅供参考,我正在使用 Apache 2.4.37 和 Modsecurity 2.9.2 这就是我所做的:
在我的自定义文件规则中:/etc/modsecurity/modsecurity_custom.conf 我添加了以下规则:
# Limit client hits by user agent
SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit" \
"id:400009,phase:2,nolog,pass,setvar:global.ratelimit_facebookexternalhit=+1,expirevar:global.ratelimit_facebookexternalhit=3"
SecRule GLOBAL:RATELIMIT_FACEBOOKEXTERNALHIT "@gt 1" \
"chain,id:4000010,phase:2,pause:300,deny,status:429,setenv:RATELIMITED,log,msg:'RATELIMITED BOT'"
SecRule REQUEST_HEADERS:User-Agent "@pm facebookexternalhit"
Header always set Retry-After "3" env=RATELIMITED
ErrorDocument 429 "Too Many Requests"
解释:
注意:我想限制为每 3 秒 1 个请求。
- 第一条规则将请求标头用户代理与“facebookexternalhit”相匹配。如果匹配成功,它会在 global 集合中创建 ratelimit_facebookexternalhit 属性,其初始值为 1(它会随着每个点击匹配用户代理)。然后,它将这个 var 的过期时间设置为 3 秒。如果我们收到一个匹配“facebookexternalhit”的新命中,它的总和为 1 到 ratelimit_facebookexternalhit。如果我们在 3 秒后没有收到匹配“facebookexternalhit”的点击,ratelimit_facebookexternalhit 将消失并重新启动此过程。
- 如果 global.ratelimit_clients > 1(我们在 3 秒内收到 2 次或更多点击)并且用户代理匹配“facebookexternalhit”(此 AND 条件很重要,否则如果产生匹配,所有请求都将被拒绝),我们设置 RATELIMITED =1,使用 429 http 错误停止操作,并在 Apache 错误日志中记录自定义消息:“RATELIMITED BOT”。
- RATELIMITED=1 设置只是为了添加自定义标题“Retry-After: 3”。在这种情况下,这个 var 会被 Facebook 的爬虫 (facebookexternalhit) 解释,并会在指定的时间重试操作。
- 我们为 429 错误映射自定义返回消息(如果需要)。
您可以通过添加@pmf 和.data 文件来改进此规则,然后初始化initcol:global=%{MATCHED_VAR} 之类的全局集合,因此您不仅限于单个规则匹配。我没有测试这最后一步(这是我现在需要的)。如果我这样做了,我会更新我的答案。
更新:
我已经调整了规则,以便能够拥有一个包含我想要限制的所有用户代理的文件,因此可以跨多个机器人/爬虫使用单个规则:
# Limit client hits by user agent
SecRule REQUEST_HEADERS:User-Agent "@pmf data/ratelimit-clients.data" \
"id:100008,phase:2,nolog,pass,setuid:%{tx.ua_hash},setvar:user.ratelimit_client=+1,expirevar:user.ratelimit_client=3"
SecRule USER:RATELIMIT_CLIENT "@gt 1" \
"chain,id:1000009,phase:2,deny,status:429,setenv:RATELIMITED,log,msg:'RATELIMITED BOT'"
SecRule REQUEST_HEADERS:User-Agent "@pmf data/ratelimit-clients.data"
Header always set Retry-After "3" env=RATELIMITED
ErrorDocument 429 "Too Many Requests"
因此,带有用户代理的文件(每行一个)位于此规则同一目录下的子目录中:/etc/modsecurity/data/ratelimit-clients.data。然后我们使用@pmf 来读取和解析文件(https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#pmfromfile)。我们使用用户代理初始化 USER 集合:setuid:%{tx.ua_hash}(tx.ua_hash 在/usr/share/modsecurity-crs/modsecurity_crs_10_setup.conf 的全局范围内)。我们只是使用 user 作为集合而不是 global。就是这样!