Wednesday, September 21, 2016

Shadowsocks各分支的安全性

这里要说的安全性并不是指密码学上的那种,而是在防探测,防流量识别并阻断上的安全性(也许算隐匿性?),所以主要针对的就是服务端。现在服务端的版本主要有以下这几个分支:shadowsocks-libev,shadowsocksR-python,shadowsocks-python,shadowsocks-go,libQtShadowsocks。而其它的分支用的人太少,所以我也没有去研究代码。

在说这个问题之前,首先要提及一点:TCP是流协议,你需要假设它可能在任何地方被拆分开,后面内容过一会儿才收到。服务端在设计的时候应该遵守以上这点来设计协议或设计其行为。

目前主要在使用的协议,一个是原始协议,另一个是OTA协议,现在分别说说这两协议在各分支上的实现。以下内容为截止到20160921时的各分支的实现情况,若将来各分支修正了,那么以修正后的情况为准,下文仅以此作为示例说明。



针对原始协议,截止到本文发表前,在IV的部分实现有问题的分支为shadowsocks-python,libQtShadowsocks。首先它们的实现总是假设第一个TCP包一定能收到完整的IV,事实上如果第一个TCP包被拆开,那么会导致shadowsocks-python解密错误(使用了错误的IV)而断开连接,而libQtShadowsocks则直接整个服务端程序crash退出(数组越界断言)。在IV之后的协议头部分实现有问题的,除了shadowsocks-go是实现正确以外,其它分支(包括SSR)全都实现错误了,也就是说如果协议头部分的包如果被拆开,除了shadowsocks-go还能正常连接外,其它的全部都会断开连接。估计是最早的时候服务端某个分支是这么写的,然后其它分支都用相同的逻辑来写,导致全错了。

对于原始协议的安全性,它无法防止CCA攻击,也就是说总是可以有办法通过足够次数的主动探测从而确认这是SS服务器。尽管如此,各分支实现的细节还是有点差别。首先,在OTA协议出现之前,我写了一篇文章来说明这种攻击的可行性,并在之后给出了具体的攻击代码,在这个时候起码还是需要穷举至少256次来确认的,不过对于shadowsocks-libev需要更多的次数以绕过它的IV buffer以便使其重复IV检测失效,不管怎么说检测的代价还是比较大的,很多人怀疑这个检测代价太大,某防火墙应该是不会这么做的。但是后来,在OTA协议出现之后,各分支都在跟进,因为它能防止CCA攻击,理论上的安全性的确提升了,但现实是,除了shadowsocksR-python分支的安全性没有下降,所有其它分支的安全性全部严重下降!我确实想不明白为什么大家都要这么实现,难道是因为shadowsocks-libev是这么写,所以大家都那样写么?现在各分支的实现,只要不是强制使用OTA协议(即同时支持原协议和OTA协议的情况下),要主动检测它是不是SS服务器,下降到基本上只需要16次连接来确认了,这导致这个主动探测攻击进入了非常实用的阶段,我觉得这太可怕了,于是之前写了一段仅针对shadowsocks-go的检测代码,演示了最多只要32次连接就成功检测这是不是shadowsocks-go,而且能连同IV长度一并检测出来(如果IV已知那只要16次连接来确认),而且理论上还能检测出它是不是仅支持OTA或两协议都支持。现在的各SS分支的实现确实安全性非常差,除非强制开启OTA,否则只要是兼容原协议,16次试探就能定位封锁,问你怕不怕。

针对OTA协议,实质就是在原始协议的头部,添加一个标志位,以及末尾添加SHA1-MAC,而其它从客户端发向服务端的数据包,均添加包的长度及SHA1-MAC。对于这个协议的实现,shadowsocks-libev及其它大部分分支均假定第一个数据包必须包含整个带了SHA1-MAC的头部,否则断开连接。这样做没有安全性上的问题,但违反了TCP协议的行为规则。这方面同样也只有shadowsocks-go遵守了TCP协议的行为规则,但是却留下了安全性上的问题,导致OTA并没有对其产生安全性上的提升,相反的导致了更容易被检测的问题,成为了目前各分支里安全性最弱的分支。

总结,如果你用的是还不支持OTA的服务端,那么你应该庆幸,还不至于太糟糕, 否则如果你用的是支持了OTA但不是SSR的服务端,那么你必须开启OTA,或等待服务端修正这个问题再用,否则你的服务器和等着被封没有区别,看你运气了。当然如果你如果用SSR的服务端,那么你应该使用auth_sha1_v4协议保证其安全性(或至少也要auth_sha1/auth_sha1_v2,具体细节差别不在本文展开了)。混淆就无所谓安全性了,按需要来就好,设置plain亦可。

感谢阅读,欢迎评价,有误请指出。

11 comments:

  1. 哈哈,没想到我实现golang版时没照着别人的实现,而是自己照着spec写,解了粘包拆包问题,反而安全性最弱了,不过spec初衷如此,没办法了,这个也没法修,只能开新协议来解了

    ReplyDelete
    Replies
    1. 其实有非常简单的修正办法,你可以采用。就是判断地址类型时(1,3,4),绝对不要先&0xF再判断,而是先判断标志位和服务端设置,无标志位时需要整个字节参数判断,有标志位或服务端设置为OTA时,先把OTA位置0,然后依然判断整个字节,别把高4位完全忽略掉

      Delete
  2. 谢谢推广普及!
    暑假期间一同事的vps被gfw IP block,赶紧升级了自己的 ss-libev, 发现一年没有动了,世界变化不小。 Ubuntu 启用 systemd了。 配置如下

    # cat /etc/ss/ss-libev.sh
    #!/bin/bash

    SCRIPTNAME=ss-libev.sh
    case "$1" in
    start)
    (/usr/bin/ss-server -c /etc/ss/shadowsocks.json -u -A --acl /etc/ss/blacklist.txt -6 > /dev/null 2>&1 &)
    echo "Shadowsocks-Libev Custom Server Service started"
    ;;
    stop)
    pkill -u nobody ss-server
    echo "Shadowsocks-Libev Custom Server Service stopped"
    ;;
    *)
    echo "Usage: $SCRIPTNAME {start|stop}" >&2
    exit 3
    ;;
    esac

    服务
    # cat /etc/ss/ss-libev.service
    [Unit]
    Description=Shadowsocks-Libev Custom Server Service
    Documentation=man:ss-server(1)
    After=network.target

    [Service]
    Type=forking
    User=nobody
    Group=nogroup
    LimitNOFILE=32768
    ExecStart=/etc/ss/ss-libev.sh start
    ExecStop=/etc/ss/ss-libev.sh stop

    [Install]
    WantedBy=multi-user.target

    ReplyDelete
  3. 谢谢,一直在您这里受益

    ReplyDelete
  4. 终于找到SSR的真正作者了,特来表示感谢。

    ReplyDelete
  5. 期待@breakwa11对ss项目做出更多贡献!
    建议@breakwa11并且做好匿名工作,以防像clowwindy被请喝茶的剧情重演!

    ReplyDelete