当前,为了保证通讯数据的安全传输或达到网络速度优化等目的,出现了一些网络代理的项目,例如初期比较有代表性的SSH代理和后来的Shadowsocks等。

众所周知,SSH代理是通过SSH协议创建端到端安全的加密通道,进而能够基于SOCKS代理协议和端口转发功能实现代理服务。虽然SSH隧道中的数据安全性得到了保证,但由于创建隧道和数据传输过程中,SSH协议本身的特征非常明显,所以容易被攻击者进行链路劫持,进而获取通讯原始数据。

为了规避上述问题,其他各种保障通讯数据安全的工具随之也被开发出来,其中具有代表性的Shadowsocks是将SOCKS5客户端与服务端的连接提前到本地进行,Shadowsocks客户端和服务端之间则利用基于预共享密钥机制的多种加密算法,并参考TOR项目中的可插拔传输(Pluggable Transport,PT)技术对其进行二次混淆加密,很好的实现了去特征化,使得传输的流量全部加密且看起来只是普通的TCP或UDP数据包,从而能够进一步保证通讯数据的安全性。

当然,数据通讯安全保障工具(本文以shadowsocks为例)也很容易被不法分子用于从事各种非法活动,例如传播非法言论或发起恶意攻击等。为了有效地检测shadowsocks流量,有必要对其通信原理进行研究。接下来我们首先介绍SOCKS代理协议,然后分析基于SOCKS5协议的SSH代理,接着分析Shadowsocks的架构和原理,最后提出一些检测Shadowsocks加密流量的思路。

一、SOCKS代理协议介绍

SOCKS是一种网络传输协议,如图1所示,SOCKS客户端可以通过SOCKS服务端访问目标服务器。

图1 SOCKS代理架构图

以基于TCP的客户端为例介绍SOCKS客户端和服务端的通信过程。

第一步:协商认证

SOCKS服务通常位于TCP端口1080上,如果客户端对服务端的连接请求成功,接下来将会对要使用的身份验证方法进行协商认证。

(1)客户端向服务端发送一个版本标识符/方法选择的消息,格式如下:

其中VER字段是当前的版本号,被设置为0x05,即SOCKS5;NMETHODS字段是出现在METHODS字段中的方法标识符所占用的字节数;METHODS字段的每一个字节表示客户端支持的一种认证方式。

(2)服务端从METHODS给定的方法中选择一种,给客户端返回一个方法选择消息,格式如下:

其中METHOD字段的值在Shadowsocks中一般有两种可能,一种是0x00,表示采用无认证的方式直接建立连接,另一种是0xff,表示没有可支持的认证方式,此时客户端必须关闭连接。

第二步:建立连接

客户端和服务器进入特定于方法的子协商,即身份验证协商,完成之后,客户端和服务端开始发送SOCKS请求和响应信息。

(1)客户端向服务端发送真正的请求细节,格式如下:

其中CMD字段的值在Shadowsocks中一般有两种,一种是0x01,表示建立TCP连接,另一种是0x03,表示关联UDP请求;RSV是保留字段;ATYP是地址类型字段,0x01表示IPv4,0x03表示域名,0x04表示IPv6;DST.ADDR是目的地址字段,其长度随地址类型不同而变化,分别是4个字节的IPv4地址、1个字节的域名长度和域名、16个字节的IPv4地址;DST.PORT是目的地址的端口。

(2)SOCKS服务端与远程需要访问的目标服务器建立连接之后,根据不同的请求,服务端通常会根据源地址和目的地址给客户端回复适合请求类型的一个或多个消息,格式如下:

其中REP字段用来告诉客户端请求的处理情况,0x00表示请求处理成功,否则会直接断开连接,其他字段的含义和请求字段相同。

第三步:数据传输

协商认证和建立连接都是在SOCKS客户端和服务端之间进行的,在这之后,SOCKS服务端在客户端和远程目标服务器之间就只对消息进行简单而直接的转发,

SOCKS协议可以进行具体的代理连接控制,但是由于数据都是明文传输,所以很轻易地就能被监听和控制。

二、SSH代理机制介绍

