Article Learn

证书检测二三事

Post by 那我就随便唱两句 at 2016-10-25 14:45:53

注:本文为“小米安全中心”原创,转载请联系“小米安全中心”

上期回顾:自动化web安全测试

0x00 这是开篇

年初时候,网上有这么一篇文章,题目叫“加速HTTPS普及!谷歌Chrome要为所有HTTP网站打上红叉”,有兴趣的瓜众可以去Google一下。历史总是惊人的相似,上学的时候没有答好题要打红叉,现而今,网站没有部署HTTPS也要打红叉,形如图1。当然,要想变绿也可以,看到那些CA机构笑眯眯的拇指搓着中食指了吗?虽然需要破财,虽然我们代表朝鲜伊朗叙利亚强烈反对,但不妨碍这是大势所趋。

图1

0x01 证书简介

说到HTTPS,就不得不提证书,而对于其中的TLS协议、加密算法细节等我们按下不表,毕竟文章要契题,更重要的是,这些细节目前我也不太懂啊。对于证书的一些概念,网上有很多资料可以帮助了解,可以根据具体细节需要进行查找,其中的术语我就不介绍了,起码PKI、X.509、CA是要了解下的,常见的pem、crt、cer、key、der等后缀文件是干什么的也是要了解了解的(请见参考资料1)。

证书一般由CA(根CA、中级CA等)机构签发,常见的CA机构如:Symantec、Comodo、Godaddy、GolbalSign 和 Digicert等,我们在一般证书信息中都可以看到,然而,对于我们自己给自己颁发证书的Sinorail Certification Authority,我表示很尴尬。大部分企业网站证书需要向这些CA购买,而且价格不菲,当然也有一些免费的证书可以申请,比如StartSSL,比如Let's Encrypt,这些免费证书个人网站用用无可厚非,但目前我还没见过哪个大点的公司在使用,当然不排除以后会用,尤其是Let's Encrypt,麻烦是麻烦了点,可是,省钱啊。

图2

对于证书包含的内容,见图2: 
- 颁发对象:颁发给谁,域名要匹配 
- 颁发者:颁发该证书的机构 
- 有效期:证书的起止日期 
- 指纹:签名标识 
- 证书链:结构及其内容(颁发对象、颁发者、有效期、指纹等)

图中这些信息都进行了一些调整,以便展示,具体会更加复杂一些。这些信息是后续对证书进行检测的前提。

0x02 证书检测

是的,到重点了。

你也看到了,获取服务器证书的信息并不困难,不就是鼠标点点点吗,然而,对一些企业内部大量域名逐一点击查看检查,这明显是智商不达标的行为。我可是程序员啊,所以重要的事情说三遍,自动化、自动化以及还是自动化。

说到这里,可能有朋友会问,网上不是有这类服务么,而且有的还提供了终端工具。你说的是sslabs(请见参考资料2)吧,这我怎么会不知道。功能很强大有没有,检测很全面有没有,但是,太慢了,且不可控。慢的主要因为有以下几点:

  1. 检测项太多。这个网站提供了相当全面的检测,包括协议、漏洞、加密套件以及模拟各种客户端类型的请求,以你智慧能想到的它基本都检测了,但是,我只需要检测证书相关啊,如同我要吃个苹果,结果给了果园让我等着秋天丰收。

  2. API限制。能提供API接口调用服务,而且还提供了各种语言的终端工具,这点我是很欣慰的,但是,美好的事物总是稍纵即逝 - 接口有限制,并发要限制、速率也要限制,具体见 Qualys API Limits(请见参考资料3)。

那么,我们的目标是什么呢?

  1. 速度要快。最起码的并发检测应该要有吧,毕竟我们也是有上万域名的企业。如果半分钟检测一条,我个人情感上是无法接受的。

  2. 目标专一。需要的我们要检测到,不需要的我们就不检测了。目前主要是检测证书是否正常,后续可能会添加其他检测项。

  3. 结果JSON化。检测结果要作为固定JSON格式返回,无论后续是格式化输出,存储,标准的JSON格式都是极好的。

