记一次安全整改,搞清楚options请求这些问题

122 阅读10分钟

阅读指引

本文尝试探索以下几个内容:

  • ①确认问题真实性: 有支持options请求吗,什情况下会产生options请求?
  • ②问题成立的合理性: 建议禁用options请求,并将允许options请求列为安全漏洞的合理依据是什么?
  • ③分析问题-产生的原因: 什情况下会产生options请求。
  • ④分析问题-跨平台差异: Dcloud 5+app的跨平台项目,而iOS端实测发现会发起大量options请求,公众号网页版也会,但是安卓却不会。
  • ⑤解决问题: 如何应对这个所谓“安全漏洞”?options请求的数量,可以缓存优化吗?

一、背景说明

年度渗透测试,测出项目支持options请求,在整改书中将支持options请求列为低位级别安全漏洞,并建议“禁用options请求,仅使用get/post请求”。安全质控部门关于这个低位漏洞的描述是,“0PTIOHS方法可以提供web服务器支持的方法列表,它表示请求有关Reguest uI标识的请求/响应链,可用的通信选项的信息”。

要解决问题,首先要确认问题是否真实存在,然后去分析问题,最后才是解决问题。

以下是我们拿到的安全质控部门发来的原文,分为3个方面:

①漏洞描述:“0PTIOHS方法可以提供web服务器支持的方法列表,它表示请求有关Reguest uI标识的请求/响应链,可用的通信选项的信息”。

②处理建议:“禁用options请求,仅使用get/post请求”。

③漏洞级别:低危漏洞。

让情况变得更复杂的是:项目框架是Dclond 5+app的跨平台项目,项目并没有显性的使用options请求,而iOS端实测发现会发起大量options请求,公众号网页版也会,但是安卓却不会。

软件工程师对于options请求自然是不陌生的,然而将其定位安全漏洞,尽管是低危,也令我感到有些疑惑。

因为在印象中,options请求是预检请求,是 HTTP/CORS 机制的一部分。而HTTP/CORS机制本身就是一种为安全而生的机制。

所以:

①options请求被列为安全漏洞合理吗?依据在哪?

②同一套前端axios请求,为什么安卓webview不产生options请求,web端和iOS端却会?

③这么多options请求日志是哪里来的?get请求也会产生吗?

④options请求的数量,可以通过优化手段减少吗?

二、探索目标

基于这样的疑惑,尝试去明确以下内容:

  • ①确认问题真实性: 有支持options请求吗,什情况下会产生options请求?
  • ②问题成立的合理性: 建议禁用options请求,并将允许options请求列为安全漏洞的合理依据是什么?
  • ③分析问题-产生的原因: 什情况下会产生options请求。
  • ④分析问题-跨平台差异: Dcloud 5+app的跨平台项目,而iOS端实测发现会发起大量options请请求,公众号网页版也会,但是安卓却不会。
  • ⑤解决问题: 如何应对这个所谓“安全漏洞”

三、确认问题真实性:有支持options请求吗,什情况下会产生options请求?

image.png

查看了服务器配置,确实开放了options请求。此外,后台日志也显示有许多options请求,而值得一提的是安卓端没有发现发起options请求,iOS端和公众号网页端都发现有options请求,而且量还不小。

仅是options请求量较大,就可以确认这一问题必须要整改了。由此问题得到确认,接下来要分析问题。

四、建议禁用options请求,并将允许options请求列为安全漏洞的合理依据是什么?

1.漏洞分类:CWE-200:信息泄露

OPTIONS 请求的响应会返回服务器支持的 HTTP 方法(Allow 头)和 CORS 策略(如 Access-Control-Allow-MethodsAccess-Control-Allow-Headers)。攻击者可借此探测服务器能力,策划针对性攻击(如尝试 PUT/DELETE 方法或利用允许的敏感头)。

另外,通过 OPTIONS 响应的特征(如支持的 HTTP 方法、头字段、服务器类型),攻击者可推断服务器版本或框架,利用已知漏洞。