SSH(Secure SHell)是一种在非安全网络环境中提供安全远程登录及其它安全网络服务的协议。OpenSSH是SSH协议的免费开源实现,相比于Telnet、rlogin和FTP等明文传输协议,OpenSSH会对所有流量(包括密码)进行加密,以有效的消除窃听、连接劫持和其他攻击。此外,OpenSSH提供了安全的隧道功能和多种身份验证方法,并支持所有SSH协议版本。

一开始,客户端和服务端都是简单而直接的进行连接,客户端向服务端发起请求,服务端向客户端返回响应,如图2所示。

图2 客户端和服务端的直接连接

后来,为了提高安全性,SSH的出现为客户端和服务端建立起一条加密通道,客户端和服务端之间的请求和响应都通过SSHtunnel进行数据传输,如图3所示。

图3 客户端和服务端通过SSH隧道连接

因为一些特殊的原因,在实际的网络应用中,经常需要用到代理,SSH的端口转发功能可以实现代理或者是内网穿透,所以可以利用支持SSH远程连接的网站云主机或者邮件VPS等中间机器实现SOCKS代理服务器的功能,也就是SOCKS客户端通过SSH隧道创建SOCKS服务器的功能。如图4所示,首先SSH客户端和服务端基于SSH协议建立起一条加密隧道,然后SOCKS客户端通过隧道向SSH服务端发起代理请求,这样SOCKS客户端就可以通过SSH服务端访问目标服务器。

图4 SSH代理架构图

SSH代理支持三种端口转发,即本地端口转发、远程端口转发和动态端口转发,都是通过SSH创建的连接隧道,基于SOCKS代理协议,实现代理转发或内网穿透。如图所示演示了SSH建立动态端口转发的实例,在本地10010端口启动一个SOCKS5服务,通过本地SOCKS5代理的数据会通过SSH连接先发送给SOCKS服务端192.168.1.9,再从192.168.1.9转发给真正要访问的远程主机。

然后打开浏览器的网络设置选项,代理服务器填写本机127.0.0.1,端口选择ssh -D设置的10100,代理协议选择SOCKS5,保存设置。浏览器的所有流量都将通过SOCKS服务端192.168.1.9,通过代理协议SOCKS5,以代理身份(192.168.1.9)进行转发和接收。

三、Shadowsocks代理机制的原理分析

1 Shadowsocks架构

Shadowsocks使用了SOCKS5代理方式进行数据的中转,但它将SOCKS服务器的实现拆分成了客户端和服务端,即ss-local和ss-server,如图5所示。所以从网络视角看,运行SOCKS5协议的两端是SOCKS5客户端和ss-local;而ss-local和ss-server之间,运行自定义的Shadowsocks协议,它定义了数据传输规则、加解密方法和协议插件,利用基于预共享密钥机制的多种加密算法,并参考TOR项目中的可插拔传输(Pluggable Transport,PT)技术对其进行二次混淆加密,使得ss-local和ss-server之间的流量全部加密且看起来只是普通的TCP或UDP流量。

图5 Shadowsocks代理架构图

ss-local:位于本地,一般在本机或局域网的其他机器,作用相当于传统的SOCKS服务端,为客户端提供代理服务。一方面监听本地的请求,并把要传输的原数据根据用户配置的加密方法和密码进行加密后发送给远程的ss-server,另一方面接收ss-server发送的数据,并将其解密后返回给SOCKS客户端。

ss-server:位于中转服务器,一方面接收ss-local发送的数据,并根据用户配置的加密方法和密码对数据进行解密,还原原来的请求,然后发送给目标服务器,另一方面将目标服务器返回的数据加密后发送给ss-local。

Shadowsocks将SOCKS5客户端与服务端的连接提前,SOCKS5协议的交互完全是在本地进行,而且对网络中传输的数据进行了基于预共享密钥机制的加密和二次混淆,使得传输的数据全部加密且外表看起来只是常规的数据包,没有明显的特征码,所以Shadowsocks能够抵御各种流量分析和深度包检测等。

