Clash 检测工具的原理

mikewang · 2024-9-30 04:02:41 · 92 次点击
我在 [/t/1076579](/t/1076579) 给出了 Clash 检测的在线工具,有评论希望我能说明以下其中的原理。

对此比较感兴趣的,可以阅读一下本文。

---

# 1. 基于跨域缺陷的利用

首先,需要了解两个术语:「[同源策略]( https://zh.wikipedia.org/zh-cn/%E5%90%8C%E6%BA%90%E7%AD%96%E7%95%A5)」和「[跨域资源共享]( https://zh.wikipedia.org/zh-cn/%E8%B7%A8%E4%BE%86%E6%BA%90%E8%B3%87%E6%BA%90%E5%85%B1%E4%BA%AB)」。

## 同源策略( Same-origin Policy )

当 B 网站的脚本,想请求 A 网站的资源时,浏览器的「同源策略」会禁止这一行为:防止 B 网站伪造您的请求,同时防止您在 A 网站上隐私的泄露。

> **举例:**  
> 在 www.google.com 下按 F12 ,输入 `await fetch("https://bing.com")` 回车,然后你会看到一条报错信息。浏览器默认阻止这一行为。

## 跨域资源共享( CORS )
而这一限制有时也会过于严格,因为 A 网站和 B 网站可能是来自同一家。因此 A 网站可以设置一个列表,允许特定的网站去访问它的数据。这就是「跨域资源共享」,这个列表就是「 Access-Control-Allow-Origin 」。

> **举例(不好的例子):**  
> Clash / Clash Meta 核心设置了「`Access-Control-Allow-Origin: *`」,通配符允许了所有网站的脚本都能调用它。  
> 假设 9090 是您的 Clash API 端口( Clash Verge 默认是 9097 )。在 www.google.com 下按 F12 ,输入 `await fetch("http://127.0.0.1:9090")` 回车,你会发现请求成功了。

## Clash 为什么这么做?

Clash 系列核心没有用户界面( GUI ),但它可以在本地运行一个 HTTP 服务,方便各种 GUI 通过 HTTP 请求调用它的接口。

其中一些 GUI 以网页形式呈现,例如「[https://d.metacubex.one/]( https://d.metacubex.one/)」。Clash 系列核心必须开一个“后门”,也就是允许跨域资源访问,否则这些 GUI 将无法运作。

不过,这个“后门”显然开得过大了。应该增加一个配置项,只允许特定网站,而不应使用 `*` 通配符。

## 怎么利用呢?

无密码保护的情况下,任意网站调用 `await fetch("http://127.0.0.1:{{port}}")` 就能利用。  
常见的端口有 `9090` 和 `9097`。如果不是,还可以遍历 1 - 65535 全部尝试一次。  

成功利用可以获得对 Clash 核心的全部操控权限,包括获取服务器列表,修改代理规则等。


# 2. 基于已知路径的识别

上一节中,能利用的前提是「无密码保护」。有密码的情况下,访问接口会得到 `401` 的错误响应。单从 `401` 错误,没有证据表明用户在使用 Clash 。但我们可以通过已知路径,去识别是否为 Clash 。

## Clash 的接口路径

在源码 `/hub/route/server.go` 中,可以获得接口的路径:
- `/logs`
- `/traffic`
- `/memory`
- `/version`
- ...

明显的特征是:访问这些路径,会得到 `401` 响应,而访问其他路径则会得到 `404`。

另外,不同的 Clash 版本,支持的 API 也不完全相同。通过探测支持的 API (返回 `401` 而不是 `404`),我们能进一步推断出 Clash 的版本号。

## 利用前提

利用的前提依然是上一节的跨域缺陷,因为如果不具备这一条,请求会直接出错失败,而不会得到 HTTP 响应码。


# 3. 基于代理端口的识别

前两节都是针对 API 端口的利用。我们还可以通过检测常见的代理端口(默认的 `7890` 和 Clash Verge 默认的 `7897`),判断用户是否在使用 Clash 。

## 普通端口的情况

对于普通的端口,不总是能碰到跨域缺陷,甚至可能都不是 HTTP 端口。对于非 HTTP 端口,不论怎么请求肯定都会是失败的。

## 耗时检测

参考以下 JavaScript 函数:
```javascript
async function portTime(port) {
    const st = performance.now();
    try {
        await fetch("http://127.0.0.1:" + port);
    } catch (error) {}
    const et = performance.now();
    return et - st;
}
```
该函数可以检测访问一个端口的耗时,并忽略失败。

一般情况下,用户本地的端口大多处于关闭状态。对于关闭状态的端口,耗时均是差不多的。我们可以随机抽取几个端口进行检测,认为是关闭端口的耗时。

如果 `7890` 或 `7897` 端口的访问耗时明显区分于抽选端口的耗时,我们可以推测 Clash 代理的端口是打开的。

## 耗时检测为什么可行?

上文提到过,只有 `Access-Control-Allow-Origin` 允许,才能进行跨域资源的访问,否则会出错。

实际上,出错不意味着没有请求发生。浏览器首先需要发送 HTTP OPTIONS 请求,才能知道 `Access-Control-Allow-Origin` 的情况。这个请求称为「 Preflight request 」。跨域检测通过之后,浏览器才会再去执行脚本指定的 HTTP 请求。

因此,即便浏览器报错拦截,实际上还是有请求发生了。这就是耗时检测的原理。

## 可利用情况

在 Windows 平台,操作系统收到 TCP RST 报文后,并不会立即认为端口关闭,而会重试 2 秒后返回错误。因此对于耗时检测,耗时两秒左右的端口是关闭的,毫秒级别耗时的端口是打开的。

在 macOS 和 Linux 平台,情况则相反,端口关闭的耗时比端口打开的短。不过由于区分度太小,准确性较低。
举报· 92 次点击
登录 注册 站外分享
10 条回复  
codehz 初学 2024-9-30 08:28:13
其实还有一个保护叫做 PNA - Private Network Access ,防止处于相对更公共 ip 地址范围的网页访问更私有的网址,甚至连导航都不行
只不过有一个小问题:它默认允许访问 127.0.0.1
totoro625 小成 2024-9-30 08:29:43
设置密码,改默认端口即可,最好改到其他软件的常用端口上,如 22/3389 这类

PS:自己的配置文件自定义了端口、密码、证书加密,然后 GUI 客户端帮我删除了这些设置。
我只想要一个便捷的 TUN 功能。
wjx0912 小成 2024-9-30 08:38:51
有点慌。就直接说吧,clash 这个端口问题有木有办法解决?
est 小成 2024-9-30 08:40:24
其实给子域名绑一个 ip 指向 127.0.0.1  也不跨域。
leokun 小成 2024-9-30 08:46:33
clash 应该强制同源,仅可使用 「非浏览器客户端」打开控制端口,例如自带的 webview ,elctron 等
Greendays 初学 2024-9-30 08:48:20
Clash For Windows 有一个随机混合端口功能,打开后应该能降低风险
zeusho871 小成 2024-9-30 08:50:24
@wjx0912 设置密码了 它只能知道你开了 clash
lisxour 小成 2024-9-30 09:22:17
那这应该是 GUI 工具的问题
ohellohell 初学 2024-9-30 09:23:22
有没有办法避免
@wjx0912 看起来没啥好办法啊,总归能检测到
12下一页
返回顶部