2.防止资源滥用

  • DDoS 攻击媒介
    OPTIONS 请求无需携带有效负载,攻击者可高频发送此类请求,消耗服务器资源(尤其未限制频率或缓存时)。
  • 冗余网络流量
    每个非简单请求(如带自定义头的 POST)均需先发送 OPTIONS 预检请求,增加网络开销。

3.最小化攻击面原则

根据安全最佳实践,应禁用不必要的 HTTP 方法。若业务无需跨域交互,关闭 OPTIONS 请求符合“默认拒绝”安全策略。

以上几点,大概就是安全部门建议禁用options请求的依据。

五、分析问题-产生的原因:什情况下会产生options请求?

但是我们的日常http请求,并没有显性的使用到 options 请求。那么这么多的options请求日志是怎么产生的呢?

options请求发生的场景: 资料显示,OPTIONS 请求是 HTTP/CORS 机制的一部分,用于在浏览器实际发送可能修改服务器状态的请求之前,检查这些请求是否被服务器允许。尽管 GET 请求通常不触发 OPTIONS 请求,但在跨域且涉及特殊请求头的场景中,这种情况可能发生。

我们实测发现,在跨域的测试环境中,post 和 get 请求都会伴随options请求的发生,且在公众号网页端和iOS端都会,唯独安卓端不会。

OPTIONS 请求是 HTTP/CORS 机制的一部分,我们由此推测根本原因有跨域了。

post请求伴随options预检请求,好理解,而 get 也伴随,说明当时的get请求被识别为了“非简单请求”,而比较可能的原因是携带了特殊请求头

六、分析问题-跨平台差异。

在前面的核实问题真实性中,发现Dcloud 5+app的跨平台项目,而iOS端实测发现会发起大量options请请求,公众号网页版也会,但是安卓却不会。

我们要更好的想解决问题,这点得先搞清楚。

首先,网页版(域名不同)和iOS版本(协议不同)好理解,跨域了而且携带了自定义请求头,前后端双方都配置相关跨域支持,产生options预检请求就显得常规了。

而安卓端(协议不同)也会跨域,但却没发起options请求。如果当时三端都出现options预检请求,我想我不会继续探索跨端差异问题。

然后去查阅了资料,看了安卓和iOS的代码,发现安卓webview是可以绕过(危险操作)options预检请求的。

怎么绕过呢?webview 的参数设置提供了一个方法叫 setAllowUniversalAccessFromFileURLs(true),有点类似在早期版本的 chrome 浏览器的跨域设置 --disable-web-security --user-data-dir=C:\MyChromeDevUserData--disable-web-security的效果。

// 获取 WebView 的配置对象,用于设置各项参数
WebSettings ws = webView.getSettings();

// 允许 file 协议页面(如本地 HTML)跨域访问其他域资源
// 警告:生产环境慎用!存在跨域安全风险(CVE-2019-5768)
ws.setAllowUniversalAccessFromFileURLs(true); 

// 允许 WebView 访问本地文件(file:// 协议)
// 风险:可能泄露设备文件(建议限制作用域)
ws.setAllowFileAccess(true); 

当然,iOS 的 webview 也有类似的配置。

所以跨端产生的关于options请求表现差异,是因为 webview 可以设置,允许 file:// 协议页面访问任意域(http/https)(有风险的操作)。

七、解决问题:如何应对这个所谓“安全漏洞”

最后解决问题。

问题是安全部门提出的,他们认为允许options请求有安全风险,建议禁止。

# 在 Apache 主配置或虚拟主机中,全局禁用
# 允许 GET 和 POST,禁止其他方法 
<LimitExcept GET POST> 
    Require all denied 
</LimitExcept>

# 如果需要仅在某个路径下限制方法(如 `/api/`):
<Location "/api/"> 
    <LimitExcept GET POST> 
        Require all denied 
    </LimitExcept> 
</Location>
# 全局禁用非 GET/POST 方法
server {
    listen 80;
    server_name example.com;

    # 全局拦截非 GET/POST 请求
    limit_except GET POST {
        deny all;
    }

    # 其他配置...
}