既然目标已然这么明确,那么接下来就是定义什么会导致证书不可信,这是我们进行检测依据所在。原因是很多的,具体来说,我把它们分为三类(欢迎补充):

1.证书本身有问题 

  • 证书激活日期未到

  • 证书已过期

  • 证书与域名不匹配

  • 证书已被吊销 (本文没考虑)

  • 证书签名不安全

2.证书配置有问题 

  • 证书链有缺失或冗余

  • 证书链顺序有误

  • 证书链中的证书过期、被吊销、签名不安全等

3.CA机构有问题 

  • 根证书不是已知受信任CA签发(例如自签名证书)

有了目标,知道了检测项,接下来就是如何进行检测,我们对于上述导致证书不可信的问题逐一进行检测。

是否激活、过期

这个是很好检测的。大部分编程语言都有证书相关的库(Python的话,我建议直接用Python 3会比较好,这方面一些有用的函数都是Python 2.7.9以后才有的),具体如何请求获取证书信息,因语言不同形式也可能有差别,但大体思路是一致的,具体参见所使用编程语言的手册,这里我是采用Go来实现,毕竟Go的标准库源码美观多了。需要注意的一点是,很多库本身是有一定证书合法性检查的,但是无法按我们的需求来进行检测,更为关键的是证书有问题会直接报错,无法获取证书信息。所以,无论采用什么语言,无论该证书是否合法,必须先要获取证书信息才能进行后续更详细的检测,因此这里要在语言层面忽略对证书合法性的检查,防止提前报错,具体到Go中就是(其他类似):

