0. 背景

在最近项目APP新版本发布后,安全机构扫描发现了一个中风险缺陷,需要我们规定期限内解决,报告中原始风险描述如下所述

Certificate Pinning Bypass: XXX android application’s SSL pinning mechanism can be bypassed. We noted that the XXX App application’s SSL pinning was bypassed by using Frida and Objection tools. Bypassing of certificate pinning permits an attacker to fake certificates on the path to the server and potentially perform man in the middle attacks against the vulnerable application.

Recommendation: Design and implement the client application in a way which ensures valid server certificates are checked. Also check if the physical device is rooted.

根据上述报告文档中披露的风险,可以大致分析出这是一个基于安卓的安全缺陷,主要表现在于安卓设备上通过渗透工具进行攻击实现绕过SSL证书固定安全机制,从而通过现代化代理工具(诸如Charles或Burp Suite)作为中间人实施请求拦截,从中窃取请求信息和响应内容,更加进阶地可以对请求数据做自定义修改,而安全机构对应解决该缺陷的建议, 则是要求我们设计并实现出一套能够确保SSL证书有效认证的方法以确保即便请求再次被拦截,服务端也不应当再对该请求进行响应

1. SSL

所以到这一步,到底什么是SSL Pinning? SSL又是什么东西? Pin又要Pin个啥? 什么是中间人?

SSL的全名是Secure Sockets Layer,但SSL已经是旧的标准,新的标准则是Transport Layer Security (TLS),协议存在的目的都是一样的,为的是保护用户资料的安全性

我们可以假设存在房子A🏡与房子B🏡,同时存在着一条通道用于两方的信息与物品的交换,在没有SSL的情况下,这条通道是透明的,外界可以直接看到双方在交换的东西,然而在有了SSL之后,原本透明的通道则升级为了非透明状态,外界无法直接看到双方的信息或物品的交互

SSL

转换到现实的互联网世界当中,未升级前的AB管道通讯则可以看做是HTTP协议,而HTTP有了SSL则升级为了HTTPS协议,所以,对应上图,HTTP就是透明渠道,HTTPS则是非透明渠道,也就是说HTTPS用了不可窥视的隧道进行通信,通过使用SSL从而使得HTTPS有了加密认证和完整性保护,说到完整性保护, SSL提供了报文摘要功能来进行完整性保护,但其实HTTP也提供了MD5报文摘要功能,不过它不是安全的

摘要算法:可以为一段数据内容生成一个独一无二的指纹,通过验证该指纹就能够确认接收到的数据内容是否完整

我们抽象地了解完SSL后,应该要知道它背后是怎么运作的,这里又涉及到了公私钥概念,SSL其中有一段逻辑是需要通过非对称式加密的公私钥进行认证后才能建立通讯管道,在建立安全的通讯渠道后,则会再利用对称式加密算法对渠道内的信息进行加解密

总结:SSL会话秘钥用的是非对称加密的方式生成传输数据用的是对称加密,所以总体上来看采用的是对称加密和非对称加密的混合加密方式

我们可以简单地理解公私钥中的公钥(Public Key)是可以公开给外界的,负责消息的加密,而私钥(Private Key)则是需要保存好不可泄露,负责消息的解密,而关于对称加密与非对称加密可以简单地通过下图进行理解

encryption

参考下图,整个SSL建立的步骤可以分为以下三个步骤:

  • Authentication: 使用非对称加密算法进行服务器数字证书的验证
  • Key Exchange: 生成并交换生成的对称加密秘钥
  • Encrypted Data Transfer: 客户端和服务端使用上一步生成的对称加密秘钥,进行双方通讯数据的加解密

SSL-Build-Process

在第二个步骤(Key Exchange)中,会产生用于消息交互的对称式秘钥,但并不会直接明文传输该对称式秘钥,而是采用服务器证书中的公钥进行加密后再进行传输

如果期望通过命令获取服务端证书,可通过以下openssl工具命令直接获取查看

1
$ openssl s_client -connect google.com:443

2. SSL Pinning

SSL Pinning也可以称之为Certificate Pinning,我们现在已经知道,公私钥是一组配对好的秘钥,所以同一组公私钥生成出来的SSL证书,其证书包含的公钥不会再变更,SSL Pinning则是将SSL固定起来,其中的固定利用的就是证书公钥的不可变性达到的

