Apache 2.4:如何用expr/ipmatch匹配非REMOTE_ADDR的IP头?
CDN后基于HTTP头的IP段封禁解决方案
问题背景
我的Web服务器部署在CloudFront CDN之后,客户端IP通过CloudFront-Viewer-Address HTTP头传递。目前已实现单IP封禁,规则如下:
RewriteCond %{HTTP:CloudFront-Viewer-Address} ^123\.45\.67\.89(.*)$ RewriteRule ^(.*)$ - [F,L]
注:规则里的(.*)$是为了匹配该头中可能包含的端口。访问日志里这个头带端口(比如127.0.0.1:1234),但重写日志里看不到端口。
CGI程序显示的头值示例包含IPv4和IPv6带端口的情况:
HTTP_CLOUDFRONT_VIEWER_ADDRESS --> 0000:0000:0000:0000:0000:0000:0000:0001:52325 HTTP_CLOUDFRONT_VIEWER_ADDRESS --> 127.0.0.1:45629
尝试用-ipmatch封禁IP段(替换变量为%{HTTP:CloudFront-Viewer-Address})失败,不确定是-ipmatch仅支持%{REMOTE_ADDR}还是端口导致匹配失败。
核心原因
-ipmatch要求匹配的是纯IP地址(不含端口),而CloudFront-Viewer-Address头包含端口号(IPv4为IP:端口格式,IPv6为完整IPv6地址:端口格式),直接用该头变量匹配会导致规则失效。
有效解决方案
通过两步Rewrite规则先剥离端口,提取纯IP后再用-ipmatch匹配网段:
通用规则(支持IPv4/IPv6)
# 提取CloudFront-Viewer-Address中的纯IP,存入环境变量VIEWER_IP RewriteCond %{HTTP:CloudFront-Viewer-Address} ^([^\:]+)(:*.*)$ RewriteRule ^ - [E=VIEWER_IP:%1] # 匹配目标IP段,返回403 Forbidden RewriteCond expr "%{ENV:VIEWER_IP} -ipmatch '123.45.67.0/24'" RewriteRule ^ - [F,L]
规则细节说明
- 端口剥离逻辑:
正则^([^\:]+)(:*.*)$会捕获第一个冒号前的所有内容:- 对于IPv4地址(如
127.0.0.1:45629),第一个冒号前就是完整的IPv4地址 - 对于IPv6地址(如
0000:0000:0000:0000:0000:0000:0000:0001:52325),最后一个冒号是端口分隔符,前面的部分是完整的IPv6地址,正则会正确捕获到IPv6地址部分
- 对于IPv4地址(如
- 环境变量传递:用
E=VIEWER_IP:%1把捕获的纯IP存入环境变量,后续规则直接调用%{ENV:VIEWER_IP}即可 - 网段匹配:
-ipmatch支持标准的CIDR格式网段,包括IPv4和IPv6
验证与排查
- 确保Apache版本为2.4及以上(
-ipmatch是Apache 2.4新增的表达式功能) - 开启重写日志(在Apache配置中添加
RewriteLogLevel 3),查看VIEWER_IP变量是否正确提取了纯IP,以及匹配过程是否正常
扩展:封禁IPv6网段
只需替换-ipmatch后的网段为IPv6 CIDR即可,例如:
RewriteCond expr "%{ENV:VIEWER_IP} -ipmatch '2001:db8::/32'" RewriteRule ^ - [F,L]
内容的提问来源于stack exchange,提问作者Special Monkey
相关产品推荐
相关产品推荐

