Apache mod_rewrite 如何比较 QUERY_STRING 与 HTTP_REFERER 中的参数?
先提取两个 foo 参数的值,拼成一个待比较的字符串,再用正则反向引用判断它们是否相等。两个值不一致时,执行 RewriteRule ^ - [F,L],返回 HTTP 403。
配置示例
以下写法假设每个查询串中最多出现一个 foo,且它的值是非空的 [0-9a-z]+ 字符串:
RewriteEngine On
RewriteCond %{QUERY_STRING} (?:^|&)foo=([0-9a-z]+)(?:&|$)
RewriteCond %1@%{HTTP_REFERER} ^([0-9a-z]+)@[^?]*\?(?:[^&]*&)*foo=([0-9a-z]+)(?:&|$)
RewriteCond %1@%2 !^([0-9a-z]+)@\1$
RewriteRule ^ - [F,L]
把这组条件和规则放在需要保护的配置范围内,并置于可能提前结束处理的规则之前。如果使用 .htaccess,服务器必须允许其中使用这些指令。
三个条件如何配合
第一条条件从 QUERY_STRING 中提取 foo 的值,保存为 %1。(?:^|&) 和 (?:&|$) 用于限定参数边界,避免把其他参数名中的 foo 或参数值的一部分当成目标。
第二条条件先展开 %1@%{HTTP_REFERER}。例如,得到的待匹配字符串可能是:
1a2b3c@https://example.com/page?foo=3c2b1a
这条条件重新捕获两个值:第一个捕获组是请求中的值,第二个捕获组是 Referer 查询部分中的值。因此,进入第三条条件时,%1@%2 就是:
1a2b3c@3c2b1a
第三条条件中的 ^([0-9a-z]+)@\1$ 只匹配两侧完全相等的情况。前面的 ! 将结果取反,因此只有两侧不相等时才执行拒绝规则。这里用 @ 作分隔符,是因为允许的参数值不包含它。
为什么同时使用 %1 和 \1
两者作用不同:
%1、%2是 mod_rewrite 对前一个匹配成功的RewriteCond捕获组的引用,可在下一条条件的TestString中展开。\1是当前正则表达式内部的反向引用,用来要求后半部分与当前第一个捕获组相同。
普通正则形式的 CondPattern 不会像 TestString 那样展开 %1,因此不能直接在右侧正则中写 foo=%1 来完成动态比较。需要先把两个值拼到左侧,再进行比较。参见 Apache RewriteCond 文档。
结果与适用边界
按上述规则推导,题目中的两个例子分别得到:
| QUERY_STRING 中的值 | HTTP_REFERER 中的值 | 这条规则的行为 |
|---|---|---|
1a2b3c | 1a2b3c | 不拒绝,继续处理 |
1a2b3c | 3c2b1a | 返回 HTTP 403 |
[F] 已经隐含 [L],保留题目要求的 [F,L] 也可以。参见 Apache F 标志说明。
这组规则只处理双方都有符合格式的 foo 时的值不一致问题。如果 Referer 缺失、任一方没有 foo、值为空或不符合字符范围,这条拒绝规则不会触发。是否拒绝这些情况,需要另行制定条件。
如果允许重复的 foo 参数,或需要比较 URL 解码后的值,应先明确参数解析规则,再调整实现,避免与应用实际读取的值不一致。
备注:内容仅供参考。
