写点什么

记一次 WAF 的 SNI 时间排查

2020 年 4 月 01 日

记一次WAF的SNI时间排查

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


try {    URL url = new URL("https://www.huaweicloud.com");    URLConnection urlConnection = url.openConnection();    HttpsURLConnection connection = (HttpsURLConnection) urlConnection;    connection.setRequestProperty("Host", "www.huaweicloud.com");    connection.setHostnameVerifier(new HostnameVerifier() {        @Override        public boolean verify(String hostname, SSLSession session) {            return HttpsURLConnection.getDefaultHostnameVerifier().verify("www.huaweicloud.com", session);        }    });    connection.connect();} catch (Exception e) {    e.printStackTrace();} finally {}
复制代码


自 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。


// Android specific code to enable SNIif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {    if (Log.isLoggable(TAG, Log.DEBUG)) {        Log.d(TAG, "Enabling SNI for " + target);    }    try {        Method method = sslsock.getClass().getMethod("setHostname", String.class);        method.invoke(sslsock, target);    } catch (Exception ex) {        if (Log.isLoggable(TAG, Log.DEBUG)) {            Log.d(TAG, "SNI configuration failed", ex);        }    }}
复制代码


从代码也看到了,这个需要安卓 4.2.2 以后的版本才是支持的。


  • iOS


因为 CFNetwork 是支持 SNI 的,因此我们只需要判断协议然后决定是用上层的网络请求转发还是用底层的 cfnetwork 来转发。


if ([self.request.URL.scheme isEqualToString:@"https"] ) {        //使用CFnetwork        curRequest = req;        self.task = [[CustomCFNetworkRequestTask alloc] initWithURLRequest:originalRequest swizzleRequest:curRequest delegate:self];        if (self.task) {            [self.task startLoading];        }    } else {        //使用普通网络请求        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];        self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];        NSURLSessionTask *task = [self.session dataTaskWithRequest:req];        [task resume];    }
复制代码


  • 浏览器


目前不管是 PC 还是移动端,主流的现代浏览器都是支持 SNI 的。


本文转载自 华为云产品与解决方案 公众号。


原文链接:https://mp.weixin.qq.com/s/syjhiuaGpQNX-mi-0Uz-Iw


2020 年 4 月 01 日 21:20173

评论

发布
暂无评论
发现更多内容

九环智能合约开发

V19927655815

APP开发

如何基于 SDK 快速开发一款IoT App 控制智能灯(iOS 版)

IoT云工坊

ios App 物联网 IoT sdk

盘点 2020 | 10 天开发前台系统技术系列

老魚

CSS 前端 全栈 js 盘点2020

APICloud AVM 多端开发 |外卖 app 开发案例源码教程(上)

APICloud

Vue Web Worker 前端框架 移动终端 前端训练

星域母子币系统软件开发|星域母子币APP开发

开發I852946OIIO

系统开发

应急指挥中心平台搭建,移动可视化指挥解决方案

t13823115967

可视化数据分析搭建 应急指挥

华为18级工程师总结的50W字算法、LeetCode、操作系统、计算机底层刷题必备笔记

Java成神之路

Java 程序员 架构 面试 编程语言

阿里P8亲授MySQL学习教程笔记,一个月吃不透那我真的白活了!

Java成神之路

Java 程序员 架构 面试 编程语言

Github、知乎下载超过 28762W 次的 Java面试题库(附答案)

Java成神之路

Java 程序员 架构 面试 编程语言

字节二面跪拜“Redis源码”后,面试官直接推荐这份笔记!真是NB

比伯

Java 编程 架构 面试 程序人生

重点人员管控系统开发,情报研判系统开发

13530558032

加密猫MIMI系统APP开发|加密猫MIMI软件开发

开發I852946OIIO

系统开发

移动生态盘点与HMS生态解析

华章IT

华为 Android Studio 移动开发 HMS

如何通过 Serverless 轻松识别验证码?

Serverless Devs

人工智能 Serverless 云原生

智慧社区综合管理平台搭建,智慧平安城市建设

13530558032

周立齐出任电动车联合创始人:网红经济背后的病态消费心理

石头IT视角

直播中不可缺少的一环-rtmp直播推流

anyRTC开发者

音视频 WebRTC CDN RTC RTMP

一线大厂开源三份JDK+Spring+Mybatis源码笔记

Java架构追梦

Java spring 源码 jdk mybatis

Java:利用BigDecimal类巧妙处理Double类型精度丢失

程序员小毕

Java 架构 编程语言 阿里 开发

阿里P9都赞不绝口的面试宝典!半月看完25大专题,居然斩获阿里P7offer

Java成神之路

Java 程序员 架构 面试 编程语言

微服务架构思考 - 理清楚,管起来

jorden wang

云视频技术领军人赵加雨:如何提升在线教育课堂互动体验

拍乐云Pano

音视频 在线教育 RTC 互动课堂 白板

抢先体验全新升级版Eternal Wallet!

Geek_c610c0

数字货币 数字货币钱包开发

为什么说rollup比webpack更适合打包库

fengxianqi

前端 Rollup webpack

高空立体云防控系统搭建,智能化平安小区建设方案

t13823115967

平安小区 智慧平安社区建设

限时!字节Java程序性能优化宝典开源,原来这才叫性能优化

996小迁

程序员 面试 性能优化 笔记

为什么香港云服务器更适合放新网站

德胜网络-阳

没能进入大数据领域

escray

面经 面试经历 101次面试

扒开 SqlSession 的外衣

田维常

mybatis

区块链溯源平台优势,区块链溯源系统解决方案

13530558032

本科毕业,六年Java开发经验,阿里技术三面+HR面,拿下38*16薪资P7offer

Java成神之路

Java 程序员 架构 面试 编程语言

微服务架构下如何保证事务的一致性

微服务架构下如何保证事务的一致性

记一次WAF的SNI时间排查-InfoQ