免责声明
本文所提供的程序(方法)可能带有攻击性,仅供安全研究与教学之用。文章作者无法鉴别判断读者使用信息及工具的真实用途,若读者将文章中的工具或信息做其他用途,由读者承担全部法律及连带责任,segmentfault和本文作者不承担任何法律及连带责任。
注:此文章适用于对Clash有一定使用经验的读者 不再对基础配置说明
0x00 意外
一直以来都使用酸酸乳自带的负载均衡功能配合Airport实现IP秒切
主要用途是对站点进行渗透测试时路径爆破和扫描端口
直到有一天Airport跑路了
兜兜转转换了一家 发现他们使用Clash作为客户端
为方便使用 我选择了Clash-For-Windows
简称CFWClash
支持跨平台 后文以Clash
进行分析(主要是CFW不开源)
拿到机场配置文件导入 简单配置下就能正常使用了
但是却始终没有找到和负载均衡有关的设置
百度一下 找到了负载均衡的配置样例
# load-balance: The request of the same eTLD+1 will be dial to the same proxy.
- name: "load-balance"
type: load-balance
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
按照样例配置好负载均衡 拿起dirsearch跑一跑 然后就被封了
用VPS起个WEB服务扫描看一看 原来IP根本就没变!
0x01 深入
继续翻文档 最终把目光停留在这个注释上
# load-balance enables the same eTLD requests on the same proxy
eTLD没接触过 补个课先
Top-level domains (TLDs) ,顶级域名,例如.com、.net、.cn、.tw、.top等。
effective TLDs(eTLDs),有效顶级域名,对于如.com.cn或.github.io这样的域名,仅仅使用.jp或.io的顶级域名是不够细化的,不足以识别 "同一个网站",目前没有统一的标准确定某个顶级域名的可注册等级。这就是为什么创建了一个 "有效顶级域名"(eTLDs)的列表。 (eTLDs列表在publicsuffix.org/list上维护)
举例:
给定的URL是https://my-project.github.io,则eTLD是.github.io,eTLD+1是my-project.github.io
换句话说,eTLD+1是有效的顶级域名和域名之前的部分。
eTLD的概念主要应用于同源跨域的判断上,在页面转换、fetch()请求、cookies、打开弹出窗口、嵌入式资源和iframe的应用较多。引用自
web.dev/same-site-same-origin
根据注释推测是使用了eTLD
的原因
那么让负载均衡不使用eTLD
就可以在扫描的时候实现秒切IP了❓
0x02 分析
打开Clash-1.6.5源码 全局搜索etld
运气不错 只有loadbalance.go
在一个函数内部声明了etld
继续分析 首先声明了变量etld
调用publicsuffix.EffectiveTLDPlusOne()
方法对metadata.Host
进行处理并赋值给etld
然后定义了一个函数getKey()
来接收处理后的metadata.Host
那么函数getKey()
返回的值可以是目的IP 也可以是eTLD(eTLD+1)
(其实分析到这里 已经隐约感觉到问题没有出在eTLD
)
继续搜索 发现函数strategyConsistentHashing()
调用了getKey()
根据函数名判断strategyConsistentHashing()
是一致性哈希算法策略
一致性哈希算法是负载均衡调度算法的其中一种
以Nginx负载均衡的算法进行类比 相当于ip_hash
和url_hash
的结合
ip_hash:每个请求按访问IP的哈希结果分配,使来自同一个IP的访客固定访问一台后端服务器,并且可以有效解决动态网页存在的session共享问题。
url_hash:按访问的URL的哈希结果来分配请求,使每个URL定向到一台后端服务器,可以进一步提高后端缓存服务器的效率。如果需要这种调度算法,则必须安装Nginx的hash软件包。
分析到这步 基本可以确定IP秒切失败的原因是使用了一致性哈希调度算法
导致的
那么只要使用轮询调度算法
来分配每次请求 就可以实现IP秒切效果
轮询调度(Round-Robin,RR):最简单的调度算法,LB按照顺序将请求依次转发给后端的RS,并没有考量后端RS的状态(处理速度以及响应时间)。
带权重的轮询调度(Weighted Round-Robin,WRR):在轮询算法的基础上加上权重设置,权重越高的RS被分配到的请求越多。
上下随便翻翻 发现函数strategyConsistentHashing()
上方已经写好了Round-Robin调度算法
本来还想尝试写一个呢hhh 既然如此 那就来看下两个函数之间的差异
直接的原因就在于变量idx
的值 而idx
的值又是由两种算法决定的
一致性哈希会把同一个IP/URL的请求分配到同一个代理上 idx
和getKey()
的返回值相关
轮询调度会把每一次请求分配到不同的代理上 idx
仅和代理的数量相关
两者在策略失效的情况下 都会默认选择第1个代理
调度算法已经有了 现在只要找到哪里调用了strategyRoundRobin()
粗略读一下代码 发现函数NewLoadBalance()
对调度算法进行了分支选择
继续跟进 函数parseStrategy()
从字典config[]
的strategy
键值对读取解析策略
推测字典对象config[]
对应Clash的配置文件*.yaml
如果在字典中没有读取到strategy
键值对 就会默认使用consistent-hashing
作为解析策略
(后续经过测试strategy
键值对要写在配置文件load-balance
的内部才会生效)
那么这里就有两种解决方法:
- 将函数
parseStrategy()
的默认返回值由consistent-hashing
更改为round-robin
- 在配置文件里新增键值对
strategy
并赋值为round-robin
显然方法2更加便捷灵活
(安福仔一枚基本不接触开发 分析缺陷的地方还请师傅们多指教)
(RR是作者后期增加的一个调度算法 早期的配置文档里没有样例 所以才有了这篇文章)
0x03 调试
将配置文件修改以后 简单进行一下测试
使用浏览器连接代理访问cip.cc
通过不断刷新页面来观察IP的切换情况
然而在每次刷新之后IP依旧固定不变
再三阅读代码确认没有问题之后 在一次测试中无意间点开了CFW的连接
原来在访问网站之后 TCP连接没有立即断开 而是处于保持连接状态
F12呼出控制台 --> 刷新 果然 请求和响应头部都被设置了Connection: keep-alive
突然想到Burpsuite默认会把Connection
字段值改写为close
于是起Burp --> 挂代理 --> 抓包 --> 重放 --> 重放 --> 重放 OK
最后 VPS起个WEB服务 拿dirsearch跑一跑验证效果
这样又可以放心大胆的干活了