首页
社区
课程
招聘
[原创]okhttp 证书绑定流程 ssl pinning分析
发表于: 4天前 2887

[原创]okhttp 证书绑定流程 ssl pinning分析

4天前
2887

https 相比 http 更加安全的其中一个原因就是增加了证书功能,用于对数据传输双方进行身份的验证和加密传输数据。之前直接使用 charles 或者 fiddler 等中间人抓包的方式,就无法获取到明文数据,或者无法与服务器建立连接。从而导致安全分析被卡在了第一步。虽然 app 使用了 https 方式对抗抓包,但是依然会有解决方案。本篇文章主要就是记录了我学习对抗https 过程中知识点的梳理。

要想反制https抓包,首先就得知道正向是如何开发的。okhttp 使用 https 有几种方式,第一种信任所有证书,第二种只信任系统证书,第三种只信任指定证书。

HostnameVerifier 中不验证主机域名,TrustAllCerts 也不做任何处理。这样就是默认信任所有证书

okhttp 如果不做任何配置,默认就是信任系统的证书

image001

更加专业的说法叫做 ssl pinning ,主要是将服务器的公钥或证书直接嵌入到客户端应用中,确保客户端只与特定的服务器建立安全连接。验证也有几种说法,单项验证和双向验证。双向验证是 app 验证服务端证书,同时服务器也验证 app证书。单项验证分两种情况,第一种客户端校验服务端证书,服务端不校验 app 证书,这是比较常见的。第二种,服务验证 app 证书,app 不校验服务端证书,这种就少见了。代码实现方式,第一种是通过 okhttp自带的 CertificatePinner 进行证书的绑定服务端证书,第二种就是前面验证系统证书时,使用的继承 X509TrustManager 的方式。

这里使用了百度的证书,把证书从网站上保存下来之后,手动生成 hash,再进行 base64编码。通过下面的命令就可以搞定

image002

通过 .certificatePinner(certificatePinner) 把 CertificatePinner 添加的百度证书绑定上去。在 okhttp 源码中,会自动进行证书的判断(后面的源码分析会有讲解),当证书不匹配就抛出异常。注意 CertificatePinner 只能绑定服务器证书进行验证,如果想要把 app 证书传入服务器,需要另外的代码。具体看后面的双向认证。

image003

使用 charles 进行抓包。尽管 charles 的证书已经导入到系统中了。依然无法抓包。通过提示就可以知道,需要让 app 信任 Charles 的证书,才能抓包。具体的对抗方式在之后有介绍。

image004

image005

X509TrustManager 自定义证书绑定。通过构造方法传入服务端的证书,在 checkServerTrusted 进行证书的校验。

使用 X509Certificate 的 equals 来判断两个证书是否相同,不相同直接抛出异常

我们把服务端证书证书放到 app 中的 assert 目录,然后通过代码读取证书设置到 X509Certificate ,在 SSLContext 初始化时,使用 TrustManager 加载 X509Certificate 。当app 发起请求时,系统会自动调用我们写的 TrustServerCerts 类中的 checkServerTrusted 校验证书。

注意这里还有一个主机校验,因为我们自己写了证书校验,那么主机校验,也需要进行我们进行操作。

通过 .hostnameVerifier((hostname, session) -> true) 校验。true 是不验证主机,这里我就偷个懒了。不做这么多操作了。

这里我使用本地服务器做了一个 https的服务器端做测试

image006

双向验证就是多了服务器校验客户端证书,请求的使用需要把 app 证书携带上。注意 app 端的证书需要时 bks 或者 jks 格式的。因为这两个证书中包含了秘钥和证书两个信息。android系统加载证书时需要这两个信息。使用 KeyStore 加载 bks 证书,传入证书秘钥,再使用 KeyManagerFactory 存放证书。关键点在 sslContext.init 初始话的时候,把 KeyManagerFactory 加载的 app 证书作为第一个参数传入,这样在发送请求时,会自动携带此证书。

同样 hostnameVerifier 也要做处理。

image007

此时开启抓包。假设 app 验证服务器的证书,你已经绕过成功了。但是没有把 app 的证书携带给服务器,因为 charles 抓 https 的包,默认是把 charles 自己的证书传给目标服务器。目标服务器开启了 app 证书校验,那一定是不会通过的,就会直接关闭这个链接请求。具体表现形式。

第一种,我自己写的 https 服务器直接返回 403 。

image008

image009

第二种,就是某app返回的 ,远程 ssl 服务器拒接请求。

image010

具体解决方案,请看下文!!

这里还有一个要注意的点。证书在app中的存放方式,可以是保存成文件例如保存成 crt bks jks 等文件直接和app一起打包。也可以把证书变成一个字符串,存放在代码中如下图所示。

image011

最终都是要把证书转成 InputStream 进行加载。

image012

image013

从 okhttp 开始发起请求跟踪,这里我用的 okhttp 版本是 3.10 这个版本代码还是使用 java 写的。先进入 execute

image014

img

没有被实现,点击左边的绿色图标跳转到实现的地方。

image015

关键点就是 getResponseWithInterceptorChain,从这个地方得到了 Response 。

image016

okhttp 的关键就是有很多的拦截器,不同的拦截器做了不同的时候。默认先加载我们自定义的拦截器 client.interceptors()。然后加载了 4 个固定拦截器 。

这里我们的重点是 ConnectInterceptor ,因为这个拦截器在建立连接的时候同时进行了证书校验。

image016

入口在 newStream

image018

findHealthyConnection 这是关键入口 ,里面做了各种检测。

image019

在这个方法的下面一点有建立连接的代码

image020

image021

进入 establishProtocol

image022

再跟进 connectTls 这就是最关键的地方,里面开始与服务器握手,握手之后进行证书的验证。

image022

通过代码里可以看到先进行了“握手”,然后验证主机域名,再验证 CertificatePinner 绑定的服务端证书。问题来了,这里没有看到验证 X509TrustManager 里面加载的服务器证书,是在哪里验证的呢?其实 X509TrustManager 是系统自带的证书验证,CertificatePinner 是 okhttp 的证书绑定认证。所以 X509TrustManager 是先执行的。在 sslSocket.startHandshake() 里面链接完成之后就调用了 X509TrustManager 进行验证。

进入 startHandshake 查看一下

image022

到这里已经进入android源码了,不好继续跟踪。但是我们可以在 X509TrustManager 里面增加一个堆栈输出。看看是调用了哪个类,在源码网站去继续跟进分析。

image025

image026


[招生]系统0day安全班,企业级设备固件漏洞挖掘,Linux平台漏洞挖掘!

最后于 4天前 被绿豆粥编辑 ,原因: 图片失效
收藏
免费 6
支持
分享
最新回复 (1)
雪    币: 4649
活跃值: (6913)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
爽文,加强了连接ssl方面的学习
2天前
0
游客
登录 | 注册 方可回帖
返回
// // 统计代码