2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰

大概6月份的时候,那时候保研的各个事情都还没有开始,有一天辅导员突然在保研群里发布了北大信息工程学院的李挥教授的招生PPT。

李挥教授的研究方向其实和我的兴趣还是挺合拍的,但这位老师的口碑实在是差到爆炸。不过当时正好赶上要在大概一周之内确定我们大三的暑期实训的内容。我在跟着学院做开发项目、找本校老师做项目之间想了想,觉得还是应该选择去李挥教授这里做这次实训。把保底留在李挥教授那里相对于我来说还是比留在武大随机一个老师好些。

李挥教授的主要成果在于一个多标识MIN网络,大概可以理解为各种未来网络体系结构的整合和实现(没错他的硕士生真的在做这个超大网络的各方面实现)。然后我是报了网络体系结构方向,那边的学长给我安排了题目——为之前已经实现的“代理”寻找能把经过的HTTPS报文打印出来的方法。

本文接下来的部分就着重于介绍关于这个项目相关的技术上的问题了。如果有同学对李挥教授的课题组有兴趣,可以看一下我之后准备写的另一篇保研总览经验帖,这里不再赘述。

本文着重于对一些框架和知识点的概览,不会涉及到很具体的技术细节。事实上,本文更适合作为一篇日记而非技术博客来阅读。

选题分析

这个项目的背景大概是这样,那边想要建立一个企业办公用的网络特区,在特区里政府会让工作人员能够访问任何网站进行些特殊工作。但同时又不能让工作人员无限制地访问外网,这就需要对他们的访问内容进行监控。具体做法就是做一个“代理”,工作人员的流量都经过“代理”和外界交互,防御系统会对从这个“代理”出去的流量放开。而这个“代理”首先要实现的一点就是能够把流经它的信息都打印出来,只有先能在“代理”上获取到流经的信息才能去谈后续的处理问题。

注意一下,这个需求并不想使用比如禁掉域名之类的简单方案,它的第一步目标很明确,能够获取到所有经过的信息的内容。目前主要考虑的是网页,也就是要能打印出来传输的HTML文本等具体内容。

在之前,学长们已经用socks5协议和epoll机制实现了“代理”基本的转发功能并且能打印出来通过HTTP协议传输的各种信息内容。但是对于HTTPS加密过的报文,在“代理”处获取到的是密文而非明文,如果就直接把这些密文打印出来的话是完全无法分析的。但是这个“代理”又必须要具有能够获取到HTTPS报文的明文信息的能力,所以我的任务就是去寻找在“代理”处打印出解密后的明文的内容的方案。

除此之外,这次编写的“代理”可作为之后MIN网络体系中的“代理”部分的基石,因为MIN网络它一个特性就是可监管,不过这是后话了。

整体设计

如果想要把这一切悄无声息的用黑客手段实现,那基本是不可能的。因为其实这个项目本身就完全和HTTPS的理念背道而驰,HTTPS几十年来发展的核心就在于传输信息的明文内容只能被网络的两端(客户端和服务器)知道而无法被网络核心中的任何节点获取,假如说一个“代理”可以在连接上之后悄无声息地获取到HTTPS传输的所有内容的明文,那么HTTPS也就没有存在的必要了。

但是这个项目有个突破点——用户对于“代理”的行为是知情的。这意味着我们可以让用户进行一些特定操作,但是也不能让用户的操作过于复杂,不然用户安装都安装不好也就不用谈使用了。

于是提出的思路大概如下:当“代理”收到了客户端发出的第一个TLS握手报文后,由“代理”解析出其中蕴含的服务器端的信息,之后由“代理”向服务端发送握手请求建立“代理”-服务器的一个TLS连接,之后“代理”再向客户端发送对于握手信息的响应,建立“代理”-客户端的TLS连接。这样,由于客户端和“代理”分别在一个TLS连接的两端,所以“代理”可以获取到客户端发送的内容的明文(当然,其在从客户端到达“代理”的过程中仍以密文形式传输)。同时由于服务器端和“代理”分别在另一个TLS连接的两端,所以“代理”也可以获取到服务器端发送来的信息的明文。我们的“代理”在这个架构中就起到了一个对两个连接中接受到的内容进行转发的作用。当然,转发的同时会“顺手”打印一下。

2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第1张图片
那么如果是普通HTTP连接又该怎么做呢?当客户端发送了HTTP的第一个握手报文时,“代理”可以判断出来这不是一个TLS握手报文,接下来就会将其直接转发给服务器。这样,客户端和服务器就会通过接下来的握手创建一个直接的HTTP连接。之后“代理”会只负责转发工作,就如同路径上的任何其他一个路由器一样,唯一不同的是“代理”还会把经过的报文“顺手”打印一下。

