Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redirect attack - Shadowsocks 流密码的不安全因素 | 博客 | Powered by skywalker_z #88

Open
eightHundreds opened this issue Sep 10, 2023 · 0 comments

Comments

@eightHundreds
Copy link
Owner

注意:本文发布于 1304 天前,文章中的一些内容可能已经过时。

Shadowsocks 是一款陪伴无数玩家多年的科学上网工具,但是近年来随着墙的日益增高,一些 Shadowsocks 流量已经可以被很好的识别出来,然后就是——“同志,你梯子塌了”。

虽然“协议可以被识别”已经众所周知,但我们依旧认为,Shadowsocks 的加密做的不错,中间人应当破解不出明文信息。

然而,晚上看到的一篇 文章,稍稍动摇了一下我对 Shadowsocks 数据安全的信心,更让我重新审视了一下流密码的安全性。之前我在做安全方面的科普的时候,我总会提到“ECB 是不安全的”,但现在发现,其它的一些流密码也不一定安全,因为它们没有保证数据的完整性,因此存在数据被篡改的可能性。

Shadowsocks 如果配置得当,还是比较安全的,或者至少可以让目前的破解方法没有用武之地。如果看完本文后依旧不放心,可以使用其它的科学上网工具。

本文的一些图片引用自维基百科,可能需要科学地观看。

考虑到可能有些同学没有密码学的基础,也对 Shadowsocks 的协议不是很了解,这里就简单的介绍一下。如果您已经有足够的知识,可以 跳到下一节

流密码

相信不少人都听说过 AES 之类的对称加密算法,但并没有了解的那么细致。例如什么是 IV?什么是基于数据块的加密?

事实上,AES 算法本体并不是为了处理无限长度的字符串而设计的,它一次只能处理 16 个字节(不管是 AES-128 还是 AES-256),我们管这 16 个字节叫做一个“数据块”,AES 就是一个基于数据块的加密算法。对于这一缺陷,可以用这样一种思路:先将明文切割成若干个数据块,对于每个块用 AES 做加密,再把每个加密后的块拼起来。

是不是很简单?其实这就是 ECB 模式。使用这个思路的 AES-256 算法被称为 AES-256-ECB。由于 16 字节对于一些数据来说实在是太小,很容易出现大量重复的块。例如有这样一张图片:

大家都知道这是一只小企鹅,我们用 ECB 加密一下再看看:

应该依旧能看出来图中是刚才那只小企鹅。ECB 模式无法隐藏原文的特征,请大家尽量不要使用。

有一些改良的方法,例如除了密码以外,再提供一个初始向量 IV 作为第 0 个数据块,在加密第 i 个块之前,先将明文跟上一个块的加密结果异或一下,即:

这种方式会比刚才的 ECB 安全得多。

还有其它的一些方式,所有的这些方式(包括 ECB)统称为“流密码”。一开始的那张小企鹅,用 ECB 以外的方式加密之后是这样的,已经完全看不出是什么了:

中间人攻击

中间人攻击是个比较好玩的东西,假设攻击者利用一些方法(比如用一个钓鱼热点)让受害者发出的数据包走到了自己这里,那么攻击者就可以查看或者修改数据包的内容。就好像小明在课上给小红传纸条,本来写的是“我喜欢你”,但在经过小亮的时候,小亮用自己的纸条替换了他们的,上面写着“滚犊子吧”,可见中间人攻击对我们的影响有多大。

注:在课上传纸条是不好的行为,小朋友们不要模仿。

大家在日常生活中最常经历的中间人攻击,可能是在访问某些 HTTP 网站的时候,突然弹出一个“宽带到期需要续费”的通知。这种中间人攻击是一些无良运营商干的,不过我们更习惯把它叫做“流量劫持”。

数据完整性

这一段就偷个懒,直接摘抄百度百科了(有删改):

完整性是信息安全的三个基本要点之一,指用户、进程或者硬件组件具有能力,能够验证所发送或传送的东西的准确性,并且进程或硬件组件不会被以任何方式改变。

翻译成白话就是:能保证数据在传输过程中不被篡改,小红收到的纸条内容跟小明传出去的一模一样。

  • 大家听说过的 ECC 内存纠错算法就能保证数据完整性,它可以发现内存数据被篡改(例如遭受了高能粒子冲击或硬件的部分损坏),并通过一些算法尽量恢复被损坏的部分;
  • HTTPS 中的数字签名也是一种保证数据完整性的方法,它可以发现传输的数据被篡改(例如中间人攻击),并且立即停止数据传输,以防止用户或服务器因为接收到虚假数据而遭受损失。

Shadowsocks 协议基础

虽然 Shadowsocks 使用的底层协议是 SOCKS5,但对于本文而言,底层的 SOCKS5 并不是重点,我们只需要关注 Shadowsocks 的客户端与服务器之间是如何传输数据的。

根据官方文档所说,客户端向服务器发送的数据,一开始是流密码的 IV(也就是说,IV 由客户端生成,并直接扔进数据包中),之后就是一段加密数据,它的明文格式是这样的:

其中数据可以是任意长度;至于目标地址,Shadowsocks 用的是 SOCKS5 的表示法:

其中,类型是 1 字节的枚举值:

  • 0x01:主机名是 IPv4 地址;
  • 0x03:主机名是变长字符串,首字节表示长度(最大 255),后面是数据;
  • 0x04:主机名是 IPv6 地址。

一次代理的过程如下:

  1. 客户端将这些数据加密后发到服务器;
  2. 服务器收到后将其解密,会得到 [1 字节类型][主机名][2 字节端口][数据]
  3. 服务器会将数据部分直接发送给 主机名:端口
  4. 服务器将主机返回的数据直接使用同样的算法加密(如果加密算法用了流密码,则会生成并使用一个新的 IV,并将其放在包的最前面),发送给客户端;
  5. 客户端解密后即可得到主机返回的数据。

