cross-origin-cors-reference/cors-misconfig-scanner
GitHub: cross-origin-cors-reference/cors-misconfig-scanner
一款零依赖的 CORS 错误配置扫描器,通过变异 Origin 头自动检测并评级自有机接口的跨域授权缺陷,附带修复建议。
Stars: 0 | Forks: 0
# cors-misconfig-scanner
一个无依赖的 CORS 错误配置扫描器,专为扫描**您拥有的** endpoint 而设计。它会发送带有变异 `Origin` header 的普通 HTTP 请求,读取响应头,并告诉您服务器存在哪些 origin 验证漏洞、每个漏洞的严重程度,以及需要进行哪些精确修改。
仅需 Python 3.9+,仅使用标准库,无需构建步骤,无需额外安装任何内容。
## 目录
- [开发初衷](#why-it-exists)
- [工作原理](#how-it-works)
- [错误配置分类](#the-misconfiguration-classes)
- [严重性评级标准](#severity-rubric)
- [环境要求](#requirements)
- [运行方式](#running-it)
- [完整参数参考](#full-flag-reference)
- [演示示例](#worked-examples)
- [退出代码](#exit-codes)
- [输出格式](#output-formats)
- [CI 集成与 GitHub Action](#ci-integration-and-the-github-action)
- [局限性及刻意不做的事](#limitations-and-what-it-deliberately-does-not-do)
- [误报及其排查方法](#false-positives-and-how-to-triage-them)
- [Python API](#python-api)
- [仓库结构](#repository-layout)
- [延伸阅读](#further-reading)
- [贡献指南](#contributing)
- [许可证](#licence)
## 开发初衷
CORS 看似只需两行配置的修改,但实际上它是一个授权决策。`Access-Control-Allow-Origin` header 指明了谁被允许从其他人的浏览器标签页内读取您的响应,而 `Access-Control-Allow-Credentials: true` 则表示他们可以在以受害者身份登录时执行此操作。如果 origin 校验出错,互联网上的任何网站都能读取您经过身份验证的 API 响应。
这些错误极其一致且乏味,它们都源于将 `Origin` header 作为*字符串*而不是*结构化 origin* 进行验证:
```
// every one of these is wrong
if (origin.endsWith('example.com')) allow(origin);
if (origin.startsWith('https://example.com')) allow(origin);
if (origin.includes('example.com')) allow(origin);
if (/https:\/\/.*example.com/.test(origin)) allow(origin);
res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // just echo it
```
这些漏洞能在代码审查中存活下来的原因是它们*确实有效*。合法 origin 被允许,应用程序正常运行,测试通过。在有人进行检查之前,没有任何东西会告诉您 `https://attacker-example.com` 和 `https://example.com.attacker.tld` 也被允许访问。这个扫描器就是自动执行这项检查的工具,并根据特定组合的实际可利用性对其严重性进行分级。
## 工作原理
对于每个目标 URL:
1. 将 URL 解析为 scheme、host 和 port,并从 host 推导出探测集合。不适用的探测会被跳过,而不是作为无意义的请求发送(纯文本目标不会进行 scheme 降级探测;没有点号分隔的 host 不会进行未转义点号探测)。
2. 每个探测发送一个请求,**每个目标内顺序执行**,每个请求带有不同的 `Origin` header。还会发送一个额外的基准请求,该请求完全不包含 `Origin` header,从而可以将静态响应头与根据请求计算得出的响应头区分开来。
3. 它记录每个响应的状态行以及所有的 `Access-Control-*` 和 `Vary` header。响应体仅读取到足以清空 socket 的程度,然后将其丢弃。
4. 分析引擎 —— 一个对这些响应进行处理的纯函数,无 I/O 操作 —— 决定哪些探测被*反射*(发送的确切 origin 原样返回在 `Access-Control-Allow-Origin` 中),是否允许凭证,响应是否随 `Origin` 变化,并据此对每个发现进行评级。
5. 结果会被渲染为文本、JSON、SARIF 或 Markdown 格式。
当 ACAO 的值与发送的 origin 在不区分大小写并忽略尾部斜杠的情况下相等时,即算作反射(reflected)—— 服务器通常会将其回显的值转为小写或添加斜杠,这两种情况仍然属于反射。`Access-Control-Allow-Origin: *` 明确*不*被视为反射;它有其专门的评级规则。
请求刻意保持简单:不跟随重定向(30x 响应会将探测转移到具有不同 origin 的服务器上,并针对错误的服务器产生结果),不安装 cookie jar,并且 `--header` 拒绝 `Cookie`、`Authorization`、`Proxy-Authorization` 和 `Origin`,以防扫描器意外成为经过身份验证的客户端。
并发是**仅在不同目标之间进行的**。无论 `--concurrency` 设置多高,单个 host 永远不会同时受到两个探测的访问。
### 单次探测剖析
```
GET /v1/me HTTP/1.1
Host: api.example.com
Origin: https://api.example.com.attacker.tld
User-Agent: cors-misconfig-scanner/1.0 (+defensive CORS audit; header-inspection only)
```
```
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://api.example.com.attacker.tld
Access-Control-Allow-Credentials: true
```
服务器回显了一个 `example.com` 无人控制的 origin,并表示欢迎使用凭证。这是一个严重级别的发现,上面的两个 header 就是全部证据:不需要也不尝试进行利用,就能确认这一点。
## 错误配置分类
下方的每个探测代表一个 `Origin` header 值。`TARGET` 代表目标的 host,`RANDOM` 代表一个新的随机标签。
| 规则 id | 发送的 Origin | 命中含义 |
| --- | --- | --- |
| `reflect-arbitrary` | `https://RANDOM.example.net` | 服务器回显任何 origin。白名单不存在或被完全绕过。 |
| `null-origin` | `null` | 字面字符串 `null` 受信任。 |
| `suffix-trap` | `https://TARGET.attacker.tld` | `origin.startsWith(target)` 的特征 —— 攻击者注册了一个以您的名称*开头*的域。 |
| `prefix-trap` | `https://attacker-TARGET` | `origin.endsWith(target)` 或 `target in origin` 的特征。 |
| `startswith-trap` | `https://TARGETevil.com` | 未锚定前缀比较的特征。 |
| `unescaped-dot` | `https://apixexample.com` (对于 `api.example.com`) | 正则表达式中包含未转义 `.` 的特征。 |
| `special-char-origin` | `https://TARGET_.attacker.tld` | 验证器对浏览器不处理的字符进行了规范化或剔除。 |
| `subdomain-wildcard` | `https://RANDOM.TARGET` | 每个子域都受信任 —— 实际上相当于 `*.your-domain`。 |
| `scheme-downgrade` | `http://TARGET` (仅限 https 目标) | TLS endpoint 信任纯文本 origin。 |
| `port-confusion` | `https://TARGET:1337` | port 不包含在比较范围内。 |
| `wildcard-acao` / `wildcard-with-credentials` | 从任何响应中派生 | `Access-Control-Allow-Origin: *`,单独存在或与凭证同时存在。 |
| `vary-origin-missing` | 派生 | ACAO 根据 `Origin` 计算,但缺少 `Vary: Origin`。 |
| `acao-duplicate` | 派生 | 两个层级都写入了 `Access-Control-Allow-Origin`。 |
| `credentials-enabled` | 派生 | 上下文:该 endpoint 允许经过验证的跨域读取。 |
### 各分类的实际利用方式(概念性)
**带有凭证的任意反射。** 攻击者发布一个页面。已登录的受害者访问该页面。该页面向您的 API 发起经过验证的跨域请求;您的服务器回显了攻击者的 origin 并表示允许使用凭证;因此,浏览器将响应体交给了攻击者的 JavaScript。受害者通过该 endpoint 能读取的所有内容 —— 个人资料数据、嵌入在 JSON 中的 token、内部标识符 —— 现在攻击者的页面都能读取。您的域上不需要存在 XSS。受害者只需访问一个页面即可。
**不带凭证的反射。** 流程相同,但请求不携带 cookie,因此攻击者只能读取匿名客户端能看到的内容。这对于公共 API 通常没什么影响,但对于访问控制依赖于网络位置的内部 API 却极具价值 —— 一个信任“你连得上我,说明你在内网”的内网服务,可以通过员工浏览器被读取。
**`null` origin。** `null` 是浏览器在沙盒化 iframe、`file://` 文档以及某些跨域重定向链中发送的内容。攻击者的页面会嵌入 `
标签:CORS扫描, LNA, Python, 无后门