0x00 背景
近日华为云 WAF 团队收到一个 WAF 旗舰版客户反馈的问题,他们的 APP 在部分安卓机上无法正常使用,取消 WAF 后又正常。秉承着“以客户价值为依归”的理论,我们立马对相关问题进行排查。首先客户的站点是 HTTPS 的,然后出问题的终端是部分系统版本比较低的安卓手机,这里可以初步判断是因为这部分终端不支持 SNI 造成的。
SNI 具体的内容在第三节中将会详细介绍,请稍等。
0x01 定位
为了验证我们的推断,我在自己模拟器上面安装了客户的 APP,针对手机浏览器和 APP 分别进行抓包,查看 SNI 的情况。
这里我们模拟器使用的 Genymotion,系统采用的安卓 5.1.0,大概的截图如下:
TIPS:这个模拟器是基于 X86 架构,跑起来非常快,但是我们目标 APP 是 ARM 架构的,还不能直接运行,我们需要安装额外的 ARM-Translate,这个就不在本文中介绍了,后面我会专门写文章来介绍,或者有需要的朋友可以直接联系我。
我们就在宿主机上面用 Wireshark 抓包即可,抓包过程也非常简单,就是分别使用浏览器打开目标网址和用 APP 登录,我直接给出抓包截图,我们对比看一下吧。
首先是浏览器的抓包:
后面这个是 APP 的包
两者区别在于 SSL 握手时候 Client 的扩展字段有没有 SNI 字段。
0x01 SNI 介绍
SNI 是 Server Name Indication 的缩写,是为了解决一个服务器使用多个域名和证书的 SSL/TLS 扩展。它允许客户端在发起 SSL 握手请求时(客户端发出 ClientHello 消息中)提交请求的 HostName 信息,使得服务器能够切换到正确的域并返回相应的证书。
在 SNI 出现之前,HostName 信息只存在于 HTTP 请求中,但 SSL/TLS 层无法获知这一信息。通过将 HostName 的信息加入到 SNI 扩展中,SSL/TLS 允许服务器使用一个 IP 为不同的域名提供不同的证书,从而能够与使用同一个 IP 的多个“虚拟主机”更方便地建立安全连接。
SSL 握手
HTTPS 其实是将 HTTP 的请求使用 TLS 加密后使用 TCP 协议传输给目的方,几者之间的关系如下:
TLS 加密需要在 TCP 连接建立之后,双方进行 SSL 握手,协商随机数和证书。大概的过程是这样的:
这里和我们这次文章比较相关的部分就是客户端发送 Hello 后,服务端返回证书,客户端校验证书有效性。
NGINX 反向代理
在现在互联网时代,IP 地址越来越紧张,因此我们经常会将多个域名或者网站使用同一台服务器,同一个 IP。NGINX 通常就是这样的网关,当一个 HTTP 请求到达时候,NGINX 会通过 HTTP 请求中的 Host 头来决定转发目的服务器。
NGINX 要能够正常的转发,那么它必须能够解析 HTTP 协议,从上面图中,我们可以看到 HTTPS 请求中 HTTP 内容被 TLS 加密,NGINX 在使用前必须进行解密,而解密需要双方协商证书。好的,问题就来了,如果是多个 HTTPS 网站共享一个 IP 和端口,SSL 握手时候,服务端如何正确选择域名证书传输给客户端呢?
为了解决这个问题在 RFC 6066 中对 TLS 的扩展进行了定义,其中就提到了在握手阶段一个 server_name 的扩展,它的内容就是域名的名字。服务端在接收到含有 SNI 的 Client Hello 后,根据其内容,去选择该域名的证书返回给客户端。
因此从上面的解释看出来,这个问题并不是只有 WAF 才会存在,而是绑定了同一个 IP+端口的多个 HTTPS 网站都会遇到这样的问题。
0x02 APP 分析
在上面定位中,我们同一个系统,浏览器携带了 SNI,但是客户的 APP 没有,因此我们决定对客户的 APP 再进行一轮分析。这里需要使用到 JEB 工具对客户的 APK 进行逆向分析。根据 activity 去查找登录方法所使用 HTTP 包即可。我们最后定位到 MobileHttpClientManager 类,实现的代码大致如下:
从代码里面看到,使用的 SDK 默认的 DefaultHttpClient,从相关文章我们知道 HttPClient 默认是不使用 SNI 的。
0x02 解决方案
Android
通常情况下,我们可以使用其他默认支持 SNI 的库,比如 URLConnection,OKHttp 等
HttpsURLConnection
自 Android 2.3 开始,HttpsURLCon-nection 就支持 SNI。如果您需要支持 Android 2.2(及更旧的版本),一种解决办法是在一个唯一端口上设置备用虚拟主机,以便了解要返回哪个服务器证书。
比较极端的替代方法是不使用服务器默认情况下返回的验证程序,而是将 HostnameVerifier 替换为不使用您的虚拟机主机名的验证程序
Apache HttpClient
虽然 HttpClient 的 4.3.2 版本在 Oracle JRE 1.7+已经支持 SNI 了,但是 Android 可不是使用的 Oracle 的 JRE 啦,这个涉及到版权等问题。
我们最好是使用时,手动设置一下 HostName。
从代码也看到了,这个需要安卓 4.2.2 以后的版本才是支持的。
iOS
因为 CFNetwork 是支持 SNI 的,因此我们只需要判断协议然后决定是用上层的网络请求转发还是用底层的 cfnetwork 来转发。
浏览器
目前不管是 PC 还是移动端,主流的现代浏览器都是支持 SNI 的。
本文转载自 华为云产品与解决方案 公众号。
原文链接:https://mp.weixin.qq.com/s/syjhiuaGpQNX-mi-0Uz-Iw
评论