TLS指纹问题
katayaburi opened this issue · 28 comments
有一篇gist谈到了目前主流支持TLS的科学上网协议的TLS指纹情况,想问一下这个问题对V2Ray来说需要解决吗?
我把Cisco的论文看了一遍,上面写的主要特征来源于SSL的Hello包,我想需要对握手包做特定的变换,可以增加一个虚拟层,使用一些混淆算法实现。这个issue提到的特征似乎是针对的http/2协议的V2RAY,不知道其他的几个通信协议怎样,圣诞假期搭环境看看特征。
好多大佬
这里是这篇gist原作者在Trojan项目中提交的Issue。
以及作者的项目以供参考,不是为了推荐,而是因为在项目的说明中作者阐述了他自己的观点(FAQ部分)。
想要完美的可以去用naiveproxy
其他库的tls流量理论不算在异常范围内.
@k79e 看了下 那个项目貌似也很硬核,直接把Chromium丢到源码里,不过原理上猜测只是H2套壳socks5?
不过个人感觉这也不是一个很好的操作,首先chrome版本变了要把chromium的源码同步过来,用户也需要更新客户端,作者维护这么多版本号也存在弃坑的可能。
所以我还是觉得uTLS的实现比较好,握手过程只要把浏览器的行为抓一下包就行了,并且用户可以通过配置随便切换浏览器和版本号。另外因为是客户端层面上的实现,也不需要防止主动探测出幺蛾子。
@kotori2 他基本上就是个改进的trojan 说远远超过没什么问题.
当年trojan伪装上面有些问题 nativeproxy如果做的没问题. 别提什么超过不超过 说秒杀都不是问题.
况且网络那部分不是老修改 又不是用户界面 没必要总是更新.
网络这方面如果老变动的话 那做统计的也没法统计了 这个版本这样那个版本那样.
@kotori2 不硬核,不是把Chromium丢到源码里,是在Chromium的基础上加了一串补丁,用Chromium自己的build system编译出来。
原理上,现在文档推荐的是socks5转h2,不叫套壳,不是socks5套在h2里面。也可以变成socks5转quic或者任何Chromium支持的代理协议,但经我评测现在推荐的是转h2。
维护上的工作主要在于Chromium版本更新以后rebase的时候偶尔有merge conflict,实际工作量大概每两个月Chrome出新版的时候我会花一个晚上把新版本build出来。
“作者维护这么多版本号”是一个误判,只有master是维护的,因为按照指纹一致的原则,用户必须用最新版本才能跟Chrome最新的版本一致。切换浏览器也是没有必要的,选用人数最多的那个效果就已经最好了。
uTLS不用它的原因是它只仿真了ClientHello,其他更复杂的静态信息,和TLS栈里状态转移的动态信息都仿真不出来。举个最简单的例子,uTLS在ClientHello里面加了brotli压缩的参数,但是证书并没有实际用brotli压缩。这一下就检测出来了。Chrome 69就开始用brotli压缩证书了 https://www.chromestatus.com/feature/5696925844111360。看过这篇 https://ieeexplore.ieee.org/abstract/document/6547102/ 的从业人员都知道著名的“鹦鹉已死”的结论。
说谁秒杀谁,同行相互贬低,不是很有意义。但实际情况是trojan项目刚成立的时候我就参与了,经过调研之后发现很多设计上的问题(上面已经讨论了),但是trojan的主程又没空,所以后来才分出来自己做的naiveproxy。但是实际情况又证明那些设计上的问题都是理论问题,实际中trojan不是用得好好的嘛?
跟v2ray比的话,v2ray肯定是现在市面上功能性最完善的一个。但是鲁棒性要怎么定义,如果是抗检测的鲁棒性,naiveproxy采取的抗检测措施肯定要比v2ray多,具体什么措施其实自己调研看论文就知道了。不过这些都是理论问题,最初我没用v2ray的原因是觉得自行发明密码学和嵌套加密(vmess over TLS)太不雅观,不知道这是不是最近有用户汇报v2ray在性能上都比naiveproxy慢的原因。
总之v2ray有它的特点,TLS指纹现在还停留在理论问题。但不一定哪天就成为现实问题,所以我的建议,还是认真调研一下。
@kotori2 不硬核,不是把Chromium丢到源码里,是在Chromium的基础上加了一串补丁,用Chromium自己的build system编译出来。
原理上,现在文档推荐的是socks5转h2,不叫套壳,不是socks5套在h2里面。也可以变成socks5转quic或者任何Chromium支持的代理协议,但经我评测现在推荐的是转h2。
维护上的工作主要在于Chromium版本更新以后rebase的时候偶尔有merge conflict,实际工作量大概每两个月Chrome出新版的时候我会花一个晚上把新版本build出来。
“作者维护这么多版本号”是一个误判,只有master是维护的,因为按照指纹一致的原则,用户必须用最新版本才能跟Chrome最新的版本一致。切换浏览器也是没有必要的,选用人数最多的那个效果就已经最好了。
uTLS不用它的原因是它只仿真了ClientHello,其他更复杂的静态信息,和TLS栈里状态转移的动态信息都仿真不出来。举个最简单的例子,uTLS在ClientHello里面加了brotli压缩的参数,但是证书并没有实际用brotli压缩。这一下就检测出来了。Chrome 69就开始用brotli压缩证书了 https://www.chromestatus.com/feature/5696925844111360。看过这篇 https://ieeexplore.ieee.org/abstract/document/6547102/ 的从业人员都知道著名的“鹦鹉已死”的结论。
说谁秒杀谁,同行相互贬低,不是很有意义。但实际情况是trojan项目刚成立的时候我就参与了,经过调研之后发现很多设计上的问题(上面已经讨论了),但是trojan的主程又没空,所以后来才分出来自己做的naiveproxy。但是实际情况又证明那些设计上的问题都是理论问题,实际中trojan不是用得好好的嘛?
跟v2ray比的话,v2ray肯定是现在市面上功能性最完善的一个。但是鲁棒性要怎么定义,如果是抗检测的鲁棒性,naiveproxy采取的抗检测措施肯定要比v2ray多,具体什么措施其实自己调研看论文就知道了。不过这些都是理论问题,最初我没用v2ray的原因是觉得自行发明密码学和嵌套加密(vmess over TLS)太不雅观,不知道这是不是最近有用户汇报v2ray在性能上都比naiveproxy慢的原因。
总之v2ray有它的特点,TLS指纹现在还停留在理论问题。但不一定哪天就成为现实问题,所以我的建议,还是认真调研一下。
其实仔细想想这个点子还是不错的,最开始我是觉得Chromium这么大的项目编译出来不管是体积还是内存占用都很可怕,但是看了下实际上还不错。
另外自行发明协议也只是VMess层面上的内容,如果去掉VMess的话,ws+tls原理就跟naive proxy一致了。我觉得套个vmess主要是为了鉴权?以及,他也没自己造轮子,只是调了官方的tls库,实现了粒度更精细的控制而已。
性能上的话,Go的特性导致他的runtime编译出来挺大的,占内存也多。虽然比隔壁python版ss好一点,但是性能肯定是比c++差。
我觉得吧,先把tls指纹的问题放在一边,不管是你使用代理聊天,还是看视频,一天长时间连着tls,已经是一个很明显的特征了。即使是ws,用的人也不多。(虽然我也没更好的想法就是了
可以给你介绍一下为什么这么小。程序组织上,本质就是把Chromium的base库和网络库抽取出来,再加上我的main函数,然后在Chromium的build system里加一个target,编出来一个可执行程序,这样完成的。
你说的长连接的问题我考虑过。长连接本身要深度检测很麻烦,现在主流的流量分类方法都是抽取连接开头几个长度做分类,连接后面做分类要记录大量数据,在资源上就比较吃力,所以长连接被深度检测的可行性较小(但不是不可能)。其次也不能否认长连接有合理应用,比如如果我是在线会议,或者页游,都可能产生合法的长连接。第三,naiveproxy实际使用中也不是一个连接用一天,如果隧道内的链接都没了,隧道本身的连接过一会也自动关了。这几个点本身还是无法把连接长度降到典型网页访问的模式,但也不是裸奔的状态。
Edit: 相反的,如果是1:1的短连接,这才是真正明显的特征,为什么。因为浏览器有规定,访问每个网站的并发连接数是不高的。如果两个站点之间并发数高于一般浏览器的并发数,就可以显著判断不是浏览器流量。
其实没必要那么硬核, 可以参考 tor 的 meek-plugin, 里面有两个后端, 一个是 utls, 一个是 webextension, 它通过自己写的一个 firefox extension 来把流量通过 firefox 发出去, 代码比较轻量, 而且也是 go 语言实现.
之前提过一个 feature request, 当时发现 utls 已经存在于 v2ray 里, 于是关闭了. 一年过去了发现没什么变化, 我就自己 fork 了一下, 把 tls 直接替换成了 utls, 默认模拟 Chrome 72 的 fingerprint. 改动很少, 可供测试.
但是遇到几个问题, 比如 QUIC 使用的是 qtls, 所以没有改动. 其次, websocket 的 tls 是直接将 wss://
格式的 URL 传入第三方库, 为了使 websocket 使用 utls 我只能 fork 这个 websocket 库再修改其代码.
最严重的问题在于, 大多浏览器都支持 http/2, 如果服务器也支持的话会自动升级到 http/2. 所以, 当 websocket 使用模拟的 chrome fingerprint 时握手时, ALPN 会协商出 http/2. 但是目前 go 的几个 websocket 库都不支持 bootstrap from http/2: gorilla/websocket#417, nhooyr/websocket#4.
为了解决这个问题, 我在我 fork 的 websocket 代码中加入判断, 如果传入的 URL 以 "/h2"
结尾, 则使用ALPN 中没有 http/2 的 safari 的 fingerprint, 这样就不会协商出 http/2 了.
你知道meek plugin为什么要用utls?就是因为之前用firefox extension,结果商用防火墙直接把firefox本身的指纹屏蔽了。firefox的市场占有率是多少?
至于用了utls为什么又不行,请看上面。
你的一小部分观点在下很难苟同, 比如上面举的最简单的例子就有一些最简单的问题:
uTLS在ClientHello里面加了brotli压缩的参数,但是证书并没有实际用brotli压缩。这一下就检测出来了
首先, TLS 1.3 里面除了密钥交换所必要的信息外, 其他信息都经过了加密, 除了长度很难从这些密文中看出来什么信息, 而长度又会因为数量不定的 Extensions 而难以确定. 其次, 服务器的证书是服务器传过来的, 有没有压缩是服务器的事, 跟 utls 没有一点关系. 我不认为 utls 加了 brotli 的参数, 然后因为 nginx 不支持导致发过来的证书没有压缩能说明什么问题. 再次, 虽然客户端也可以主动发证书, 但是那是 certificate-based client authentication, 我不认为一般人浏览网页的时候有这个需求.
(附上 TLS 1.3 的握手示意图)
然后上文提到的这个论点:
uTLS不用它的原因是它只仿真了ClientHello,其他更复杂的静态信息,和TLS栈里状态转移的动态信息都仿真不出来
TLS 1.2 我不做评论, 但是 TLS 1.3 只有一个 RTT (甚至可以 0-RTT), 请问除了 ClientHello 还有什么需要仿真的? 是加密过的 [Application Data] 还是加密过的 {Finished} ?
你在另一个 issue 中提到了通过 State Transition 来探测的一篇 INFOCOM 14 的论文 Markov Chain Fingerprinting to Classify Encrypted Traffic 我刚刚读了一遍, 它通过 Handshake 中的 msg_type 的状态转移来区分加密流量.
但是有几个问题, 比如论文中有一些没有控制好的变量, 比如每个软件的后端服务器也各不相同. 论文本身也承认: Obtaining application discrimination mainly comes from incorrect implementation practice, the misuse of the SSL/TLS protocol, various server configurations, and the application nature. 很难说明流量的动态转移特征是主要源于 Client 使用了不同 TLS 库的因素, 还是服务器配置和这个应用本身的特征. 对于后者, 所有翻墙协议都存在这个问题, 而这个问题不是 uTLS 需要解决的. 就比如前面提到的长链接和多链接的区别, 这个在 v2ray 上可以通过 mux 而部分 mitigate.
另外一个问题, 当时的 TLS 1.3 并不普及, 现在对于 TLS 1.3 而言, 除了 ClientHello 和 ServerHello 可以明文分辨, 其他流量都已加密, 作为中间人很难得知 TLS 状态. 并且 TLS 1.3 简化了状态机, 砍掉了许多可能性 (比如拒绝 renegotiation), 使得这六年前的方法很难以适用.
最重要的问题在于, 论文里没有讨论这个方法的 scalability. 在真实的网络环境下, 存在各种 CDN, middlebox, 各种合法软件的各种版本的流量, 同一个软件访问不同的服务, 等等等等, 通过 markov chain 来区分 v2ray 和浏览器是非常不现实的. 可以看到论文中的例子虽然能区分 Mozilla 和其他软件的流量, 但是这个 Mozilla 只是特指 version check 的流量, 而不是通用的 Firefox 浏览网站的流量.
当然, 模拟 Chrome 永远不会是真的 Chrome, 在一个理想的环境下总能找到一些方法分辨出真假. 所谓 parrot-is-dead 大概就是这样一个原则. uTLS 的 README 里面也提到这一点:
Aren't there side channels? Everybody knows that the
bird is a wordparrot is deadThere sure are. If you found one that approaches practicality at line speed — please tell us.
然而 principle 是 principle, practical 是 practical. 我们所针对的情景既不是 APT 攻击, 也不是 CIA 级别的 long-term investigation. 只需要能在大流量下得以容身即可. 很多识别方法用来做大规模检测的成本和速度往往是不实际的. 如果你真想到办法能以可以接受的代价辨别 utls 和 Chrome 的流量特征, 不妨具体说一说.
Tor 使用 firefox 的原因我感觉纯属对 Google 的不信任. 如果换成 Chrome 不见得有什么实质困难. 你在 gist 里提到 chrome extension 无法进行 raw tcp 的链接, 但是在 v2ray 这边完全可以只需其转发 websocket, 完全没必要进行 raw tcp 通信.
上面提到:
商用防火墙直接把firefox本身的指纹屏蔽了
Firefox 市场占有量低但也不少, 窃以为商业防火墙不太可能直接放弃这一块. 如果你有确凿证据或者消息来源, 希望能够补充. 这里有一个很简单的实验, 就是我的 Firefox 的确能够访问 meek-bridge. 另外, 据我刚刚的抓包实验, Tor 的 meek-plugin 默认还是 Firefox 的 fingerprint. 在**并非不可用, 而是速度太慢. 我刚刚测试了一遍的确可以连接上. 有人会卡在 "Loading relay descriptors" 上, 但是实际上这时候已经连上 meek-plugin 了. 只是没有足够的 directory information. 如果提前通过 tor over v2ray 的方法 build 过一次 circuits 之后, 再切回 meek-plugin 是可以正常使用的.
另外因为这个毕竟是 v2ray 的 issue, 所以我们考虑的问题都是如何把其他人的思路和成果剽窃借鉴到 v2ray 里面来. 不同的工具的目标应用场景不太一样, 只有合适与否, 并无高下之分. 因为我觉得 utls 和 webextension 对于 v2ray 解决 TLS 指纹问题来说是性价比较高的方案, 所以这里我就提出我自己的建议. 当然我也不是 v2ray 的开发人员, 采纳与否也要看他们的想法. 而且非常感谢你做出这么详细的调研, 我之前也想过改 chromium 代码, 但因能力不足而放弃. 很钦佩你能真的做出来一个新的项目.
Edited: 把 Firefox 屏蔽的消息来源似乎是 Google Group 上的一个讨论, 我仔细看了看, 说把 Firefox 屏蔽了有些危言耸听. 大概只是将它同时根据 SNI (meek 的 SNI 是固定的) 和特定版本的 Firefox TLS 特征来阻断链接. 因为 Tor 使用的 Firefox 都是很老的 ESR 版本, 比起桌面用户的确非常罕见. 换做 v2ray 不妨实现一个 chrome 下的 webextension, 安装在浏览器里配合 v2ray 即可.
你在论证的时候混淆了TLS标准和TLS实现的区别。标准是一样的,但是实现却千差万别,因为标准里面有不少SHOULD和MAY,这里的差别就是产生指纹的地方。特别是动态状态转移,我指的不是收集流量用马尔可夫链分类这种,而是类似于针对老版shadowsocks的嗅探攻击,可以在tls会话中间直接注入一些特殊的数据,可以直接触发具体实现各不相同的错误处理逻辑链,产生可见的分类特征。
“Google Group 上的一个讨论”,帖子就是meek的主开发David Fifield发表的,你觉得这个跟使用utls之间有没有因果关系?
你的核心论点还是回到,“现在没有实际存在的攻击,所以它就是安全的”。这就取决于怎么定义安全了。当我看到一个use-after-free,不需要做出来PoC也可以知道可能会有RCE。重复我上面说过的,我不是说v2ray一定要把tls特征做的完美无缺,但是也不能否能存在这一族的攻击方法。Shadowsocks项目一开始也不承认流量混淆的必要性,后来的情况我们都知道了。
我不知道我哪里混淆了 TLS 标准和实现的区别. 我是说你不能通过一个用前几只鹦鹉的死法里总结出来的 parrot-is-dead 的规律来直接否定新的 parrot 的具体尝试.
在二进制安全里, UAF 存在即为不合理. 但是在密码学里, feasible 是一个很常见的概念: AES-256 保证了 256 bit 的安全性, 可是几年前的密码学分析就可以在 254.4 bit 的复杂度下破解, 而且如果是相关密钥这个复杂度可以降到 99.5. 但是人们还是认为 AES 依然安全, 不是因为“现在没有实际存在的攻击,所以它就是安全的”, 而是因为"目前发现的攻击方法在 near future 也不 feasible, 所以是安全的" 如果发现了新的攻击, 算法就会被淘汰改进. 密码学这么多年一直如此. 你大可声明 AES 不安全, 但是并无意义.
而且我并没有否认存在这一类攻击的方法的可能, 我只是觉得如果找不出来 feasible 的攻击, 那么就如同讨论 AES 不安全一样毫无意义. 甚至完全可以退一步, 不考虑 feasible 只考虑 possible, 假设 attacker 有一台 Summit 级别的超算, 网络环境绝佳, 作为 0 丢包 0 延迟线路上的中间人, 你能设计算法在有限时间内以 50% 以上的正确率区分 utls 和 chrome 的流量吗? 如果不能, 那么就像说: "我一看到 Chrome 是用 C++ 写的, 就知道它肯定有漏洞. 你看果不其然, 过了几年果然发现了 RCE. 而我 Firefox 在改用 Rust, 自然有天然优势."
“Google Group 上的一个讨论”, 帖子是 16 年的, uTLS 是 NDSS 19 上的一篇论文提出的. 相隔三年的因果, 可见作者对此也是念念不忘. 既然作者惦记 Firefox 指纹的不合理性惦记了三年, 那么如今他为何还在使用 Firefox 作为默认指纹呢? 何况我也说了, 鉴于存在对 Firefox Desktop 的 253 Million Monthly Active Users 数量上的歧视, v2ray 完全可以靠 Chrome extension 来转发流量.
我可以直接否定新的parrot的具体尝试,如果若干不同领域的尝试都已经证明了:协议模仿这种做法实际上会造成更多的特征暴露,那这次有什么革命性的措施可以避免这一问题吗?
这里的攻防领域是流量分类,跟密码学没有关系,所以这个类比不说明问题。你可能要表达的意思,纵深防御本身具有它的价值。uTLS的目的是在TLS的众多特征中消灭一个特别显著的特征,纵深防御。我的观点是1)它并没有完全消除全部的特征,2)它有因为模仿而造成独特特征的问题。
“如果找不出来 feasible 的攻击, 那么就如同讨论 AES 不安全一样毫无意义”--我举use-after-free是因为它跟协议模仿都属于“一看就知道此处可以搞出exploit”的这类问题,唯一需要的只是时间和金钱。只要你稍微看过BoringSSL,OpenSSL,GnuTLS几种不同实现之间的...光是错误处理就已经有很多不同了。这个找模仿缺陷的工作实际上就是单调地审计每种密码栈实现的状态机和错误处理,同时用一些流量分析的数据来辅助分析。迄今为止没有人公开出来“feasible 的攻击”,是因为没有资金,不是因为找不出来。
别的项目具体操作我就不做猜测了。Chrome extension无法用的原因第一是特别低效,第二是很多环境不能运行Chrome。
mark
我觉得挺好的,从现在开始,我也会在互联网github等技术平台,和大家交流技术,我在ss里面发现有一个tls指纹,不知道是啥东西,请教下大家。
This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days
今天我发现,gorilla/websocket 现在支持 设置 NetDialTLSContext 函数了,这样就不需要使用fork版的 websocket,而直接可以在 NetDialTLSContext 函数里使用 uTLS 来伪装 指纹了。
最严重的问题在于, 大多浏览器都支持 http/2, 如果服务器也支持的话会自动升级到 http/2. 所以, 当 websocket 使用模拟的 chrome fingerprint 时握手时, ALPN 会协商出 http/2. 但是目前 go 的几个 websocket 库都不支持 bootstrap from http/2: gorilla/websocket#417, nhooyr/websocket#4.
对于这个问题,建议是直接在服务端关闭 http2 支持。v2fly/v2ray-core#1926
如果去除当前版本的加的 http/1.1,也就是回退到 h2, http/1.1,目前特征只是 go 语言普通的 tls 握手包。
当然后续不知道能不能在加密流量的情况下,知道是 h2 还是 websocket,理论基于 tls 1.3 的话,后续流量都是加密的。
为了解决这个问题, 我在我 fork 的 websocket 代码中加入判断, 如果传入的 URL 以
"/h2"
结尾, 则使用ALPN 中没有 http/2 的 safari 的 fingerprint, 这样就不会协商出 http/2 了.
@emc2314 至少在 tls 1.3 时代,在发送之前就得知道是 http/1.1 还是 http/2。也就是,先选择协议,再得到 url ...你这本末倒置了。
@kotori2 不硬核,不是把Chromium丢到源码里,是在Chromium的基础上加了一串补丁,用Chromium自己的build system编译出来。
原理上,现在文档推荐的是socks5转h2,不叫套壳,不是socks5套在h2里面。也可以变成socks5转quic或者任何Chromium支持的代理协议,但经我评测现在推荐的是转h2。
维护上的工作主要在于Chromium版本更新以后rebase的时候偶尔有merge conflict,实际工作量大概每两个月Chrome出新版的时候我会花一个晚上把新版本build出来。
“作者维护这么多版本号”是一个误判,只有master是维护的,因为按照指纹一致的原则,用户必须用最新版本才能跟Chrome最新的版本一致。切换浏览器也是没有必要的,选用人数最多的那个效果就已经最好了。
uTLS不用它的原因是它只仿真了ClientHello,其他更复杂的静态信息,和TLS栈里状态转移的动态信息都仿真不出来。举个最简单的例子,uTLS在ClientHello里面加了brotli压缩的参数,但是证书并没有实际用brotli压缩。这一下就检测出来了。Chrome 69就开始用brotli压缩证书了 https://www.chromestatus.com/feature/5696925844111360。看过这篇 https://ieeexplore.ieee.org/abstract/document/6547102/ 的从业人员都知道著名的“鹦鹉已死”的结论。
说谁秒杀谁,同行相互贬低,不是很有意义。但实际情况是trojan项目刚成立的时候我就参与了,经过调研之后发现很多设计上的问题(上面已经讨论了),但是trojan的主程又没空,所以后来才分出来自己做的naiveproxy。但是实际情况又证明那些设计上的问题都是理论问题,实际中trojan不是用得好好的嘛?
跟v2ray比的话,v2ray肯定是现在市面上功能性最完善的一个。但是鲁棒性要怎么定义,如果是抗检测的鲁棒性,naiveproxy采取的抗检测措施肯定要比v2ray多,具体什么措施其实自己调研看论文就知道了。不过这些都是理论问题,最初我没用v2ray的原因是觉得自行发明密码学和嵌套加密(vmess over TLS)太不雅观,不知道这是不是最近有用户汇报v2ray在性能上都比naiveproxy慢的原因。
总之v2ray有它的特点,TLS指纹现在还停留在理论问题。但不一定哪天就成为现实问题,所以我的建议,还是认真调研一下。
请问这里 utls 无法仿真的TLS连接中更复杂的静态信息指的是什么呀,能解答一下嘛