PAYATHON 2026

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 中的值这条规则的行为
1a2b3c1a2b3c不拒绝,继续处理
1a2b3c3c2b1a返回 HTTP 403

[F] 已经隐含 [L],保留题目要求的 [F,L] 也可以。参见 Apache F 标志说明。

这组规则只处理双方都有符合格式的 foo 时的值不一致问题。如果 Referer 缺失、任一方没有 foo、值为空或不符合字符范围,这条拒绝规则不会触发。是否拒绝这些情况,需要另行制定条件。

如果允许重复的 foo 参数,或需要比较 URL 解码后的值,应先明确参数解析规则,再调整实现,避免与应用实际读取的值不一致。

备注:内容仅供参考。