在互联网上,经常会遇到一些不受欢迎的HTTP请求,这些请求可能来自特定的IP地址。对于网站管理员来说,能够动态地阻止这些IP地址是非常重要的。然而,发现在互联网上并没有现成的解决方案。因此,决定自己实现一个解决方案,并在此分享给大家。
在Linux系统中,通常使用iptables来阻止不需要的IP或IP范围。以下是使用iptables阻止IP的一些示例:
# 阻止特定IP
iptables -A INPUT -s 0.0.0.1 -j DROP
# 阻止特定IP范围
iptables -A INPUT -m iprange --src-range 0.0.0.0-0.0.0.255 -j DROP
# 使用CIDR表示法阻止特定IP范围
iptables -A INPUT -s 0.0.0.0/24 -j DROP
iptables是一个非常方便的工具,如果服务器直接连接到互联网,并且没有代理或负载均衡器,那么可以使用它。但是,在网站上,使用的是AWS Elastic Beanstalk应用,运行在Linux上的Apache,并且前面有Elastic Load Balancer。因此,得到的IP地址总是负载均衡器的IP地址,只有在HTTP头部的"X-Forwarded-For"中,才能得到请求客户端的IP地址。
由于iptables只能使用IP地址,而不能使用HTTP头部的值,因此被迫寻找替代方案。决定使用mod_security来实现这个功能。mod_security提供了"Collections"功能,它实际上是一个HashMap。IP地址是键,值是一个整数。如果值为1,则将其解释为白名单IP;如果值为2,则将其视为黑名单IP。如果键不存在,则IP既不是白名单也不是黑名单,因此会正常处理。
如果发现IP在白名单中,将不再进行进一步的检查,请求将被接受。如果发现IP在黑名单中,将断开连接,并将日志记录在modsecurity日志文件中。
要将IP添加到黑名单,可以访问应用程序的特定URL,并将IP作为参数传递。例如:
www.example.com/ip/blacklist?ip=0.0.0.1
类似地,要将IP添加到白名单,可以使用:
www.example.com/ip/whitelist?ip=0.0.0.2
要从白名单和黑名单中移除IP,可以使用:
www.example.com/ip/remove?ip=0.0.0.3
然后,最重要的是确保这些命令只有在从当前机器(即localhost)发出时才被执行。
安装mod_security后,它会创建一个名为"modsecurity.d"的目录,并加载该目录中所有带有".conf"扩展名的文件。
现在来看文件"my_rules.conf"中的源代码,该文件位于"modsecurity.d"目录中。
# 添加/移除IP到动态白名单和黑名单 - 仅限localhost
# 从动态白名单和黑名单中移除 - 移除允许的变量
SecRule REQUEST_FILENAME "^/ip/remove$" "chain,phase:1,t:none,deny,nolog,status:200"
SecRule REMOTE_ADDR "^127.0.0.1$" "chain,t:none"
SecRule ARGS:ip "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" "t:none,initcol:ip=%{args.ip},setvar:!ip.allowed"
# 添加到动态白名单 - 允许值为1
SecRule REQUEST_FILENAME "^/ip/whitelist$" "chain,phase:1,t:none,deny,nolog,status:200"
SecRule REMOTE_ADDR "^127.0.0.1$" "chain,t:none"
SecRule ARGS:ip "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" "t:none,initcol:ip=%{args.ip},setvar:ip.allowed=1"
# 添加到动态黑名单 - 允许值为2
SecRule REQUEST_FILENAME "^/ip/blacklist$" "chain,phase:1,t:none,deny,nolog,status:200"
SecRule REMOTE_ADDR "^127.0.0.1$" "chain,t:none"
SecRule ARGS:ip "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" "t:none,initcol:ip=%{args.ip},setvar:ip.allowed=2"
# 允许来自localhost的任何请求
SecRule REMOTE_ADDR "^127.0.0.1$" "phase:1,t:none,allow,nolog,ctl:ruleEngine=off"
# 初始化IP集合,使用从x-forwarded-for或remote_addr获取的IP地址
SecRule REQUEST_HEADERS:x-forwarded-for "^\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b" "phase:1,t:none,pass,nolog,capture,setvar:tx.client_ip=%{tx.1}"
SecRule &TX:CLIENT_IP "@eq 0" "phase:1,t:none,pass,nolog,setvar:tx.client_ip=%{remote_addr}"
SecRule &TX:CLIENT_IP "!@eq 0" "phase:1,t:none,pass,nolog,initcol:ip=%{tx.client_ip}"
# 处理动态白名单和黑名单
# 如果IP在动态白名单中,则允许
SecRule IP:ALLOWED "@eq 1" "phase:1,t:none,allow,nolog,ctl:ruleEngine=off"
# 如果IP在动态黑名单中,则丢弃
SecRule IP:ALLOWED "@eq 2" "phase:1,t:none,drop,log,logdata:'Dynamic Blacklist'"
以下是将IP添加到黑名单、白名单或从列表中移除的示例。使用Linux的curl命令来访问URL,并且只关心HTTP响应代码,可以通过使用"http_code"选项来获取。
# 黑名单几个IP
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/blacklist?ip=0.0.0.1
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/blacklist?ip=0.0.0.2
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/blacklist?ip=0.0.0.3
# 白名单几个IP
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/whitelist?ip=0.0.1.1
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/whitelist?ip=0.0.1.2
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/whitelist?ip=0.0.1.3
# 如果存在,则从两个列表中移除IP
curl -s -o /dev/null -I -w "%{http_code}" localhost/ip/remove?ip=127.0.0.1