PAYATHON 2026

如何修复 PCI DSS 扫描中的 HTTP Security Header Not Detected

支付老李

结论

这个问题与 IIS 的 CGI 功能没有关系。扫描器将漏洞归到 CGI 类别,只是其内部分类方式。真正需要处理的是:为 HTTPS 站点实际返回的所有响应 添加相应的安全响应头。

Windows Server 2012 R2 通常使用 IIS 8.5,可以在站点根目录的 web.config 中统一配置以下响应头:

  • X-Frame-Options
  • Content-Security-Policy
  • X-Content-Type-Options
  • Strict-Transport-Security
  • X-XSS-Protection(主要用于兼容旧扫描规则)

配置完成后,需要从外部访问 https://xxx.abc.com/,检查实际返回的响应头。IIS 管理器中显示了配置项,不代表重定向、错误页、反向代理或应用程序生成的响应一定包含这些响应头。

IIS 8.5 配置示例

将以下内容合并到网站根目录现有 web.config 的 <configuration> 节点中,不要再创建第二个 <system.webServer> 节点。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <remove name="X-Frame-Options" />
        <remove name="X-XSS-Protection" />
        <remove name="X-Content-Type-Options" />
        <remove name="Content-Security-Policy" />
        <remove name="Strict-Transport-Security" />

        <add name="X-Frame-Options" value="SAMEORIGIN" />
        <add name="X-XSS-Protection" value="1; mode=block" />
        <add name="X-Content-Type-Options" value="nosniff" />
        <add name="Content-Security-Policy"
             value="default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self' https://payment-provider.example;" />
        <add name="Strict-Transport-Security"
             value="max-age=31536000" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

https://payment-provider.example 只是占位符,必须替换成支付服务商明确要求的真实域名。如果支付流程需要向多个银行、3D Secure ACS 页面或支付网关提交表单,应将这些地址逐一加入 form-action,不能直接使用示例配置上线。

先使用 <remove>,再使用 <add>,可以减少服务器级配置与站点级配置重复,避免最终返回两个同名响应头。

各响应头应该怎样选择

X-Frame-Options

如果网站完全不需要通过 <iframe> 嵌入其他页面,可以使用:

X-Frame-Options: DENY

如果只允许同源页面嵌入,可以使用:

X-Frame-Options: SAMEORIGIN

不要使用已经废弃且浏览器支持不一致的 ALLOW-FROM。如果需要精确允许多个外部来源,应使用 CSP 的 frame-ancestors。

这两个指令的作用方向不同:

  • frame-ancestors:指定哪些网站可以嵌入当前页面。
  • frame-src:指定当前网站可以加载哪些 iframe。

3D Secure 支付经常涉及 iframe、弹窗、跨域表单或跳转,因此是否适合使用 frame-ancestors 'self',需要结合支付服务商的集成文档判断。

Content-Security-Policy

不能只为了让扫描器检测到响应头,就随意填写 CSP。策略过严可能阻止现有的 JavaScript、CSS、字体、验证码、统计脚本和支付页面正常工作。

上面的策略可以作为一个较严格的起点:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self' https://payment-provider.example;

如果页面使用了内联脚本、CDN、第三方支付脚本或 iframe,需要根据实际来源添加 script-src、style-src、img-src、connect-src、frame-src 等指令。例如:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.example.com; frame-src 'self' https://secure-payment.example; frame-ancestors 'self'; object-src 'none'; base-uri 'self'; form-action 'self' https://secure-payment.example;

不要为了省事配置宽泛的 *,也应尽量避免长期使用 'unsafe-inline' 和 'unsafe-eval'。

正式启用前,可以先通过以下响应头观察策略的影响:

Content-Security-Policy-Report-Only: ...

需要注意,Content-Security-Policy-Report-Only 不等同于正式的 Content-Security-Policy。如果扫描器明确要求后者,报告模式通常无法消除该扫描项。

X-Content-Type-Options

使用固定值:

X-Content-Type-Options: nosniff

同时要确保 CSS、JavaScript、字体和下载文件返回正确的 Content-Type。启用 nosniff 后,浏览器可能拒绝加载 MIME 类型错误的静态资源。

Strict-Transport-Security

IIS 8.5 可以通过自定义响应头添加 HSTS:

Strict-Transport-Security: max-age=31536000

这个响应头只能通过 HTTPS 返回。浏览器会在 max-age 有效期内强制使用 HTTPS,因此启用前要确认:

  • 整个网站都能通过 HTTPS 正常访问;
  • TLS 证书有效,续期机制可靠;
  • HTTP 请求会跳转到 HTTPS;
  • 没有业务入口仍然依赖 HTTP。

首次部署时,可以先设置较短的有效期,例如:

Strict-Transport-Security: max-age=300

确认没有问题后,再提高到一年:

Strict-Transport-Security: max-age=31536000

只有所有子域名都能长期使用 HTTPS 时,才能加入:

Strict-Transport-Security: max-age=31536000; includeSubDomains

不要贸然添加 preload。站点进入浏览器预加载列表后,其影响比普通 HSTS 更难撤销。

X-XSS-Protection

为了兼容旧版扫描器,可以配置:

X-XSS-Protection: 1; mode=block

现代浏览器已经淘汰了这个响应头。它不能替代输出编码、输入校验和 CSP。如果扫描器仍将其列为必检项,可以保留;如果扫描机构允许提交风险说明或申请规则豁免,也可以向扫描服务商确认具体判定标准。

通过 IIS 管理器配置

如果不方便编辑 web.config,也可以在 IIS 管理器中配置:

  1. 打开 inetmgr。
  2. 选中具体网站,不要误选服务器级节点。
  3. 打开 HTTP Response Headers。
  4. 依次添加上述响应头及对应值。
  5. 检查是否已有同名响应头,避免重复。
  6. 重新访问网站并验证实际响应。

修改 web.config 通常会触发应用重新加载,不必重启整台服务器。如果配置没有生效,可以回收对应的应用程序池,但应选择业务允许的时间窗口。

验证实际响应

可以从服务器外部执行:

curl -I https://xxx.abc.com/

预期至少能看到类似结果:

HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self' https://payment-provider.example;
Strict-Transport-Security: max-age=31536000

还需要分别检查:

curl -I https://xxx.abc.com/
curl -I https://xxx.abc.com/login
curl -I https://xxx.abc.com/payment
curl -I https://xxx.abc.com/nonexistent-page

检查范围应包括正常页面、登录跳转、支付入口、404/500 错误页,以及返回 301、302、401、403 状态码的响应。PCI 扫描器访问的是 /,因此不能遗漏站点根路径的响应。

如果网站前面还有 CDN、WAF、负载均衡器或反向代理,应以公网最终返回的响应为准。这些中间层可能删除、覆盖或重复添加响应头。

常见失败原因

完成配置后仍然无法通过扫描,常见原因包括:

  • 响应头加到了错误的网站或服务器节点。
  • 被扫描的主机名绑定到了另一个 IIS 站点。
  • / 返回重定向,但重定向响应没有安全头。
  • CDN、WAF 或反向代理修改了源站响应。
  • ASP.NET 代码或其他 IIS 模块删除、覆盖了响应头。
  • 同名响应头出现多次,而且取值冲突。
  • HSTS 被错误地加在 HTTP 响应上,HTTPS 响应反而没有。
  • CSP 只配置了 Content-Security-Policy-Report-Only。
  • 扫描服务仍在使用缓存结果,还没有重新扫描。

最终能否通过扫描,取决于具体 PCI 扫描服务商使用的规则版本。上述配置可以解决“响应中未检测到安全头”的技术问题,但 CSP 白名单仍须根据实际 ASP.NET 和 3D Secure 支付流程调整。上线前,应在测试环境中完整验证支付、回调、验证码和登录流程。

备注:内容仅供参考。