以TCP连接为例,ss-local通过发送加密的数据流启动到ss-server的TCP连接,数据流的格式为[目标服务器地址][载荷],确切的加密方案因使用的加密算法而异。ss-server接收加密的数据流,解密和解析最前面的目标地址,然后建立到目标服务器的新的TCP连接,并将有效负载数据转发给目标。ss-server也要接收来自目标服务器的应答,将其加密并转发回ss-local,直到ss-local断开连接。所以,ss-server主要是为ss-local执行网络地址转换。

Shadowsocks中使用的地址遵循SOCKS5地址格式:

[1字节类型][可变长度主机][2字节端口]

其中,类型值的定义为:

0x01:主机是一个4字节的IPv4地址

0x03:主机是一个可变长度的字符串,从1字节长度开始,后面是最多255字节的域名

0x04:主机是一个16字节的IPv6地址

2 加密方式

Shadowsocks支持两种加密方式,流加密和AEAD加密,流加密中密文和原文是一一对应的,后来Shadowsocks增加了AEAD类的加密算法,从流加密变成了块加密,增加了探测难度。

3.2.1 流加密

流加密方法只提供保密性,数据的完整性和真实性得不到保证。下表中的流加密方法提供了合理的保密性:

Name

Key Size

IV Length

aes-128-ctr

16

16

aes-192-ctr

24

16

aes-256-ctr

32

16

aes-128-cfb

16

16

aes-192-cfb

24

16

aes-256-cfb

32

16

camellia-128-cfb

16

16

camellia-192-cfb

24

16

camellia-256-cfb

32

16

chacha20-ietf

32

12

流加密的过程为输入密钥key、初始化向量IV和消息message,生成与消息长度相同的密文ciphertext:

Stream_encrypt(key, IV, message) => ciphertext

流解密的过程为输入密钥key、初始化向量IV和密文ciphertext,生成原始消息message:

Stream_decrypt(key, IV, ciphertext) => message

其中,密钥可以直接由用户输入,也可以由密码生成,密钥的生成遵循OpenSSL中计算密钥的函数EVP_BytesToKey。

以TCP流的流加密为例,加密的TCP流是在加密的有效负载数据前面加上随机生成的初始化向量:

[IV][encrypted payload]

3.2.2 AEAD加密

AEAD(AuthenticatedEncryption with AssociatedData)加密同时提供保密性、完整性和真实性,所以应尽可能使用AEAD加密方法,推荐使用下表中的AEAD加密方法。在具备AES加速的CPU上,建议使用aes-XXX-gcm系列,移动设备建议使用chacha20-ietf-poly1305。

Name

Key Size

Salt Size

Nonce Size

Tag Size

chacha20-ietf-poly1305

32

32

12

16

aes-256-gcm

32

32

12

16

aes-192-gcm

24

24

12

16

aes-128-gcm

16

16

12

16

(1)密钥的生成

主密钥的生成和流加密方法一样,遵循OpenSSL中计算密钥的函数,既可以由用户输入,也可以由密码生成。为了解决一次性密钥对重用的缺陷,使用HKDF从预先共享的主密钥派生出每个会话的子密钥,并使用子密钥在流密码和AEAD密码中进行加解密。

HKDF_SHA1是使用SHA1构造的HKDF函数,接受密钥key、随机非加密salt和信息字符串info,生成一个子密钥subkey,即使输入的主密钥是弱的,生成的子密钥在密码上也是强的:

HKDF_SHA1(key, salt, info) => subkey

其中,salt在预共享密钥的整个生命周期中必须是唯一的,信息字符串将生成的子密钥绑定到特定的应用程序上下文。

(2)经过身份验证的加解密

AE_encrypt函数输入密钥key、非加密的随机数nonce和消息message,生成密文ciphertext和身份验证标记tag,对于每次调用中的给定密钥,nonce必须是唯一的:

AE_encrypt(key, nonce, message) => (ciphertext, tag)

AE_decrypt函数输入密钥key、非加密随机数nonce、密文ciphertex和身份验证标记tag,生成原始消息message,如果任何输入被篡改,解密将失败:

AE_decrypt(key, nonce, ciphertext, tag) => message

以AEAD加密TCP流为例,加密的TCP流是在任意数量的加密块前面加上随机salt和每个会话的子密钥,加密块的结构为:

[encryptedpayload length][length tag][encrypted payload][payload tag]

3 二次混淆