回到一开始说的那篇文章上,作者的发现是:如果攻击者抓到了一个 Shadowsocks 服务器返回的包,并且已知数据部分的开头七个字节,那么有可能在不知道密码的情况下,利用那个 Shadowsocks 服务器来解出包的绝大部分内容(最多损失 16 字节)。

作者的思路是这样的:

假设有一台 Shadowsocks 服务器,攻击者通过嗅探或其它方式抓到了这个 Shadowsocks 服务器返回的一个包。

为了知道明文内容,攻击者要么暴力破解密码(随着大家安全意识的提升,这已经几乎不可行了),要么想办法利用这台 Shadowsocks 服务器帮忙解密。

作者选择了后者,即想办法把这个包变成客户端发的包,让服务器解密后代理到自己指定的服务器,这被称为 Redirect attack

上一节说到,Shadowsocks 客户端发的包格式(明文状态下)是 [1 字节类型][主机名][2 字节端口][数据]。如果攻击者可以利用加密算法的缺陷来篡改明文数据,就可以把主机名改成攻击者的服务器地址,Shadowsocks 服务器就会以为客户端想访问攻击者的服务器,于是就把解密后的包中的数据部分发了过去。

先考虑如何篡改数据。假设这台 Shadowsocks 服务器的加密算法使用的是 AES-256-CFB,那么解密的方式如维基百科所述是这样的:

其中 IV、每一块 Ciphertext 和 Plaintext 长度都是 16 字节。

作者发现,key 是不变的,IV 也可以重用服务器返回包中的那个,那么如果只修改第一块 Ciphertext,那么只有前两块 Plaintext 会改变,更重要的是,由于第一块 Plaintext 就是第一块 Ciphertext 跟某个串 A 的异或值,那么攻击者完全可以通过修改第一块 Ciphertext 的值来控制第一块 Plaintext!具体方法如下:

假设当前的第一块 Ciphertext 是 c1,第一块 Plaintext 是 p1IV 做了一系列 whatever 的运算得到的结果是 a,那么:

攻击者需要做的就是将 c1 ^= (q1 ^ p1)。但这里有一个问题,我们并不能知道具体的 p1 是什么!不过还好,它是明文数据的一部分,在上网的过程中,总有些协议的头几个字节是固定的,例如 HTTP 协议。

在 21 世纪的第三个十年,大家应该早就切换成 HTTP 1.1 了,因此返回的数据包一开始一定是 8 个字节 HTTP/1.1。攻击者能否将 TA 的主机地址压缩到这么小呢?毕竟除掉 1 字节类型和 2 字节端口以外,可用空间只有 5 字节了。

对于绝大部分攻击者来说,不可能拿到不超过 5 字节的域名,因此只能考虑 IPv4 了,而且如果用 IPv4 的话,甚至只需要总共 7 个字节!举个例子:

划线的三部分分别代表了:使用 IPv4 协议、地址是 192.168.1.3,端口是 4626。

那么我们完全可以令:

用这个 7 字节的 new_c_part 替换掉之前 c1 的前 7 个字节,然后直接将替换后的整个包发送到刚才的 Shadowsocks 服务器。

Shadowsocks 服务器尝试解密,解密后发现明文是这样的:

服务器会认为这是一个合法的客户端请求,因此将后面的一串 XX(明文数据)按照前 7 个字节的要求,转发到了 192.168.1.3:4626

攻击者早就在这儿坐等了,方法非常简单,只需要用 nc 启动一个端口监听即可:

由于攻击者修改了 c1,而 c1 在 CFB 模式中又用来解密 p2,因此收到的 p2 这 16 个字节应该是乱码。攻击者最终可以还原出 p2 以外的所有数据。论文中的命令行截图也说明了这点,获取到的数据的第一个字节是之前包的明文的第 8 个字节(前 7 个是 HTTP/1.),然后有 9 个字节是正确的,之后 16 个字节是乱码,再之后是完全正确的:

作者给出的防御措施是:

  • 禁用 shadowsocks-py、shadowsocks-go、go-shadowsocks2、shadowsocks-nodejs
  • 只用 shadowsocks-libev,并且只使用 AEAD 加密

原因如下:shadowsocks-libev 的实现很久之前就已经禁止了 IV 重用,可以在一定程度上防止这种攻击;只要加密算法带有 AEAD 特性,那么数据就无法被篡改,本文的攻击方式也是无效的。

虽然文章中只列举了 HTTP 协议和 CFB 模式的例子,但理论上来说,所有头部 7 个字节已知的协议和所有类似流密码的组合,都可以被这种方法攻击。你不能保证你科学上的网总是 HTTPS,即使是 HTTPS,如果是国内网站,当某些不可抗力获取了其证书之后,你的 TLS 流量总是会被解密的。

不过有一些值得欣慰的地方:

由于墙的逐渐升高,大家已经逐渐意识到“只有加密是不行的”了,因此纷纷改用带有混淆功能的科学上网工具。由于攻击者无法得知混淆的参数(甚至不知道哪个流量是梯子的流量),因此这个方法不再起作用了。

目前大部分科学上网工具已经禁用了旧的加密算法,甚至强制只让使用带有 GCM 或者 Poly1305 的加密算法,这些算法有严格的 AEAD 特性,可以极大保证数据安全。TLS 1.3 强制使用 AEAD 也在某些程度上为它的安全性做了担保。

如果你还在用 Shadowsocks 或其衍生工具,并且依旧使用普通的流密码来加密,那么请立即听从作者给出的防御措施,为了你的服务器,也为了你自己。

添加了对 IV 重用相关的补充。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant