目录
一、allow/deny
allow 和 deny 是 Nginx 的 ngx_http_access_module 模块中用于访问控制(Access Control)的指令,主要用于限制客户端 IP 对某个资源、目录或接口的访问权限。可配置于 http、server、location、limit_except 等上下文中。
使用 deny 指令控制特定 IP 和 CIDR 网段不能访问:
location / {
# 禁止指定 IP 地址访问
deny 192.168.1.100;
# 禁止 CIDR 网段
deny 192.168.2.0/24;
allow all;
}
使用 allow 指令只允许特定 IP 和 CIDR 网段可以访问:
location /admin {
# 允许指定 IP 地址访问
allow 192.168.2.1;
# 允许 CIDR 网段访问
allow 192.168.3.0/24;
deny all;
}
allow 和 deny 指令的优点是简单方便,适合规则少、不复杂的场景,直接返回 403,逻辑单一,当需要配合 UA、请求路径等进行控制则无能为力,这时用 geo 和 map 指令会更灵活。
CIDR 网段写法:
192.168.2.0/24表示掩码为 255.255.255.0 的整个网段(即 192.168.2.0 ~ 192.168.2.255),/24是前缀长度,数字越大范围越精确。常见对照:
/8→ 255.0.0.0 → 一个 A 类网,约 1677 万个 IP/16→ 255.255.0.0 → 一个 B 类网,约 65536 个 IP/24→ 255.255.255.0 → 一个 C 类网,256 个 IP
二、geo/map
geo 和 map 的作用都是根据特定条件动态生成变量,从而替代性能较差的 if 语句,作用域为整个 http 块,当拿到请求时,Nginx 会用客户端的 IP 或其他变量查找这个映射,找到则赋予对应的值,找不到则使用默认值,二者区别在于应用场景不同:
-
geo是专门针对 IP 的映射,支持 IP 地址和 CIDR 网段 -
map则是通用的变量映射工具。它可以基于任意源变量(如 URI、User-Agent、Host 等)的值,按照设定的规则生成新的目标变量,如果基于 IP 作映射时只支持 IP 地址,不支持 CIDR 网段
geo/map 的好处在于把判断结果存为变量,可以和其他维度的判断做组合,灵活度远高于 allow/deny。
2.1、使用 geo 实现IP黑名单
geo $remote_addr $is_ip_blocked {
# 默认为0,不限制
default 0;
# 封禁整个 192.168.0.0/16 网段
192.168.0.0/16 1;
192.168.1.0/24 0; # 但 192.168.1.0/24 例外(因为掩码更长,优先)
}
geo 指令根据客户端 IP 地址($remote_addr)建立映射,从上到下进行匹配,且 CIDR 掩码越长越优先,匹配到 IP 将 $is_ip_blocked 设置为 1,否则返回为 0。
如果不指定第一个变量,默认用 $remote_addr。
当 Nginx 部署在 CDN(如 Cloudflare)或反向代理之后时,
$remote_addr拿到的是代理服务器的 IP,而非真实客户端 IP。此时基于$remote_addr的geo封禁会失效——封的全是代理 IP。解决方案是使用 http 块中
ngx_http_realip_module模块:http { real_ip_header X-Forwarded-For; set_real_ip_from 127.0.0.1; real_ip_recursive on; # 其他配置 ... }对应的 IP 映射中引用的变量修改为
$realip_remote_addr:-geo $remote_addr $is_ip_blocked { +geo $realip_remote_addr $is_ip_blocked { # 默认为0,不限制 default 0; }
2.2、使用map实现UA/路径黑名单
map $http_user_agent $is_ua_blocked {
# “~*”:不区分大小写的正则匹配
~*Firefox/[1-5][0-9] 1;
~*Chrome/[5-8][0-9] 1;
default 0;
}
map 指令根据源变量 $http_user_agent(Nginx 内置变量,表示请求头中的 User-Agent 字段)的值,通过正则匹配或精确匹配,映射出目标变量 $is_ua_blocked 的值。map 中正则表达式的匹配顺序为从上到下,首次匹配即返回。
UA 中的正则匹配:
Firefox/[1-5][0-9]:匹配 Firefox 10 ~ Firefox 59([1-5]匹配十位 1 ~ 5,[0-9]匹配个位 0 ~ 9)Chrome/[5-8][0-9]:匹配 Chrome 50 ~ Chrome 89
这些规则封禁的都是非常老旧的浏览器版本,正常浏览器早已不在这些版本范围内。现实中碰到这些 UA 的请求,几乎可以断定是爬虫或扫描器伪装的。
*补充——`$http_变量规则:** Nginx 中请求头字段会自动转为$http前缀变量,字段名中的-转为,全小写。例如User-Agent→$http_user_agent,X-Forwarded-For→$http_x_forwarded_for`。
与此类似的就可以基于请求路径 $request_uri 的变量映射:
map $request_uri $is_path_blocked {
default 0;
~*^/(admin123|webadmin|pma|login|phpeditor|dashboard)/(.*) 1;
~*^/((wp.*)|test(.*)|(php(.*)info)|server(.*)|xmlrpc|cmd|php|install)\.php 1;
~*\.(sql|bak|zip|tar|gz|rar|7z|bak|old|conf|config|env|yml|phpeditor|git)$ 1;
"~^.{301,}$" 1;
}
$request_uri 也是 Nginx 的内置变量,为完整的请求 URI,包含查询参数,不含域名,通过对这部分信息做正则匹配,命中则返回 1。这三条规则分别针对不同类型的恶意请求:
规则 1——恶意目录扫描:
~*^/(admin123|webadmin|pma|login|phpeditor|dashboard)/(.*)
这些路径(phpMyAdmin、后台登录、编辑器等)是最常被试探的目标。(.*) 匹配目录下的任意子路径。
规则 2——恶意 PHP 文件扫描:
~*^/((wp.*)|test(.*)|(php(.*)info)|server(.*)|xmlrpc|cmd|php|install)\.php
WordPress 相关文件(wp-config、wp-login)、phpinfo、xmlrpc 等都是自动化攻击脚本的高频目标。
规则 3——敏感后缀文件:
~*\.(sql|bak|zip|tar|gz|rar|7z|old|conf|config|env|yml|phpeditor|git)$
.sql 数据库备份、.env 环境变量、.git 仓库信息、.conf 配置文件,这些文件一旦泄露,后果严重。$ 锚定 URI 结尾,确保匹配的是文件扩展名。
规则 4——超长 URI:
"~^.{301,}$"
{301,} 表示匹配 301 个字符及以上。正常请求的 URI 极少超过 300 字符,超长 URI 通常来自缓冲区溢出攻击或恶意注入尝试。
注意:正则表达式中如果包含大括号 {}, 则必须用双引号包裹,否则 Nginx 配置解析会报错。
2.3、组合逻辑
将三个维度的判断结果用 : 拼接成一个字符串(如 "0:0:1"),再对这个字符串做正则匹配,实现 OR 逻辑,任意一个为 1,最终结果就是 1。
map "$is_ip_blocked:$is_ua_blocked:$is_path_blocked" $is_blocked {
~1:[01]:[01] 1;
~[01]:1:[01] 1;
~[01]:[01]:1 1;
default 0;
}
如果要实现 AND 逻辑,三个维度全部命中才封禁,只需一条规则:
map "$is_ip_blocked:$is_ua_blocked:$is_path_blocked" $is_blocked {
~1:1:1 1;
default 0;
}
三、完整示例
3.1、黑名单配置
将上面四段 geo/map 单独写成一个配置文件 blacklist(或写在 http 块中):
# ==================== IP 封禁映射 ====================
geo $remote_addr $is_ip_blocked {
# 默认不封禁
default 0;
# ---------- 封禁的 IP 列表 ----------
# 192.168.1.1 1;
#
# ---------- 封禁的网段(CIDR) ----------
# 封禁整个 192.168.0.0/16 网段
192.168.0.0/16 1;
192.168.1.0/24 0; # 但 192.168.1.0/24 例外(因为掩码更长,优先)
}
# ==================== UA 封禁映射 ====================
map $http_user_agent $is_ua_blocked {
~*Firefox/[1-5][0-9] 1;
~*Chrome/[5-8][0-9] 1;
default 0;
}
# ==================== 路径封禁映射 ====================
map $request_uri $is_path_blocked {
default 0;
# 常见恶意扫描路径
# 规则:“~*”:不区分大小写,“^/”:必须以 / 开头
~*^/(admin123|webadmin|pma|login|phpeditor|dashboard)/(.*) 1;
# 特定名称的 PHP 文件
~*^/((wp.*)|test(.*)|(php(.*)info)|server(.*)|xmlrpc|cmd|php|install)\.php 1;
# 特定后缀
~*\.(sql|bak|zip|tar|gz|rar|7z|bak|old|conf|config|env|yml|phpeditor|git)$ 1;
# URI 长度超过 300
"~^.{301,}$" 1;
}
# ==================== 组合逻辑:任意一个为 1 则封禁 ====================
map "$is_ip_blocked:$is_ua_blocked:$is_path_blocked" $is_blocked {
# 当任意一个判定为 "1" 时,结果为 "1"
~1:[01]:[01] 1;
~[01]:1:[01] 1;
~[01]:[01]:1 1;
# 其他情况默认为0,不封禁
default 0;
}
3.2、引入配置
由于 geo/map 块的作用域为 http 块,所以在网站的配置文件 www.conf 的 server 块外使用 include 指令引入配置文件,在 server 配置中引用 $is_blocked 使之生效:
# 引入黑名单配置
include ./blacklist;
server {
listen 80;
server_name example.com;
root /var/www/html;
# 记录黑名单访客的访问日志,表示仅当变量值非 0 时记录日志,正常请求不会写入该日志文件。
access_log /xxx/path/www_blocked.log combined if=$is_blocked;
# 引用$is_blocked,对黑名单访客返回指定状态码
if ($is_blocked) {
return 444;
}
location / {
try_files $uri $uri/ =404;
}
}
if ($is_blocked) 的判断逻辑: Nginx 的 if 会将变量值视为布尔值,0 为假,非零为真。当 $is_blocked = 1 时条件成立,执行 return 444。
444 是 Nginx 私有状态码,表示直接关闭连接而不返回响应,如果爬虫请求密集时可以使用 444 返回码节省流量。
评论 (0)