如图所示,Shadowsocks在SIP003中支持了插件,与Tor项目中实现的可插拔传输插件非常相似,流量可以通过不同的插件进行二次混淆加密,使其看起来只是各种普通的加密流量数据包,所以不是很容易被识别。SIP003的每个插件都是作为本地端口转发工作的,当前使用较多的插件有simple-obfs、v2ray-plugin。

插件客户端/服务端是作为Shadowsocks客户端/服务器的子进程启动的,如果发生任何错误,插件的子进程应该使用错误代码退出,然后Shadowsocks的父进程也会停止。当用户停止Shadowsocks客户端/服务器时,插件的子进程也将被终止。

插件通过环境变量接受参数。四个必备的环境变量是远程插件服务的主机名SS_REMOTE_HOST和端口SS_REMOTE_PORT、本地Shadowsocks或插件服务的主机名SS_LOCAL_HOST和端口SS_LOCAL_PORT。一个可选的环境变量是SS_PLUGIN_OPTIONS,如果插件需要额外的参数,比如配置文件的路径,这些参数可以作为格式化字符串中的额外选项传递,例如"obfs=http;obfs-host=www.baidu.com ",其中分号、等号和反斜杠必须用反斜杠转义。

4 效果展示

SOCKS协议只是进行具体的代理连接控制,数据都是明文传输,不具备安全性。Shadowsocks的数据传输是建立在SOCKS协议之上的,使用预共享密钥机制,加密方式是预先约定好的,不需要进行协商。因此Shadowsocks融合了代理控制和安全保证,观察者只能判定这个数据流为未知的协议。

通过Shadowsocks访问https://www.baidu.com/,利用wireshark抓包分析,如图所示。红色框中是TCP的握手过程,紫色框中是TCP的挥手动作,两个框的中间是客户端向服务端的请求数据包和服务端对客户端的回复数据包。Shadowsocks客户端和服务端之间的通信有多种可选择的方式进行加密,所以看起来只是普通的TCP或UDP包,这和客户端的原始请求是TCP还是UDP有关。

打开一个请求数据包,如图所示。观察IP和TCP的头部,源地址是客户端的IP地址和端口,目的地址是服务端的IP地址和端口,Data部分是原始请求数据经过加密之后的结果。

打开一个回复数据包,如图所示。观察IP和TCP的头部,源地址是服务端的IP地址和端口,目的地址是客户端的IP地址和端口,Data部分是原始回复数据经过加密之后的结果。

四、小结

Shadowsocks本质上是SOCKS5协议的加密版本,融合了代理控制和安全保证,可以选择多种加密和混淆方式,体积小、速度快、抗干扰能力强,不仅部署简单,而且对服务器的配置要求较低。Shadowsocks使用自行设计的协议进行加密通信,规避了早期SSH代理容易被注入并还原原始数据的问题,因此可以很好的保障通讯链路中的数据安全。

为了防止诸如Shadowsocks这样的隐私保护技术的非法使用,已有很多针对此类流量进行的识别研究。其中基于统计和基于行为的识别方法是目前研究较多的两种方法,都属于机器学习方法,是将人工提取的特征输入机器学习模型进行训练调参,最终得到用于识别流量的模型,一般提取的特征包含网络流特征、数据包特征、加密协议特征和主机通信行为特征等, 探加密流量识别提供了一种可行的方法。还有一些研究中使用了深度学习的方法,能够克服机器学习特征设计的难题,一般的做法是将原始的流量数据视为领域无关的字节数据,经过预处理后直接输入深度神经网络自动学习特征,只要数据足够多并且有足够的代表性,也能得到较好的识别精度。

参考链接:

[1].https://github.com/shadowsocks

[2].https://www.ssh.com/

[3].https://www.ietf.org/rfc/rfc1928.txt

[4].https://toutiao.io/posts/5fo90o/preview

[5].https://shadowsocks.org/assets/whitepaper.pdf

[6].https://github.com/shadowsocks/shadowsocks-org/issues/42

[7].https://github.com/gumblex/ptproxy

[8].https://github.com/madeye/obfs4-tunnel

[9].https://shadowsocks.org/en/spec/Plugin.html

声明:本文来自绿盟科技研究通讯,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。