注意,对待TLS报文和普通的HTTP报文时“代理”都要承担转发工作,然而对TLS转发时是两个SSL/HTTPS连接间的转发,而对普通HTTP报文时则是一个HTTP连接内的类似于网络层协议的转发(虽然在具体实现上仍然是传输层的两个TCP连接间的转发),这是完全不同的!

写到这里想必读者会有点迷糊,HTTP是建立在TCP连接上的一个无连接协议,那么什么叫一个HTTP连接里的两个TCP连接呢?其实这里我是随口说的一个名词,没有客观来源。但是我们考虑一下,假如说我们HTTP的客户端C和服务器端S之间有一个网络节点A,在C和A之间建立TCP连接,在A和S之间也建立TCP连接,同时我们保证A的转发绝对可靠,那么我们可不可以把这个系统看成C和S之间只存在一个TCP连接呢?

其实这个A就是现在常见的“代理”的抽象,它存在,但是它从整体上可以忽略。那么更进一步,C和S可不可以直接进行一次TLS升级,即直接把这个抽象的唯一一个TCP连接升级成TLS呢?答案是完全没有问题,因为TLS的握手报文完全可以通过两个稳定的TLS连接在C和S之间进行传输,最终就能形成一个直接作用于C和S的TLS连接。

所有,我口胡了一个HTTP连接的概念,意指在C和S之间进行直接交互,A可以忽略的通过HTTP协议传输报文的过程。

其实本质上,对于HTTPS流量和普通HTTP流量,我们分别将我们的系统看作了“两个实际的TCP连接”和“一个虚拟的TCP连接”。

SSL与TLS

我们都知道,HTTP协议是广泛使用但是不安全的,想要安全我们就要使用HTTPS协议,那么HTTPS协议具体是什么呢?

HTTPS协议 = HTTP协议 + SSL/TLS协议,在HTTPS数据传输的过程中,需要用SSL/TLS对数据进行加密和解密,需要用HTTP对加密后的数据进行传输,由此可以看出HTTPS是由HTTP和SSL/TLS一起合作完成的。

2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第2张图片

SSL的全称是Secure Sockets Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。SSL协议在1994年被Netscape发明,后来各个浏览器均支持SSL,其最新的版本是3.0

TLS的全称是Transport Layer Security,即安全传输层协议,最新版本的TLS(Transport Layer Security,传输层安全协议)是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本。在TLS与SSL3.0之间存在着显著的差别,主要是它们所支持的加密算法不同,所以TLS与SSL3.0不能互操作。虽然TLS与SSL3.0在加密算法上不同,但是在我们理解HTTPS的过程中,我们可以把SSL和TLS看做是同一个协议。

本小节部分节选自:Https原理及流程

TLS协议格式与过程

关于TLS协议格式与过程,最基本的是参考以下RFC文档:

  1. RFC 2246 : The TLS Protocol Version 1.0
  2. RFC 2818 : HTTP Over TLS
  3. RFC 4366 : Transport Layer Security (TLS) Extensions
  4. RFC 5246 : The Transport Layer Security (TLS) Protocol Version 1.2
  5. RFC 6066 : Transport Layer Security (TLS) Extensions: Extension Definitions
  6. RFC 6176 : Prohibiting Secure Sockets Layer (SSL) Version 2.0
  7. RFC 8446 : The Transport Layer Security (TLS) Protocol Version 1.3

根据这次项目中的实践,现有的大部分商业网站支持的仍是TLS 1.2的协议。因此应着重理解RFC 5246的相关内容。

关于SSL/TLS协议握手过程的一些介绍博客:

  1. 数字证书原理,公钥私钥加密 - 读过最浅显易懂的密钥topic
  2. 图解SSL/TLS协议
  3. SSL/TLS协议运行机制的概述

关于SSL/TLS协议报文格式及具体交互过程的一些介绍博客:
TLS1.2协议设计原理

其他资料:

  1. SSL/TLS与OpenSSL的发展
  2. 一个专注于研究TLS1.3的博主的专栏
  3. TLS协议分析(这篇比较专业,不推荐新手阅读)
  4. QQ群:201757544

OpenSSL

恰似LLVM对于编译器,OpenCV对于图像处理,OpenGL对于图像合成,在安全编程领域,也有些广泛使用的框架,也许其中最值得每个踏入此领域的人都了解的就是OpenSSL.