# 针对特定路径的配置
server {
    listen 80;
    server_name example.com;

    location /api/ {
        # 仅允许 GET/POST,其他方法返回 403
        limit_except GET POST {
            deny all;
        }

        # 其他配置(如代理或静态文件)
        proxy_pass http://backend;
    }
}

所以最简单的就是在服务端和web应用服务器,直接禁止options请求。

web 端原本可以采用反向代理,然后禁用options请求,解决问题。但现实是,这是一套代码生成多端的跨端设计,而且共用同一套服务接口。禁用options请求,在安卓端和iOS端会导致请求无法正常进行,因为这边是跨域(file:// 协议本地html 和 htpps 请求接口,协议不同)的。

所以如果要禁用options请求,有一种方案是将在网页端发起的axios请求,改为使用原生的OkHttp 作为 HTTP 客户端。但这会增加大量工作量,而且违背一套代码生成多端的初衷,所以这不适用。

那么接下来,考虑采取的措施:

  • 精细化 CORS 配置,限定更细致的允许跨域范围,降低信息泄露和跨域攻击风险。
  • 执行预检请求缓存优化策略,减少 OPTIONS 请求频率。
# 要通过 **预检请求缓存优化** 来减少 OPTIONS 请求频率,
# 核心是配置 `Access-Control-Max-Age` 响应头,
# 告知浏览器缓存预检结果的有效期。

<IfModule mod_headers.c>
    # 处理 OPTIONS 预检请求
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule ^(.*)$ $1 [R=204,L]

    # 设置 CORS 头
    Header set Access-Control-Allow-Origin "https://your-domain.com"
    Header set Access-Control-Allow-Methods "GET, POST, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type"
    Header set Access-Control-Max-Age "600"  // 缓存 10 分钟
</IfModule>

# 注意事项:
# ① 所有现代浏览器均支持 `Access-Control-Max-Age`,
# 但需注意,IE ≤ 11,最大缓存时间固定为 600 秒。
# ② Access-Control-Max-Age 会缓存 CORS 策略,
# 这意味着更改缓存策略可能不会马上生效,当然这通常影响不大。
# 值得一提的是 Access-Control-Max-Age 不会导致数据旧化,
# 常规 GET/POST 的实际响应数据,由 Cache-Control 控制。

写在最后

小结:

① OPTIONS 请求是 HTTP/CORS 机制的一部分,只有跨域场景才会发生 options 请求。

② get 请求也可能会伴随 options 预检请求(例如自定义特殊请求头)。

③ webview 中也会像常规浏览器那样执行 CORS 机制。

④ webview 提供了方法允许跨域访问其他域资源(生产环境慎用)。

⑤ 减少 OPTIONS 请求频率,可以在应用服务器侧,执行预检请求缓存优化策略。

⑥ 降低信息泄露和跨域攻击风险,可以通过精细化 CORS 配置,限定更细致的允许跨域范围实现。

⑦ 关于缓存,值得一提的是 Access-Control-Max-Age 不会导致数据旧化,其缓存的是 CORS 策略,常规 GET/POST 的实际响应数据缓存策略,由 Cache-Control 控制。

⑧ 解决 webview 加载本地html和http请求跨域的方案之一是:将在网页端发起的axios请求,改为使用原生的 OkHttp 作为 HTTP 客户端。

⑨ options 请求被列为安全漏洞的可能原因:信息泄露(CWE-200)、防止资源滥用、最小化攻击面原则。

未尽事宜: 对于Dclond 5+app的跨平台项目,有没有办法不跨域呢?

(主要是指安卓和iOS端本地加载html资源用file:// 协议,而接口则是 http 协议跨域场景。如果给html搭建一个和接口同源的服务器,流畅度会下降,如果改为使用原生端的http发请求,改造量大而且和其他端的请求就不是同一套代码了,虽然webview有允许跨域的配置但那本身就是风险操作。)