本文发表于2015 IEEE S&P 会议上,主要贡献是开发了一个SSL/TLS代码的审查工具SSLINT,这个工具能够有效的防止API的误用。
一.研究背景
虽然现在已经诞生了一批如openSSL和GunTLS的可靠TLS实现,但是事实上由于开发者的意识水平以及文档不足等限制,在开发中误用API的情况经常发生,而这就导致了安全API不能起到它们应当发挥的作用。
本文着眼于研究客户端上大规模的SSL证书验证脆弱性的检测,在ubuntu上部署了SSLINT之后,它能够自动检测应用是否使用了SSL/TLS的API以及API误用的情况,这一设计首先将正常的API调用模式进行建模,建模成程序依赖图(program dependency graphs,PDGs)的形式,之后采用图查询的方式查找应用中的API调用是否符合安全的API调用模式,如果查找结果为不匹配就认为出现了API误用。
二.基本思路
对于TLS程序的开发来讲,要想正确安全的使用TLS,程序设计者需要遵循一定的顺序。比如说在openSSL中,如果作为一个客户端程序想要安全的和服务器进行通信,程序员需要首先生成一个SSL_CTX,再使用这个context去初始化对应的SSL结构,之后建立连接并验证服务器端发来的证书。其中任何一个地方出错,比如说没有对服务器的证书进行验证,都会造成可被攻击者利用的脆弱性。
这样,如果能够将这些正确的调用抽象出来,就能够拿程序实际调用的代码与之比较,如果有不符合的地方,就说明存在API误用的情况;还有一种思路是匹配误用特征,但是这需要我们列举出所有的误用方式,由于误用可以是指数多种情况的组合,所以这一条思路是难以实现的。
在之前的工作中,对于TLS/SSL协议及其实现的分析方式主要是手动查找和黑箱(black-box)分析。手动查找就是通过逐行查看代码,发现其中隐藏的漏洞,如著名的heartBleed以及前些年论文中的lucky13;黑箱分析则是将程序的实现当做一个黑箱,通过调整输入和输出来寻找漏洞,比如我们通过字典攻击查找弱口令就是一个典型的黑箱分析。本文的分析方式和之前略有不同,不同于传统的安全分析方法,本文采用了基于静态文本(代码)分析的抽象建模以及特征匹配方式,并实现了自动的漏洞查找,这是本文最为关键的一个创新之处
三.SSL/TLS正确的调用方式及其抽象
3.1 openSSL的API调用
使用openSSL进行编程的客户端在校验服务器提供的证书时不会将证书的校验结果显式的提供给openSSL连接建立API的调用者,而是需要调用者自己去调用相应的API才能发现证书的正确性,openSSL在这里提供了两种方式,第一种是在SSL_connect
之前使用SSL_CTX_set_verify
函数设置需要显式验证的标志位;第二种是在SSL_connect
之后通过SSL_get_verify_result
来获取证书校验结果。
通过以上两种方式,调用者可以确认证书验证的结果,但是openSSL没有提供一个获取证书名与所访问的域名是否匹配的方法。
遗憾的是,在实际情况中,程序编写者可能忽略了采用以上两种方式进行校验,而是直接通过SSL_connet
连接到服务器之后就开始进行通信,而这为可能的中间人攻击留下了漏洞。
3.2 GnuTLS的API调用
在GnuTLS中,函数库提供了对域名认证和证书链认证的支持,这需要使用者调用gnutls_certificate_get_peers
获取对方证书链,之后采用gnutls_x509_crt_import
获取证书链的第一个证书,并使用gnutls_x509_crt_check_hostname
检测证书的名称与访问的域名是否相匹配;验证证书本身是否合法的函数为gnutls_certificate_verify_peer2
。但是在某些应用的源代码中,这两个调用并没有被发现。
3.3 抽象表示
这篇文章里使用了名为PDG(Program Depedence Graph,程序依赖图)的方式来进行API调用过程的抽象。之前人们使用CFG(Control Flow Graph,控制流图)和DFG(Data Flow Graph,数据流图)分别表示了程序的控制路径及数据依赖路径,但是对于一个完整的API调用方式建模来说,不仅要保证数据的依赖是正确的(比如你需要先获取证书再对证书进行验证),还要保证程序的控制流是符合标准的(比如只有当验证通过时才能进行通信),这就使得必须要结合CFG和DFG的特点,也就是PDG。
PDG是一个有向图,每个节点是函数调用、判断语句或者常量变量,如果两个函数之间(或者某函数和某常量之间)存在数据的依赖关系,比如SSL_CTX_new
之后才能进行SSL_new
操作,那么代表这两个函数的节点之间就有一条有向边。但是这并不代表这这两个函数之间在代码上必须是前后相邻的,比如他们中间可以有非本PDG中的其他函数或者变量操作。如果某一个函数需要它前面的判断语句才能够进行,那么这两个节点之间存在一条有向边,如论文中图3所示:
四.如何进行误用的查找
由于程序建模之后是一个有向图,而我们的思路是在这个有向图中寻找符合标准调用的调用路径,所以一个图搜索或者说图查询语言就必不可少。
本文所使用的图查询语言是Cypher,是一种说明性质的语言,用于查找有向图中匹配的样式,这种语言的标准形式如下:
在以上查询中,vi和vj代表PDG中的两个节点,方括号中的l代表了依赖的类型,可能是数据依赖,即data,也有可能是控制流依赖,即control,星号表示既匹配直接的依赖关系(两个点中间有一条边的形式)也匹配间接的依赖关系。condition代表了查找的条件,具体来说,这个条件指的是vi和vj所代表的点是什么,在下面的图中可以更加明显的看出来这一点。最后一个return自然就代表了查询到之后返回的值,一般而言都返回对应的节点,文章中具体的查询语句如下所示:
五.实验
5.1 一些实验细节
实验中,由于各个软件的编译方式都不一样,因此作者采用了包管理工具(apt),进行自动化的编译和处理。在构建PDG的时候,使用了名为CodeSurfer的工具,从文章中的意思来看应该是一个可以基于程序具体运行时具体的API调用进行控制流及数据流分析的软件。输出为上面提到的PDG。
5.2 实验过程
在生成了程序的PDG之后,实验者采用图查询方式的对安全的API调用模型进行查询,如果任意一条安全的API调用路径被找到,则认为该应用程序是遵守了安全模型进行API调用的,否则认为应用存在安全漏洞。
5.3 实验结论
实验者发现了ubuntu上27个未被发现的应用程序漏洞,并将它们提交给了开发者,并得到了其中14个的确认,这些应用程序包括邮件客户端、实时聊天软件、非浏览器http应用以及数据库客户端等。对于未得到确认的存在漏洞的应用,实验者构造了具体的中间人攻击来确认漏洞的存在。
除此之外,实验者还探究了其他一些方面,比如自签名证书的滥用、社区开发的弊端,这些都是可能诱发安全隐患的因素。最后一部分中,作者提到了本文的一些局限性,主要在于静态分析的局限性(不能分析内存的调用情况以及未事先定义的库函数)、CodeSurfer的稳定性(应用所含代码过多容易造成软件崩溃)、自定义函数等等。
六.思考
这篇文章采用一种之前没在这一领域中用过的方法来分析应用中API调用的安全性,这种方法思路上并不难,之前应该也在其他的领域中有应用,但是应用到安全性分析中还是第一次。这篇论文之所以能够发到这么好的会议上,可能是因为它开创了一个新的方法,这个方法可能会给接下来的各种分析以启发,还有一个可能性是这篇文章中的工作量很大,给出了大量的实例,这都是需要我去学习的。