Modsecurity & Apache:如何通过 header 限制访问速率?

Modsecurity & Apache: How to limit access rate by header?

我同时使用 Apache 和 Modsecurity。我正在尝试通过请求的 header(如 "facebookexternalhit")来限制命中率。然后 return 一个友好的“429 太多请求”和 "Retry-After: 3".

我知道我可以读取 header 的文件,例如:

SecRule REQUEST_HEADERS:User-Agent "@pmFromFile ratelimit-bots.txt"

但是我在构建规则时遇到了问题。

任何帮助将不胜感激。谢谢。

经过 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 个请求。

  1. 第一条规则将请求 header 用户代理与 "facebookexternalhit" 相匹配。如果匹配成功,它会在 global collection 中创建 ratelimit_facebookexternalhit 属性 1 的值(它会随着每次匹配用户代理的匹配而递增此值)。然后,它将此 var 的过期时间设置为 3 秒。如果我们收到匹配 "facebookexternalhit" 的新命中,它会将 1 加到 ratelimit_facebookexternalhit。如果我们在 3 秒后没有收到匹配 "facebookexternalhit" 的命中,ratelimit_facebookexternalhit 将消失,此过程将重新启动。
  2. 如果 global.ratelimit_clients > 1(我们在 3 秒内收到 2 次或更多次点击)AND 用户代理匹配 "facebookexternalhit"(这个 AND 条件很重要,否则如果匹配是所有请求都将被拒绝产生),我们设置 RATELIMITED=1,以 429 http 错误停止操作,并在 Apache 错误日志中记录自定义消息:"RATELIMITED BOT".
  3. RATELIMITED=1 设置只是为了添加自定义 header "Retry-After: 3"。在这种情况下,此 var 由 Facebook 的爬虫 (facebookexternalhit) 解释,并将在指定时间重试操作。
  4. 我们为 429 错误映射自定义 return 消息(以备不时之需)。

您可以通过添加 @pmf 和一个 .data 文件来改进此规则,然后像 initcol:global=%{MATCHED_VAR} 一样初始化全局 collection,这样您就不会被规则限制为单个匹配。我没有测试这最后一步(这是我现在需要的)。如果我这样做,我会更新我的答案。

更新:

我已经调整了规则,以便能够拥有一个包含我想要速率限制的所有用户代理的文件,因此可以在多个 bots/crawlers:

中使用单个规则
# 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 collection:setuid:%{tx.ua_hash}tx.ua_hash/usr/share/modsecurity-crs/modsecurity_crs_10_setup.conf 的全局范围内。而我们简单地使用 user 作为 collection 而不是 global。就这些!

使用“deprecatevar”可能更好, 你可以允许更大一点的突发 leneanancy

# 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,deprecatevar:user.ratelimit_client=3/1"

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 "6" env=RATELIMITED

ErrorDocument 429 "Too Many Requests"