shadow tls

    渗透测试 lz520520 2年前 (2022-10-07) 858次浏览

    前言

    最近看到v2ex上有人分享了一个有意思的技术
    https://v2ex.com/t/875975


    shadow tls

    shadow tls

    简单来说,这个技术可以在tls握手阶段实现完全合法有效的与指定域名网站的握手,而后续的传输数据阶段则是传输自身的恶意payload。
    这样我可以让tls握手阶段,SNI以及证书同步伪装,使得流量更加可信。

    对应的demo项目 https://github.com/ihciah/shadow-tls

    tls协议

    分析之前,先搞清楚tls的协议结构。

    1. tls分为两层,记录层和握手层,记录层只有一种记录协议;握手层有4种协议,Handshake、Alert、ChangeCipherSpec、ApplicationData。
    2. 协议流程,握手阶段和数据传输阶段;握手阶段,常用到的握手层协议有Handshake、Alert、ChangeCipherSpec,而数据传输阶段就是ApplicationData。

    先说下分层,如下图所示。
    shadow tls

    shadow tls

    记录层

    记录层的协议只有记录协议,长度5字节。
    记录协议负责在传输连接上交换的所有底层消息,并且可以配置加密。每一条 TLS 记录以一个短标头开始。标头包含记录内容的类型 (或子协议)、协议版本和长度。原始消息经过分段 (或者合并)、压缩、添加认证码、加密转为 TLS 记录的数据部分。

    • Content Type(1 bytes):用于标识握手层协议类型
    • Version(2 bytes):tls版本信息
    • Length(2 bytes):握手层数据包长度

    PS: 简单来说,记录协议主要功能是对握手层进行数据压缩、加密、完整性保护等等。

    Content Type有4个值,参考go官方库说明如下,可以看到这些类型在上面数据包中也有记录

    Version

    握手层

    Handshake
    shadow tls

    Alert
    shadow tls

    ApplicationData
    shadow tls

    ChangeCipherSpec
    shadow tls

    数据包交互

    如果了解过tls协议,会知道tls分为握手阶段以及数据传输阶段。
    交互流程如下,握手阶段主要进行共享密钥生成以及身份认证,数据传输阶段就使用生成的共享密钥进行加密传输。
    shadow tls

    数据包
    shadow tls

    代码实现层面
    在通过tls封装后,write实际操作如下,会进行Handshake
    shadow tls

    判断握手是否完成
    shadow tls

    未完成握手会调用握手函数,但这里可以看到只是一个函数签名,因为对于server和client的握手处理是不一样的,需要传入不同的函数实现。
    shadow tls
    shadow tls

    比如,clientHandshake,会生成clientHello发送,并读取serverHello等一系列操作。
    shadow tls

    分析

    根据上面的简单分析,握手阶段,服务端会返回一个Certificate包,包含了该服务端的tls证书,其中还包含了证书链,这也是我们浏览器上能查看服务端证书的原因,并且可以根据证书链来校验证书合法性。
    shadow tls
    而数据传输阶段,数据包格式较为固定,均为Application Data,并且握手层一般是通过握手阶段协商好的密钥进行加密传输的。
    shadow tls

    所以shadow tls的实现原理也就出来了。

    1. 握手阶段,服务端将客户端的请求转发到一个可信域名上,这样保证流量侧看到的服务端证书是一个可信域名的证书
    2. 等握手完成后,数据传输阶段,停止转发,客户端和服务端之间加密传输恶意payload即可。那么这里就有一个疑问了,由于tls的防中间人攻击,使用的是非对称算法进行握手协商出共享密钥,我的服务端是拿不到的,其实这个无所谓,我看不到,中间设备也同样看不到,那么我的客户端和服务端用一个假的密钥加密数据伪造一个Application Data进行传输,在中间设备看起来也是完全正常的。

    实现

    原理就这么简单,实现的话,只需要注意一下握手结束的标识,将转发模式切换成恶意payload通信模式即可,我这里选择的是判断接收到第一个application data协议的包,则切换模式。

    client

    编写前,review了下官方tls库,写的针不戳,这里参考他的写法,也是将普通conn封装一层。
    shadow tls
    shadow tls
    同样,握手也是在write和read时,先判断是否完成握手,未完成则先进行握手。
    完成的话,write就构造application data,格式如下;read就读取后,解密数据,key目前暂时写死,后续考虑一些其他协商方式。

    shadow tls

    因为主要功能是在于服务端转发和切换模式,而客户端握手就相对简单了,将conn封装到tls.Client中,然后调用Handshake(),即可发送握手,而这里有个小trick,封装后的tlsConn,只进行握手,而数据通信还是使用原来的conn,这样就不会受tlsConn自身协商的算法以及key限制了。
    shadow tls

    server

    server端代码如下,先和可信域名建立一个tcp连接,然后起一个goroutine,等待可信域名响应数据,写入客户端连接。
    再一个循环等待读取conn连接,将他写入到可信域名连接里,一旦判断到ContentType是ApplicationData则退出循环,表明握手结束。

    过程看起来很简单,但测试发现读取tls数据时老是出问题,暂时不得而知原因,正常来说根据tls数据包格式,先读取5字节,然后根据长度字段再继续读取剩余部分,应该正常。
    最后还是参考了官方tls库的方法,通过一个bytes.Bufferconn中读取数据,应该是atLeastReader的实现比较巧妙吧。
    shadow tls

    测试

    封装好库,测试代码也就很简单了
    shadow tls

    数据包,握手阶段转发可信域名,后续application data用固定密钥加密伪造

    在9995端口监听,可以看到客户端通过该端口可以正常上线,而通过浏览器访问,会返回可信域名的页面(这里是做了一个伪装,区分了下上线流量和浏览器访问流量,增加迷惑性),并且证书还是有效的。
    PS: 这里绑定host,是为了更直观证书的有效性,不绑定也不会有区别,只是IP访问无法直观看到证书有效性。
    shadow tls

    这样在原来的SNI欺骗之上,增加了可信域名证书,让通信流量更加趋于正常。

    参考


    Security , 版权所有丨如未注明 , 均为原创丨
    转载请注明原文链接:shadow tls
    喜欢 (1)