OpenSSL是一个用于传输层安全性(TLS)和安全套接字层(SSL)协议的健壮的、商业级的、功能齐全的工具包。它也是一个通用的密码学库。最重要的是,它是开源的。OpenSSL提供了两种可供使用的版本,第一种是许多命令行命令的集合,这种被封装的命令集可被那些需要这些功能的非底层开发人员使用。第二种则是C语言库,用于需要直接在代码中调用相关功能的底层开发人员。

OpenSSL的官网:OpenSSL

但是,OpenSSL有一个极其被人诟病的缺陷,它的文档简直是惨绝人寰,或者说它几乎没有文档,我们打开本应该是OpenSSL最详细文档的manual,页面如下:

2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第3张图片
各个API一个一个罗列在这里,没有分类,没有分页,更没有相关的模块介绍,最重要的是,没有任何一个引新手入门的tutorial,我就只能在这页的几千个函数中去寻找自己需要的函数。
2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第4张图片
当进入到某一个函数内部时,就可以发现其是由多个函数共享一个detail界面。更可怜的是,即使是detail界面,这四个函数总共的介绍也就只有上图所示的那一点点。

OpenSSL的命令集版本的文档倒是编写的很完善,可是对于库函数版本的文档却彷佛完全没有下文。实在是让人恼火。另外OpenSSL的开发团队现在正在努力开发OpenSSL的3.0版本,估计文档工作又要往后拖了。

对于OpenSSL使用的困难是我在这个项目中遇到的最大的困难之一,它使我的项目进展至少延缓了一倍。下面介绍一些我在项目过程中找到的有关官网之外的OpenSSL入门的其他资源:

  1. OpenSSL-Wiki : OpenSSL的Wiki被志愿者们维护用来作为OpenSSL官网文档的补充,在这里可以看到许多最常用的函数的详细解释和编程样例。也可以看到许多其他的阅读资料。美中不足的是,其中一些条目所使用的代码已经不再适用于现在的OpenSSL版本。另外其只对OpenSSL中最常使用的一小部分编写了文档。

  2. OpenSSL-Github : OpenSSL在Github上的库,在其中的issuer中可以找到不少有用的信息。

  3. Cryptography Stack Exchange : 这是一个有关加解密的Stack Exchange社区,许多OpenSSL的编程问题可以在这个社区中找到答案。

  4. Secure programming with the OpenSSL API:IBM开发者社区的一篇可作为OpenSSL的tutorial的博客。

  5. 用OpenSSL编写SSL,TLS程序(2) : 一个使用OpenSSL实现TLS连接的client/server端实现。

数字证书

让我们来考虑我们的项目结构中最后一个需要考虑的部分,数字证书。

如果已经阅读了关于TLS协议握手过程的基本内容,我们就会知道在server端收到client发来的client hello后,server不仅会返回server hello,还会返回自己的数字证书(server certificate)。数字证书的作用是让服务器证明自己“我是我”。

关于X509数字证书的RFC文档为:

  1. RFC 6960 : X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP

下面几篇文章简明易懂地介绍了一些数字证书的核心概念:

  1. 数字证书原理,公钥私钥加密 - 读过最浅显易懂的密钥topic
  2. X.509数字证书的结构与解析
  3. openssl、x509、crt、cer、key、csr、ssl、tls 这些都是什么鬼?
  4. 根证书和中间证书的区别

2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第5张图片

下面一些内容介绍了一些基本的使用OpenSSL函数操作数字证书的方法:

  1. Parsing X.509 Certificates with OpenSSL and C(这篇文章有很多代码现在的版本已经不能再运行,它的一个中文版本为用openssl和C解析X.509证书)
  2. Extract RSA public key from a x509 char array with openssl
  3. OpenSSL自建CA和签发二级CA及颁发SSL证书(使用OpenSSL命令在终端中签发证书)
  4. openssl-sign-by-ca(Github上一个使用OpenSSL在代码中签发证书的demo)

回顾一下之前的整体设计小节,那么我们的设计就会遇到一个问题,当Client端与“代理”建立TLS连接后,Client端收到的是“代理”的证书而非Server端的证书,那么Client端会不会因为收到的证书不匹配而报错呢?或者说假如我们在“代理”上伪造出来一个域名正确的证书,Client端能否信任它呢?(我们不能够把“代理”从Server端获取到的证书直接发送给Client端作为“代理”的证书,因为“代理”无法获取到Server端的私钥!我们如果想要建立一个TLS连接必须在这个连接的服务器端同时提供证书和证书对应的私钥)