conn, err := tls.DialWithDialer(dialer, "tcp", domain+":443", &tls.Config{    InsecureSkipVerify: true,  # 设置忽略证书安全性验证})

获取到证书信息就好办了。证书信息中会包含有两个日期,NotAfter表示过期日期,NotBefore表示激活日期。通过对比当前日期是否介于二者之间,就可以判断证书是否激活或者过期。而且还可以计算距离过期天数,快过期时,提前发送邮件提醒相关人员申请新的证书。

域名是否匹配

域名是否匹配其实也是很好检测的。因为,很多语言标准库里也有提供类似verifyHostname的函数来进行检测。但是对于那些没有提供该函数的情况该如何检测呢?好说,具体检测的逻辑如下:

  1. 如果这里的”域名“是IP,那么检测IP是否属于证书IP SANs(Subject Alternative Names)。

  2. 如果不是IP,那么检测是域名否属于证书的DNSNames。

  3. 如果不属于DNSNames,那么检测域名是否匹配颁发对象的CommonName。

如果上述三点都不匹配,那证书就可以GG了。注意:这里的匹配,对于IP而言就是绝对相等,而对于域名而言,要考虑通配符的情况,通配符匹配判定可以参见此处(请见参考资料4),* 匹配任何字段。

证书签名是否安全

具体到签名算法,我没有完整的列表,目前知道的,MD2-RSA,MD5-RSA,SHA1-RSA 都被认为是不安全的签名,这里重点强调下SHA1-RSA,因为目前很多网站的证书任然采用(截至2015年5月,大约45%的数字证书皆使用此算法,SHA-1的证书将从2017开始不再被主流浏览器厂商视为安全的),说到这里我就很尴尬了,原因就不说了,欢迎到MiSRC提漏洞。

  • RSA:由证书提供。属于非对称加密算法。SSL证书大部分会使用此算法来生成公钥私钥对,以便对SSL/TLS会话中的对称密钥进行加密/解密(SSL加密传输过程中,非对称秘钥用于对“对称秘钥”加密,而信息本身用对称秘钥进行加密)。目前,低于1024bits的RSA密钥已经被认为不安全了。

  • MD2/MD5/SHA1/SHA2:由证书提供。哈希摘要算法,用于验证签名真实性,其安全性 MD2<MD5<SHA1<SHA2。此外还有一个很接近的fingerprint指纹算法(使用sslabs检测时可以看到,一般采用SHA1),这个是客户端浏览器对证书整体做的摘要算法,用于浏览器内部证书管理,跟签名算法无关。

知道了以上这几点,也就方便进行检测了,获取证书的签名算法然后进行对比即可。

证书链的检测

对于证书链检测,主要是两方面,一是证书链中证书个体的检测,另外一个是证书链整体的检测。对于前者,与之前服务器证书检测一样,主要也是检测是否过期、签名是否安全等,外加是否属于自签名证书(即自己给自己签发,也就是SubjectKeyId与AuthorityKeyId相同)。对于证书链的检测,主要集中在以下几点:

  1. 证书链长度检测。既然都称为链了,那长度最起码也得大于2吧。而最长长度也得要有一定的限制,毕竟没有哪个浏览器证书链包含十几份证书,具体上限是多少,我没有找到相关资料,可以自行设定,根据后续检测结果调整。

  2. 证书链顺序检测。这里顺序检测主要是保证下级证书必须是由上级机构签发,即证书链中,下级证书的AuthorityKeyId要与上一级证书的SubjectKeyId相等。

  3. 证书链根证书检测。这里要进行根证书的可信检测,主要是指证书链的根部证书必须包含在已知的可信根证书集中,这是保证后续签发证书可信的必要条件。

证书链长度检测比较简单,编程获取的证书信息中都会包含证书链信息,一般以列表形式存在,只要检测其列表长度即可。

证书顺序的检测需要从服务端证书开始,依次检测该证书的AuthorityKeyId是否与列表中下一证书的SubjectKeyId一致,保证下级证书由上级机构签发。

aKey := certs[0].AuthorityKeyIdfor _, c := range certs[1:] {    // simply check    if bytes.Compare(aKey, c.SubjectKeyId) != 0 {        paths["chainIssue"] = append(issues, "incorrect order")        break    }    aKey = c.AuthorityKeyId}

根证书可信的检测,主要是检测证书链中根部证书是否可信。浏览器和操作系统中一般都会自带大量可信CA根证书,例如:

操作系统证书位置

Debian/Ubuntu/Gentoo

/etc/ssl/certs/ca-certificates.crt

Fedora/RHEL

/etc/pki/tls/certs/ca-bundle.crt

OpenSUSE

/etc/ssl/ca-bundle.pem

OpenELEC

/etc/pki/tls/cacert.pem

除此之外还有类似aosp.pem,apple.pem,microsoft.pem,java.pem,mozilla.pem等作为补充,总之,可以包含你所知道的所有可信根证书集合,越全越好,甚至可以把那些浏览器不信任但我们认为可信的根证书也包含在内,比如SRCA。对于可信CA根证书的检测,只需确定证书链中的根证书是否属于所提供的可信CA根证书集即可,具体到细节实现,就是判断证书链根证书的SubjectKeyId是否包含在上述证书集组成的字典(key为SubjectKeyId,value可以为证书)中,当然其中需要一些细节处理。具体代码逻辑参见此处(请见参考资料5)

其他进一步检测

  1. 证书是否被吊销。通过OSCP进行查询,目前还未实现。

  2. 漏洞、协议、加密套件等方面的检测,这是后话。

0x03 还有谁

以上的检测手段只是在下自己摸索的一些方法,网上找不到类似可供参考的解决方案,谬误不可避免,毕竟我党摸着石头过河也是犯过很多错误的,如果有所帮助,我很欣慰,如果浪费你时间,怪我咯?这其中比较有参考价值的就是Go标准库中x509源码实现以及testssl.sh(请见参考资料6)。如果想要更精确的进行检测,可以考虑通读这几份源码以及相关的RFC,这个需要的时间就比较长了,有时间的朋友可以研究下,然后,完了记得回来指导纠正。

文章中文字描述较多,没有贴太多的代码,具体实现已放到Github(请见参考资料7)上,目前基本可用,缺少一些文档示例,编译运行后你会发现其结果与ssltest(请见参考资料8)检测中的Authentication部分很相似,那肯定啊,我就是照着它的功能来实现的,当然多少会有一些出入,因为目前还在等我们的精神元首明哥开发后台界面,以便进一步跟踪检测结果来修复调整。

好了,该说的说完了,去吧皮卡丘,传送门(请见参考资料9)

—   联系我们   —

新浪微博

公众号