目录
很久以前水了一篇备案检测是怎么做到的?,今天又看到了一个有意思的技术——tlshello 分片,具体在哪里看到的。懂得都懂。部分原理内容摘抄自敏感网址,因而不注明来源,见谅。
本文纯粹的是验证自己的好奇心,以及对网络技术的学习和理解,如果你看完有什么奇奇怪怪的念头,请做个守法公民。
TCP 分片
TCP 是一种基于流的协议,用户和应用程序可以通过该协议使用抽象数据流发送数据。TCP 将这些数据流转换为实际的网络数据包(称为 TCP Segment)。每个 TCP Segment可以包含完整的应用程序消息,也可以仅包含部分消息。后者称为 TCP 分片。如下所示,以 HTTP GET 消息表示。
==================TCP Segment====================
= GET /index.html HTTP/1.1 =
= host: proxyguess.bjun.tech =
= ... =
= =
= =
=================================================
| |
V
==================TCP Segment 1==================
= GET /index.html HTTP/1.1 =
= host: proxyguess =
= =
= =
= =
=================================================
==================TCP Segment 2==================
= .bjun.tech =
= ... =
= =
= =
= =
=================================================
上面的部分TCP Segment包含了一个未分片的HTTP GET 请求,
下面的部分是分片后的2个TCP Segment与前面做的事情一致。
TLS RECORD 分片
虽然 TLS 消息可以分片到多个 TCP 段,但它们也可以单独在 TLS 层上分片。这是可能的,因为 TLS 层由两个不同的层组成:TLS 消息层和 TLS 记录层。在 TLS 记录层上,每个 TLS 消息都包装在 TLS 记录结构中。最重要的是,单个 TLS 消息可以拆分到多个 TLS 记录中,从而导致 TLS 记录碎片。如下图所示。
==================TCP Segment======================
= =================TLS Record==================== =
= =0x010000D5 = =
= =SNI:proxyguess.bjun.tech = =
= = = =
= = = =
= = = =
= =============================================== =
===================================================
| |
V
==================TCP Segment======================
= =================TLS Record 1================== =
= =0x010000D5 = =
= =SNI:proxyguess. = =
= = = =
= = = =
= = = =
= =============================================== =
= =
= =================TLS Record 2================== =
= =bjun.tech = =
= = = =
= = = =
= = = =
= = = =
= =============================================== =
===================================================
| |
V
================== IP PACKAGE 1======================
===================TCP Segment ======================
= = =================TLS Record 1================ = =
= = =0x010000D5 = = =
= = =SNI:proxyguess. = = =
= = = = = =
= = = = = =
= = = = = =
= = ============================================= = =
= ================================================= =
=====================================================
================== IP PACKAGE 2======================
===================TCP Segment ======================
= = =================TLS Record 1================ = =
= = =bjun.tech = = =
= = = = = =
= = = = = =
= = = = = =
= = = = = =
= = ============================================= = =
= ================================================= =
=====================================================
上面的部分表示一个完整的TCP Segment和TLS RECORD中的 TLS ClientHello ,
中间的部分是在同个TCP Segment中包含了被分片后的TLS ClientHello 对应的两个TLS RECORD。
下面的部分将分片后的TLS ClientHello 对应的两个TLS RECORD 分配到 2个IP数据包中
测试
配置
这是某软件的配置,这里拿他在做TLSHELLO 分片器
{
"inbounds": [
{
"tag": "inbound-1080",
"port": 1080, // SOCKS 代理端口,在浏览器中需配置代理并指向这个端口
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"udp": true
}
}
],
"outbounds": [
{
"tag": "fragment",
"protocol": "freedom",
"settings": {
"fragment": {
"packets": "tlshello",
"length": "100-200",//分片长度
"interval": "0-1"//分片发送时间间隔 ms
}
},
"streamSettings": {
"sockopt": {
"TcpNoDelay": true,
"mark": 255
}
}
}
],
"routing": {
"domainStrategy": "AsIs",
"rules": [
{
"type": "field",
"inboundTag": ["inbound-1080"],
"outboundTag": "fragment"
}
]
}
}
测试
-
正常
curl https://bjun.tech:9764/demo/ja3/ja3.php
{"client":{"ja3":"bd0bf25947d4a37404f0424edf4db9ad","ja3_str":"771,49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,0-5-10-11-13-35-16-23-65281,29-23-24,0","ja4":"t12d2109h1_76e208dd3e22_7af1ed941c26","ja4_o":"t12d2109h1_93eee3d26c42_5da3d607c71a","ja4_r":"t12d2109h1_000a,002f,0035,003c,003d,009c,009d,009e,009f,c009,c00a,c013,c014,c023,c024,c027,c028,c02b,c02c,c02f,c030_0005,000a,000b,000d,0017,0023,ff01_0804,0805,0806,0401,0501,0201,0403,0503,0203,0202,0601,0603","ja4_ro":"t12d2109h1_c02c,c02b,c030,c02f,009f,009e,c024,c023,c028,c027,c00a,c009,c014,c013,009d,009c,003d,003c,0035,002f,000a_0000,0005,000a,000b,000d,0023,0010,0017,ff01_0804,0805,0806,0401,0501,0201,0403,0503,0203,0202,0601,0603"},"sever":{"ja3s":"1af33e1657631357c73119488045302c","ja3s_str":"771,49199,65281-11-35-23","ja4s":"t120400_c02f_12a20535f9be","ja4_ro":"t120400_c02f_ff01,000b,0023,0017"}}
-
启用TLSHELLO 分片
curl -x socks5://127.0.0.1:1080 https://bjun.tech:9764/demo/ja3/ja3.php
{"client":{"ja3":"1c2d6b64d730549b97c138d092a8ca05","ja3_str":"771,49196-49195-49200-49199-159-158-49188-49187-49192-49191-49162-49161-49172-49171-157-156-61-60-53-47-10,0-5-10-11-13-35-16---,29-23-24,0","ja4":"t12d211000_76e208dd3e22_ed8d2d8aa674","ja4_o":"t12d211000_93eee3d26c42_c06c60c54598","ja4_r":"t12d211000_000a,002f,0035,003c,003d,009c,009d,009e,009f,c009,c00a,c013,c014,c023,c024,c027,c028,c02b,c02c,c02f,c030_0000,0000,0000,0005,000a,000b,000d,0023_0804,0805,0806,0401,0501,0201,0403,0503,0203,0202,0601,0603","ja4_ro":"t12d211000_c02c,c02b,c030,c02f,009f,009e,c024,c023,c028,c027,c00a,c009,c014,c013,009d,009c,003d,003c,0035,002f,000a_0000,0005,000a,000b,000d,0023,0010,0000,0000,0000_0804,0805,0806,0401,0501,0201,0403,0503,0203,0202,0601,0603"},"sever":{"ja3s":"1af33e1657631357c73119488045302c","ja3s_str":"771,49199,65281-11-35-23","ja4s":"t120400_c02f_12a20535f9be","ja4_ro":"t120400_c02f_ff01,000b,0023,0017"}}
ja3 是一个TLS hello 的指纹,具体看一看php-JA3 TLS握手指纹实践3 - 纯php实现
这里的可以看到ja3获取了一个很奇怪的值,明显是错误的。从Ja3_str中可以发现,部分拓展是没获取到的。
说明这里的TLSHELLO 分片 对我的JA3指纹获取形成了干扰。原因是我只取了一个数据包用于指纹提取,而这个数据包是部分的握手内容,自然就出现了错误。
注意:使用使用winshark,tshark等抓包工具,其中的Ja3等指纹是正确的。(因为他们由专门为这个问题进行数据重组,见文末的连接)
突破备案检测测试
那么,好奇心就来了。能突破腾讯云的备案检测吗。众所周知,备案检测是同个检测HTTPS握手包中的SNI进行判断的。那么TLSHELLO分片能对其产生干扰吗。
触发备案拦截
- 域名配置到服务器中,这里使用不存在的域名 bjun.test
- 发起一个http请求访问,过一段时间,可能几个小时后,就会稳定的返回网址无法访问:网站内容和备案信息不准确…… 的页面提示
curl -k --resolve bjun.test:80:42.194.183.26 http://bjun.test/ip.php -vvv
* Added bjun.test:80:42.194.183.26 to DNS cache
* Hostname bjun.test was found in DNS cache
* Trying 42.194.183.26:80...
* Connected to bjun.test (42.194.183.26) port 80
> GET /ip.php HTTP/1.1
> Host: bjun.test
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 302 OK
< Connection: Keep-Alive
< Location: https://dnspod.qcloud.com/static/webblock.html?d=bjun.test
* no chunk, no close, no size. Assume close to signal end
<
* Closing connection
- 发起一天https请求,会在握手到得到一个RST的TCP 数据包。
curl -k --resolve bjun.test:443:42.194.183.26 https://bjun.test/ip.php -vvv
* Added bjun.test:443:42.194.183.26 to DNS cache
* Hostname bjun.test was found in DNS cache
* Trying 42.194.183.26:443...
* Connected to bjun.test (42.194.183.26) port 443
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* Recv failure: Connection was reset
* schannel: failed to receive handshake, SSL/TLS connection failed
* Closing connection
* schannel: shutting down SSL/TLS connection with bjun.test port 443
* Send failure: Connection was reset
* schannel: failed to send close msg: Failed sending data to the peer (bytes written: -1)
curl: (35) Recv failure: Connection was reset
就此,未备案拦截已经成功触发。
启动 TLSHELLO 分片 测试
- 100-200 长度分片 ,失败
curl -x socks5://127.0.0.1:1080 -k --resolve bjun.test:443:42.194.183.26 https://bjun.test/ip.php -vvv
* Added bjun.test:443:42.194.183.26 to DNS cache
* Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* Hostname bjun.test was found in DNS cache
* SOCKS5 connect to 42.194.183.26:443 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* schannel: failed to receive handshake, SSL/TLS connection failed
* Closing connection
* schannel: shutting down SSL/TLS connection with bjun.test port 443
curl: (35) schannel: failed to receive handshake, SSL/TLS connection failed
- 100-150 长度分片 ,失败
- 100-130 长度分片 ,部分成功
- 100-120 长度分片 ,稳定成功
- 30-50 长度分片 ,稳定成功
curl -x socks5://127.0.0.1:1080 -k --resolve bjun.test:443:42.194.183.26 https://bjun.test/ip.php -vvv
* Added bjun.test:443:42.194.183.26 to DNS cache
* Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* Hostname bjun.test was found in DNS cache
* SOCKS5 connect to 42.194.183.26:443 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.1
> GET /ip.php HTTP/1.1
> Host: bjun.test
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.20.2
< Date: Fri, 31 May 2024 16:58:59 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Vary: Accept-Encoding
……
好吧,稳定突破触发备案拦截
抓包后的简单结论
client hello 被分成2个IP包,部分请求SNI 拓展位于第二个包,部分请求是刚好在SNI部分被分割。而小段分片则SNI拓展必然不会出现的第一个包中。
由此可以推测,腾讯云的备案拦截和我的JA3代码一样没有注意到这个问题,也是同个首个包去判断的。
思考
用途
突破备案鄙人觉得没啥卵用。哪个正常用户会有这样的环境呢?大费周章,还不如换个端口来的简单。
修复
像我的JA3 和 备案防火墙应该怎么修复这个问题呢?或许可以参考winshark的处理办法,进行包重组。当然这需要一定代价。
只从我的JA3 思考
- 针对TLS 握手维护TLS RECORD 的状态,但这需要缓存前后这几个包的数据。会增加内存使用和计算量,同时可能被针对性攻击。
- 从openssl中获取JA3,也就是在TLS RECORD 重组后再处理,类似nginx或者ja3拓展的思路。当然,php应该是做不到的。
- 屏蔽该情况的数据包对应的请求,至少在我的观察,到目前,正常的网络中TLSHELLO 分片的数据包可是太罕见了。
记于:2024年6月1日。后续我的JA3有更新或者备案防火墙有更新再补充。
其他
scrapfly.io JA3
scrapfly.io 开发的指纹获取工具,scrapfly.io https://tools.scrapfly.io/api/tls,通过TLS HELLO 分片访问会报错。
这里用的是 curl-impersonate-win,我电脑的curl 可能是应该不支持H2,这个网站访问不了
.\curl_chrome104.bat -x socks5://127.0.0.1:1080 https://tools.scrapfly.io/api/tls
404 page not found
参考
tls-record-protocol
An issue that took ten years to fix in winshark