如果读者已经读过了我上面列出的各个资料,那么应该很容易想到一个比较好的解决方案,我们首先在“代理”上自签发一个根证书(称为证书A),接下来每当收到一个client端发来的Client Hello请求时,我们首先让“代理”和server建立一个SSL连接,通过这个连接获取到server端的证书,然后通过这个获取到的server端的证书用我们的根证书A签发一个信息完全一致的证书(称为证书B)。因为证书B和server端的证书的信息是一致的,所以我们可以通过客户端的域名检查。但此时证书B还无法被客户端信任。于是我们需要在用户安装我们的“代理”客户端程序时将根证书A加入到用户的证书信任列表中。这个过程可能会需要用户进行一些操作,没有关系,反正用户是知道我们的所作所为的,写在使用文档中让他们在安装时完成这些操作就可以了。这样,由于用户信任了我们的根证书A,那么他们也会信任有这个根证书签发的所有证书。这样证书B就得到了信任,client端就不会再报任何错误了。
使用这种方法,用户只会在安装过程中进行些额外操作。一旦安装完成,之后就和平常浏览网页的感受一模一样了。唯一可怕的是,其实他访问的每一个网站的证书都是我们伪造的。

最后特别提醒一下,数字证书的域名不仅仅会存在于Subject Name中,还可能存储在Extension字段中的subjectAltName中。另外这点坑了我很久。

SNI

SNI的Wiki解释如下:
服务器名称指示(英语:Server Name Indication,缩写:SNI)是TLS的一个扩展协议[1],在该协议下,在握手过程开始时客户端告诉它正在连接的服务器要连接的主机名称。这允许服务器在相同的IP地址和TCP端口号上呈现多个证书,并且因此允许在相同的IP地址上提供多个安全(HTTPS)网站(或其他任何基于TLS的服务),而不需要所有这些站点使用相同的证书。

简而言之,使用了SNI机制的服务器在建立与客户端建立TLS连接时不能将TCP直接升级,必须在这之前进行一次声明,声明客户端将与此服务器的哪一个名称建立TLS连接。

如何在OpenSSL中处理SNI可以参考此网址:How to implement Server Name Indication (SNI)

(扩展)加解密算法与密钥协商算法

相信任何一个对网络安全稍有了解的人,都会对课堂上学过的MD5、SHA、DES、AES、RSA印象深刻。如果忘记了,可以看一看这篇博主的文章回忆一下。

  1. 第一篇、MD5算法和SHA-1算法
  2. 第二篇:对称加密及AES加密算法
  3. 第四篇:非对称加密及RSA加密算法

这些算法在TLS中主要是会组成各种加密套件,每一个TLS连接都对应着一种固定的加解密套件。

比如如果双方选取的加密套件为TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。则密钥交换算法使用ECDHE(短暂椭
圆曲线),身份认证算法使用RSA,对称加密算法使用AES_128_GCM,摘要算法使用SHA256。

加密套件由clienthello报文指定内容,由serverhello报文最终确定,具体过程可以参考之前TLS协议格式与过程小节中的一些博客。

我们分析刚刚提到的TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256加密套件,其中RSA、AES、GCM都是我们之前非常熟悉的。但是ECDHE是什么呢?

百度一下很容易就会知道它是一种密钥协商算法。密钥协商算法最经典的应该教科书上都会讲的DH算法,but DH is too old.
现在主流密钥协商算法早已更替了十几次,连ECDHE算法都不再是现在最先进的算法了。

但是ECDHE算法利用椭圆曲线进行加密仍然是非常有意思的,这里整理了一些资料以供感兴趣的读者深入了解:

  1. 迪菲-赫尔曼密钥交换
  2. 椭圆曲线迪菲-赫尔曼金钥交换
  3. A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography(特别推荐阅读这篇,其中很多动画做的很好)
  4. Trapdoor function
  5. ECDH密钥协商算法

关于ECDHE在TLS协议中具体实现的RFC文档为:

  1. RFC 8422 : Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS) Versions 1.2 and Earlier

当然,其实我们大多数情况下并不需要理解这个算法的具体数学含义和计算过程,我们只用调用实现了它的API就可以了。

(扩展)辅助工具

在学习的过程中,误打误撞地将很多工具用在了项目中,这里记录一下。

WireShark

WireShark是一个非常优秀的抓包分析工具,可以用他来判断我们的握手过程从哪一步开始出现了问题。
2020年6-8月总结——解析“代理”流量中的TLS报文、网络安全初瞰_第6张图片
如图所示,WireShark可以很直观的显示出报文中的每一个字段是什么内容。当然,在一般情况下它是无法对密文信息进行展示的。不过网上也有许多用WireShark显示密文信息的教程,我没有尝试,就不过多介绍了。