假设有一个APP可以访问Google搜索,而同时Google域名对应的SSL证书内的公钥是A,则APP中的代码可以预先固定好这个公钥A,因此当我们真正使用这个APP进行Google搜索时,则会先拉取Google的SSL证书进行认证,而此时证书内的公钥如果与APP内固定好的公钥A不相同时,则会拒绝连线,只有证书内的公钥与APP内预先设定好的公钥A一致,双方才会继续连线

这个过程称之为SSL Pinning,用于确保连线的网址是安全的,而如果出现了公钥比对不一致的状况,一般就是所谓的中间人攻击

3. 中间人攻击

中间人攻击英文是Man-in-the-middle Attack,又称为MITM,在正常连接的状况下,都是属于下图的状况

normal-ssl

而中间人攻击,则是客户端与服务端中间存在着一个人帮我们与服务端进行数据交换,如下图所示,而这代表着我们的数据都会被这个中间人捕获到

image-20230126151827369

所谓这个中间人的角色,便会充当服务端与客户端进行SSL通道的建立,所以对于客户端来讲,中间人则会被当成是真正的服务端,只是客户端不知情,但其实现代PC端浏览器会帮助我们检测类似这种中间人的状况,并给出相应的警告,如下所示

image-20230126152141724

4. 根因

由上我们得知在SSL/TLS协议中,其证书需要被验证是否签发于真正的证书签发机构(CA),对于使用混淆证书实施中间人工具,则可能出现下图中的状况,如果使用的是有效证书,客户端将会检查该证书并将其标记为可信有效证书,一旦证书被认证完成且通讯协议握手完成,SSL/TLS协议就会被客户端认为已经安全建立, 因此攻击者能够获取客户端和服务器之间的所有明文数据

为什么本次安全缺陷的中间攻击者伪造的证书能够被客户端认为是有效的? 其根本原因还是在于项目中的Android应用使用的是Android SDK中默认提供SSL实现方式,现如今市面上的大部分Android应用程序都存在着相同的缺陷,而通过Frida和Objection工具即可实现SSL Pinning绕过,主要是通过修改字节码的方式将Android默认的SSL Pinning机制hook修改返回值 ,使得无论SSL证书是否有效客户端都能成功建立连接,从而使用Burp Suit代理工具能够获取请求中的内容

SSL Pinning example

5. 复现

根据检测报告中披露的渗透工具,基于Android进行SSL Pinning绕过主要使用到了FridaObjectionBrup Suite

  • Frida: 一款基于Python + JavaScript的Hook框架,是跨平台的Hook工具,可以植入代码片段到原生APP的内存空间中(可以想象成是Chrome浏览器的油猴扩展程序),Frida环境的搭建主要分为以下两个部分:

    • 运行在系统上的交互工具frida
    • 运行在Android设备上的注入工具frida-server
  • Objection:  一款基于Frida的移动应用运行时扩展工具,可以帮助进行一系列的安全评估,可同时支持iOS和Android生态的应用程序

  • Brup Suite: 一款高度集成化的渗透测试工具,包含了很多的功能,能够协助我们进行Web应用程序的渗透测试和攻击,但基于当前上下文,我们主要使用它拦截通过代理的所有网络流量,实现中间人攻击

上述工具的安装步骤此处不再赘述,将在文末附上相关的参考链接,本文将基于以下环境进行场景复现:

Tool Version
Frida 16.0.8
Objection 1.11.0
MacOS 13.0
Burp Suite Community Edition 2022.12.6
Android Debug Bridge 1.0.41

在完成上述工具的安装后,我们准备了一台安卓模拟器(基于Android Studio - Device Manager运行),成功将应用安装包APK载入模拟器,导入并运行frida-server注入工具用于后续与Objection的配合,具体命令如下所示:

frida-server在安装于设备上时需要提前查看设备的架构体系以便选择对应的包

frida-server下载:https://github.com/frida/frida/releases

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 确认目标设备运行状态
$ adb devices -l
List of devices attached
emulator-5554 device product:sdk_gphone_arm64 model:sdk_gphone_arm64 device:emulator_arm64 transport_id:1

# 载入APP安装包
$ adb install app.apk
# 载入frida-server
$ adb push frida-server /data/local/tmp/
# 赋予相应操作权限
$ adb shell "chmod 755 /data/local/tmp/frida-server"
# 开启adb的root权限
$ adb root
# 启动frida-server
$ adb shell "/data/local/tmp/frida-server &"

