Skip to content

Apache URL 包含 %3F 导致 403 错误的解决方法

问题描述

当 Apache 服务器升级到较新版本(特别是 2.4.52+)后,许多用户遇到 403 Forbidden 错误。该问题通常出现在以下场景:

  • URL 中包含编码的问号字符 %3F(例如通过 PHP 的 rawurlencode() 生成)
  • 用户提交的表单字段值包含问号并被用于 GET 请求
  • 重定向 URL 中包含编码参数

错误示例:

php
<!-- $test 包含 "?" 时触发 403 -->
<a href='test?a=<?= rawurlencode($test); ?>'>test</a>

错误日志中可见:
AH10508: Unsafe URL with %3f URL rewritten without UnsafeAllow3F

根本原因

该问题源于 Apache 修复的安全漏洞 (CVE-2024-38474):

  1. Apache 默认禁止 URL 中出现编码问号 %3F
  2. rawurlencode() 会将 ? 转换为 %3F
  3. 新版 Apache 将含 %3F 的 URL 视为潜在攻击而直接拒绝

安全警告

禁用此防护可能暴露服务器风险,相关漏洞将在 Black Hat 大会上公开细节。

解决方案

✅ 推荐方案:修改应用逻辑(优先推荐)

重构 URL 结构避免传递带问号的参数:

问题代码

php
<a href='test?a=<?= rawurlencode($test); ?>'>测试链接</a>

解决方案 1:参数分离法

php
<?php
// 将参数分离到不同键值
$params = http_build_query(['path' => '/path/to/resource', 'size' => 'large']);
?>
<a href='test?a=<?= rawurlencode($params) ?>'>安全链接</a>

解决方案 2:路径参数法
Bad: example.com/login?returnto=/cats/long-hair%3fsize=large
Good: example.com/login?returnto=/cats/long-hair&returntoparams=size%3dlarge

⚠️ 临时方案:添加 UnsafeAllow3F 标志(需谨慎)

.htaccess 中为重写规则添加标志:

apache
# 修改前
RewriteRule ^ index.php?p=$1 [QSA,L]

# 修改后(添加 UnsafeAllow3F)
RewriteRule ^ index.php?p=$1 [QSA,L,UnsafeAllow3F]

注意事项

  1. 仅适用于 Apache < 2.4.60 的过渡方案
  2. 需评估安全风险(可能暴露 CVE-2024-38474)
  3. 升级 Apache 后应尽快移除此设置

🔧 其他解决方案

方案 1:升级到 Apache 2.4.60+

bash
# Ubuntu 示例
sudo apt update && sudo apt install apache2

优势:

  • 修复 CVE-2024-38475(评分 9.1/10)
  • 无需使用 UnsafeAllow3F
  • 获得完整安全补丁

方案 2:修复重写规则(Craft CMS 示例)

apache
# 修改前(触发问题)
RewriteRule (.+) index.php?p=$1 [QSA,L]

# 修改后(推荐)
RewriteRule (.+) index.php [L]

通过在 PHP 中解析 $_SERVER['REQUEST_URI'] 替代查询参数。

方案 3:HTML 实体替换(特殊场景)

php
<?php
// 先替换 ? 为实体再编码
$encoded = rawurlencode(str_replace('?', '&#63;', $test));
?>
<a href='test?a=<?= $encoded ?>'>测试链接</a>

最佳实践

  1. 立即升级 Apache ≥ 2.4.60
  2. 审计重写规则
    bash
    grep -r "RewriteRule" /etc/apache2/
  3. 重构含 ? 的 URL 参数
    • 关键参数置于路径中 example.com/article/123
    • 复杂参数使用 JSON 编码 + base64
      php
      $data = base64_encode(json_encode(['return' => '/path?param=value']));

长期策略

» 避免在 URL 参数中传递完整 URL(改用参数映射)
» 对用户输入执行严格的允许字符白名单验证
» 使用专门的 URL 生成库(如 Symfony 的 Router)

总结

Apache 的安全更新导致含 %3F 的 URL 被阻止,根本解决方案是:

  1. 升级 Apache 到 2.4.60+ 版本
  2. 修改应用程序避免传递编码问号
  3. 精简重写规则(移除不必要查询字符串)

临时方案仅适用于无法立即升级的环境,且应及时过渡到安全方案。

关键参考