CURL

curl远比你想象的更强大!它的参数可以为你提供各种各样的测试,包括是否使用代理、DNS解析放置在代理处还是客户端处、是否模拟某一个浏览器发送请求,它甚至还可以测试下载速率!

curl - How to use

apt-get与vcpkg

对于java,我们可以用maven或者gradle来很方便地进行包管理,C#的nuget和python的pip也可以进行类似的功能。那么,C++有没有类似的工具呢?

如果在Ubuntu下很简单,用apt-get就可以解决。几乎所有能想到的依赖包都在apt-get库中,就想安装应用程序一样使用apt-get就可以很方便的安装这些包。

如果在Windows下,我们可以使用vcpkg来做到这点。vcpkg是微软为了解决C++依赖混乱而开发的工具,它完全开源而且现在已经得到了广泛好评。

vcpkg Github

doxygen

那么,C++有没有类似于javadoc那样的文档生成功能呢?

我们可以用doxygen来实现这一点,doxygen是一个非常强大而灵活的文档生成工具。LLVM的文档就是用doxygen生成的。用在C++程序上再合适不过了。

doxygen官网

项目代码

考虑到现在网上关于OpenSSL的可阅读和学习的代码实在是太少,这里本来应该放一下项目的实际代码的。其实这个项目的核心代码非常短,只有两三百行,正适合作为像我这样的OpenSSL新手的学习资料。

但是考虑到这个项目还是应该保密的,所以这里还是先做成待补充吧,等到之后如果可以放了再放上来。也许我到时候可以再写一篇纯粹的需求分析-技术选型-代码实现的技术博客,不像现在这样归纳自己在各方面了解到的新东西,阅读起来也会舒服些。

结语

这几个月的学习过程过得…很奇妙。

我本来是完全没有想过去碰安全方面的东西的。因为我诚然之前是对安全、黑客、逆向这些一丢丢兴趣都没有。我知道有很多同学是对安全有真兴趣的,但是我诚然对这方面一点也提不起来劲。而且我感觉我本科学的东西广度已经够广了,一点也不想再去给自己开辟新的知识域了,只想去找个自己有点兴趣的方向深入钻研一下。

然而还是由于项目需求去恶补了这么多知识,还是又填充了自己安全方向的空白。说实话在保研期间,莫名奇妙看了很多东西,视频编解码、视频帧插入、网络安全、软件测试,这些本来觉得自己不会有缘分去接触的东西现在也都接触了。而且如果不出意外的话,应该之后研究生就会去研究软件测试了。真是未曾设想的道路。

有时感觉计算机专业的人真是身不由己,哪怕自己的主方向不是这里,哪怕说自己对哪一块并不感冒,总会有些必须要做的理由让你在某个时间把这块补上来。这是未来生活的常态吗?问了两个学长都说是的。但是我也不太确定。不过学习总是不至于令人厌恶的。一样东西学也便学了,现在看自己不会的东西竟有时会产生一种平常心,觉得不过是知识与时间的交易。我想我也许还是可以做一个学者的吧。

这次的项目一方面来看工程量并不大,最后我整理好代码后总共我添加的有益代码也就500行左右,在2个月的时间下显得很少很少。但是由于只有我一个人在做而且实验室的学长学姐们之前也没有接触过这方面的内容,这个工程开发项目硬生生被我做成了研发。为了摸清一个个握手报文之间的交互而苦啃上百页的英文RFC文档的日子仍然历历在目,为了寻找诡异的报错的解决办法寻找各个网站的蛛丝马迹的经历仍然仿若昨天。中文博客流于表面,官网文档缺失、StackOverflow总是热衷于与我关注点不同的问题,Wiki和其他国家的人的博客落后于现在的OpenSSL版本。我真的很少能在使用了Google和StackOverflow后仍感受到无力感,这次算是一次。

无论如何,这个工作做完之后学长们对我的评价还是很高的。他们称赞我说能做出这个工作是很厉害的,也给了我到李挥老师那里读研的offer。今年李挥老师的offer诚然不是很好拿到,当李挥老师给我打电话确认时我另一位同学便没有接到任何消息。不过我最后没有去这里。总之,我想我这两个月还是在项目或者说学习层面上做了些工作的,我想给自己打个70分还是没有问题的。

你可能感兴趣的:(SSL,阶段总结,网络,安全,https,tls)