在目标设备完成上述操作后,使用Objection工具直接启动该应用,启动方式有以下两种:

1
2
3
4
5
6
7
# 通过frida-ps工具查看监听进程
$ frida-ps -Ua
PID Name Identifier
---- ----------- ---------------------------------------
1481 Google com.google.android.googlequicksearchbox
2944 Photos com.google.android.apps.photos
5292 賓士暢行 com.daimler.mbpass.android
  • 通过指定PID的方式进行启动
1
$ objection -g 5292 explore
  • 通过APP应用标识符的方式进行启动
1
$ objection -g com.daimler.mbpass.android explore -q

在使用Objection工具进入唤起应用后,使用内置提供的SSL Pinning禁用命令进行绕过,如下所示

1
2
3
4
$ com.daimler.mbpass.android on (google: 11) [usb] # android sslpinning disable
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.verifyChain()
(agent) Found com.android.org.conscrypt.TrustManagerImpl, overriding TrustManagerImpl.checkTrustedRecursive()
(agent) Registering job 725272. Type: android-sslpinning-disable

在成功禁用后,打开Burp Suite工具并于Proxy - Options - Add添加代理监听,如下所示

Burp Suite Proxy

在成功添加代理监听后,回到Objection工具内按照android proxy set <ip_address> <port>格式执行设置代理,如下所示

1
2
3
4
# 设置请求代理
$ com.daimler.mbpass.android on (google: 11) [usb] # android proxy set 192.168.2.47 8090
(agent) Setting properties for a proxy
(agent) Proxy configured to 192.168.2.47 8090

在完成以上代理设置后,设备的请求流量将统一走向Burp Suite工具,并能够通过工具内的Intercept面板查看已拦截的请求(需要确保Intercept开关为ON),亦可直接修改请求参数,如下所示

intercept request

至此我们已经能够成功复现报告文档中提到的SSL Pinning Bypass,而基于目前的现状,已经能够自由地读取APP触发的所有请求与响应,可能会暴露敏感的API信息,如身份令牌等重要参数

6. 解决方案

那么转而看向复现过程中最关键的一句Objection指令android sslpinning disable,可以发现这条命令的背后会创建一个新的TrustManager用于覆盖默认的SSL证书验证字节码(可以理解成是用一个假的B物件把真实的A对象给替换了),此外还会重写okhttp3.CertificatePinner.check()方法让其在证书无效的状况下也不会抛出异常,即所有的SSL证书都会直接认定为是有效的,再者,市面上通用的HTTP请求组件也都已经在该指令的打击范围之内

针对SSL Pinning Bypass,一方面是由于APP安全性较低,可以被轻易地使用现有开源工具入侵实施攻击,另一方面关于APP的HTTP请求,采用的是市面上通用的组件,位于开源工具的攻击范围内,因此针对这个缺陷的可行技术方案有以下:

  • 证书固定: 引入证书固定机制确保在正常环境下运行时不会收到中间人攻击
  • HOOK检测:在APP运行时,对于Frida / Xposed这类HOOK框架工具做针对性的检测
  • ROOT检测:考虑到HOOK操作依赖ROOT权限,因此可以在Android APP运行时,针对APP运行设备做ROOT检测
  • 代码混淆:在打包前对APP源代码进行混淆,一定程序上可以使HOOK难度增高
  • 定制化HTTP请求:由于目前APP请求与SSL证书验证採用的是成熟的开源组件,而非客制化,另一方面EPA Report中所采用的攻击工具也已经针对这类开源组件做了破解,因此通过客制化可以在一定程度上避免SSL Pinning被绕过,但effort较大

另外,考虑到APP未做任何加固操作,极易被破解或盗版,建议可以针对APP包做加壳操作,保护核心的程式码算法,同时防止二次打包(需要专业机构进行加壳,开发者独立完成有难度)

关于FRIDA检测,可以参考以下检测流程:

  • 遍历运行的进程列表从而检查fridaserver是否在运行
  • 遍历/data/local/tmp目录查看有无frida相关文件
  • 遍历映射库,检测frida相关so
  • 检查frida server默认端口:27042
  • 内存中扫描frida特征字符串 “LIBFRIDA”
  • frida使用D-Bus协议通信,为每个开放的端口发送D-Bus的认证消息
  • 检测有无frida特征的线程名,如:gum-js-loop

7. 参考链接