移动应用程序强烈地忽略所有SSL错误并允许您随意拦截和修改它们的流量的日子已经一去不复返了。相反,大多数现代应用程序至少会检查证书是否向有效的可信证书颁发机构(CA)提供链。作为pentesters,我们希望说服应用程序,我们的证书是有效和可信的,所以我们可以中间人(MITM)它,并修改它的流量。在本博客中,我将介绍4种可用于绕过Android上SSL证书检查的技术:
这些范围从相当简单到相当先进 - 这个博客将尽力涵盖每个博客,而不会陷入特定情况的细节。
为什么我们需要特别注意移动应用程序的 SSL MITM条件?为了查看和模糊移动应用程序的Web服务调用,我们需要使用拦截代理(如BurpSuite或ZAP)。使用代理拦截SSL流量时,来自客户端的SSL连接会在代理处终止 - 无论代理发送用于标识自身的任何证书都由移动应用程序评估,就好像该代理是Web服务端点一样。默认情况下,Burp等工具生成的自签名证书不具有有效的信任链,并且如果证书无法验证为可信,则大多数移动应用程序将终止连接,而不是通过可能不安全的渠道进行连接。下面的技术都有一个共同的目标,即说服移动应用程序信任我们的拦截代理提供的证书。
避免SSL错误的最简单方法是拥有一个有效的可信证书。如果您可以将新的受信任的CA安装到设备上,则这相对容易 - 如果操作系统信任您的CA,则它会信任您的CA签署的证书。
Android有两个内置证书存储区,用于跟踪操作系统(系统存储区(持有预先安装的CA)和用户存储区(持有用户安装的CA)所信任的CA)。来自developer.android.com:
默认情况下,来自所有应用的安全连接(使用TLS和HTTPS等协议)信任预安装的系统CA,而针对Android 6.0(API级别23)或更低级别的应用默认情况下信任用户添加的CA存储。应用程序可以使用base-config(针对应用程序范围的自定义)或domain-config(针对每个域的自定义)自定义自己的连接。
这对我们意味着什么?如果我们试图以MITM为目标的Android 6.0或更低版本的应用程序,我们可以简单地将我们的CA添加到用户添加的CA商店。当应用程序验证我们自定义证书的信任链时,它会在信任库中找到我们的自定义CA,并且我们的证书将被信任。但是,如果应用程序的目标版本是6.0以上的Android版本,它将不会信任用户添加的CA商店。为了解决这个问题,我们可以编辑应用程序的清单并强制其将Android 6.0作为目标。目标API级别在AndroidManifest.xml文件的'manifest'元素的'platformBuildVersionCode'属性中指定。
1 |
<span class="hljs-tag"><<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span> <span class="hljs-attr">package</span>=<span class="hljs-string">"com.test.app"</span> </span><strong><span class="hljs-tag"><span class="hljs-attr">platformBuildVersionCode</span>=<span class="hljs-string">"25"</span></span></strong><span class="hljs-tag"> <span class="hljs-attr">platformBuildVersionName</span>=<span class="hljs-string">"7.1.1"</span>></span> |
上面的manifest元素的目标是'platformBuildVersionCode = 25',我们需要将其更改为23。
1 |
<span class="hljs-tag"><<span class="hljs-name">manifest</span> <span class="hljs-attr">xmlns:android</span>=<span class="hljs-string">"http://schemas.android.com/apk/res/android"</span> <span class="hljs-attr">package</span>=<span class="hljs-string">"com.test.app"</span> </span><strong><span class="hljs-tag"><span class="hljs-attr">platformBuildVersionCode</span>=<span class="hljs-string">"23"</span></span></strong><span class="hljs-tag"> <span class="hljs-attr">platformBuildVersionName</span>=<span class="hljs-string">"</span></span><strong><span class="hljs-tag"><span class="hljs-string">6.0</span></span></strong><span class="hljs-tag"><span class="hljs-string">"</span>></span> |
当应用程序使用此更新的清单重新打包时,它将信任用户添加的CA存储。
或者,如果需要在特定平台上运行版本,我们可以在APK的'/res/xml/network_security_config.xml'配置文件中定义特定的信任锚点。例如,以下文件定义了需要存储在/ res / raw / my_ca(来自https://developer.android.com/training/articles/security-config.html)的新的可信CA:
1 2 3 4 5 6 7 8 |
<span class="php"><span class="hljs-meta"><?</span>xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"utf-8"</span><span class="hljs-meta">?></span></span> <span class="hljs-tag"><<span class="hljs-name">network-security-config</span>></span> <span class="hljs-tag"><<span class="hljs-name">base-config</span>></span> <span class="hljs-tag"><<span class="hljs-name">trust-anchors</span>></span> <span class="hljs-tag"><<span class="hljs-name">certificates</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"@raw/my_ca"</span>/></span> <span class="hljs-tag"></<span class="hljs-name">trust-anchors</span>></span> <span class="hljs-tag"></<span class="hljs-name">base-config</span>></span> <span class="hljs-tag"></<span class="hljs-name">network-security-config</span>></span> |
如果应用程序仅验证所提供的证书是有效的,则该技术应该允许您建立成功的MITM条件。
如果您成功将证书安装到用户添加的CA商店,该应用程序的目标是Android 6.0,并且当您尝试浏览其他受SSL保护的资源时,证书显示为有效,但该应用程序仍然死于SSL错误?开发人员有可能采取了额外的步骤来限制应用程序所信任的一组CA。回想一下技术1,我们定义了一个自定义的信任锚,并提供了一个CA证书的路径 - 这是开发者可能用来保护他们的应用程序免受SSL拦截的功能。
如果自定义证书链与应用程序一起分发,则提取APK并用我们的自定义CA覆盖提供的CA应该足以使我们的拦截证书可信。请注意,在某些情况下,可能会发生对信任链的附加验证,因此此方法可能会产生混合结果。
使用诸如APK Studio之类的工具打开APK可以使与已部署的应用程序捆绑在一起的证书显而易见。在上图中,证书位于“资产”目录下。用我们的自定义CA覆盖适当命名的'UniversalRootCA'证书应该允许我们欺骗应用程序接受我们的证书。
如果安装自己的CA不足以成功代理SSL通信,则应用程序可能正在执行某种SSL固定或额外的SSL验证。通常,为了绕过这种类型的验证,我们需要挂钩应用程序的代码并干扰验证过程本身。这种类型的干扰仅限于根植/越狱手机,但在Frida Gadget的帮助下,现在可以测试Android应用程序,并且可以访问完整的Frida功能,而无需设备生根。
如果您以前执行过移动应用程序渗透测试,那么您可能熟悉Frida框架。完全覆盖Frida的功能不在本博客的范围之内,但是在较高层面上,它是一个允许您在运行时篡改应用程序代码的框架。通常情况下,Frida将作为独立程序在操作系统上运行 - 但这需要根植设备。为了避免这种情况,我们可以将Frida Gadget注入到目标APK中。Frida Gadget包含了Frida的大部分功能,但封装在一个动态库中,该库在运行时被目标应用程序加载,允许您测试和修改目标应用程序的代码。
要加载Frida Gadget,我们需要提取APK,插入动态库,编辑一些smali代码,以便我们的动态库是在应用程序启动时被调用的第一件事,然后重新打包APK并安装它。整个过程已经在John Kozyrakis的文档中详细记录,并且至少需要手动进行一次,以便了解所有内容如何协同工作。但为了节省时间,我们还可以使用另一种工具 - 异议。 异议自动完成整个过程,并且只需要在命令行上提供目标APK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
C:\ >objection patchapk -s test_app.apk No architecture specified. Determining it <span class="hljs-keyword">using</span> `adb`... Detected target device architecture <span class="hljs-keyword">as</span>: armeabi-v7a Github FridaGadget <span class="hljs-keyword">is</span> v10<span class="hljs-number">.6</span><span class="hljs-number">.28</span>, local <span class="hljs-keyword">is</span> v10<span class="hljs-number">.6</span><span class="hljs-number">.13</span>. Updating... Downloading armeabi-v7a library to C:\.objection\android\armeabi-v7a\libfrida-gadget.so.xz... Unpacking C:\.objection\android\armeabi-v7a\libfrida-gadget.so.xz... Cleaning up downloaded archives... Using Gadget version: <span class="hljs-number">10.6</span><span class="hljs-number">.28</span> Unpacking test_app.apk App already has android.permission.INTERNET Reading smali <span class="hljs-keyword">from</span>: C:\Temp\tmp8dxqks1u.apktemp\smali\com/test/app/TestMainActivity.smali Injecting loadLibrary call at line: <span class="hljs-number">10</span> Writing patched smali back to: C:\Temp\tmp8dxqks1u.apktemp\smali\com/test/app/TestMainActivity.smali Creating library path: C:\Temp\tmp8dxqks1u.apktemp\lib\armeabi-v7a Copying Frida gadget to libs path... Rebuilding the APK with the frida-gadget loaded... Built <span class="hljs-keyword">new</span> APK with injected loadLibrary and frida-gadget Signing <span class="hljs-keyword">new</span> APK. jar signed. Signed the <span class="hljs-keyword">new</span> APK Performing zipalign Zipaling completed Copying final apk <span class="hljs-keyword">from</span> C:\Users\cwass\AppData\Local\Temp\tmp8dxqks1u.apktemp.aligned.objection.apk to current directory... Cleaning up temp files... |
在此之后,我们的工作目录中应该有一个名为'test_app.objection.apk'的文件 - 异议默认情况下会将'.objection'附加到原始APK的名称。我们可以像安装任何其他APK一样安装此APK - adb install test_app.objection.apk
应将其推送到我们的连接设备。在目标设备上安装了异议更改APK后,运行该应用程序应导致在应用程序启动屏幕上暂停。此时,我们可以连接到应该在设备上侦听的Frida服务器。如果您更喜欢使用Frida实用程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
<span class="hljs-symbol">C:</span>\>frida-ps -U PID Name ---- ------ <span class="hljs-number">6383</span> Gadget <span class="hljs-symbol">C:</span>\>frida -U gadget ___<span class="hljs-number">_</span> / <span class="hljs-number">_</span> <span class="hljs-params">| Frida 10.3.14 - A world-<span class="hljs-keyword">class</span> dynamic instrumentation framework |</span> (<span class="hljs-number">_</span><span class="hljs-params">| |</span> > <span class="hljs-number">_</span> <span class="hljs-params">| Commands: /_/ |</span><span class="hljs-number">_</span><span class="hljs-params">| help -> Displays the help system . . . . object? -> Display information about 'object' . . . . exit/quit -> Exit . . . . . . . . More info at http://www.frida.re/docs/home/ [Motorola Moto G (5) Plus::gadget]-> Java.available <span class="hljs-literal">true</span> Alternatively, Objection supports interaction with the listening Frida server by using the ‘explore’ command: C:\>objection explore ___|</span> <span class="hljs-params">|_ |</span><span class="hljs-number">_</span><span class="hljs-params">|___ ___|</span> <span class="hljs-params">|_|</span><span class="hljs-number">_</span><span class="hljs-params">|___ ___ |</span> . <span class="hljs-params">| . |</span> <span class="hljs-params">| |</span> -<span class="hljs-number">_</span><span class="hljs-params">| _|</span> <span class="hljs-number">_</span><span class="hljs-params">| |</span> . <span class="hljs-params">| |</span> <span class="hljs-params">|___|</span>__<span class="hljs-number">_</span><span class="hljs-params">|_|</span> <span class="hljs-params">|___|</span>__<span class="hljs-number">_</span><span class="hljs-params">|_|</span> <span class="hljs-params">|_|</span>__<span class="hljs-number">_</span><span class="hljs-params">|_|</span><span class="hljs-number">_</span><span class="hljs-params">| |</span>__<span class="hljs-number">_</span><span class="hljs-params">|(object)inject(ion) v1.2.2 Runtime Mobile Exploration by: @leonjza from @sensepost [tab] <span class="hljs-keyword">for</span> command suggestions com.test.app on (motorola: 7.0) [usb] # android hooking search classes TrustManager android.security.net.config.RootTrustManager android.app.trust.ITrustManager$Stub$Proxy android.app.trust.ITrustManager android.security.net.config.NetworkSecurityTrustManager android.security.net.config.RootTrustManagerFactorySpi android.app.trust.TrustManager android.app.trust.ITrustManager$Stub com.android.org.conscrypt.TrustManagerImpl com.android.org.conscrypt.TrustManagerImpl$ExtendedKeyUsagePKIXCertPathChecker com.android.org.conscrypt.TrustManagerImpl$TrustAnchorComparator com.android.org.conscrypt.TrustManagerFactoryImpl javax.net.ssl.TrustManagerFactory$1 javax.net.ssl.TrustManager javax.net.ssl.TrustManagerFactory javax.net.ssl.X509TrustManager javax.net.ssl.TrustManagerFactorySpi javax.net.ssl.X509ExtendedTrustManager [Ljavax.net.ssl.TrustManager;</span> |
此时,您应该能够从内置的SSL固定旁路功能中受益:
1 2 3 4 |
<span class="hljs-selector-tag">com</span><span class="hljs-selector-class">.test</span><span class="hljs-selector-class">.app</span> <span class="hljs-selector-tag">on</span> (<span class="hljs-selector-tag">motorola</span>: 7<span class="hljs-selector-class">.0</span>) <span class="hljs-selector-attr">[usb]</span> # <span class="hljs-selector-tag">android</span> <span class="hljs-selector-tag">sslpinning</span> <span class="hljs-selector-tag">disable</span> <span class="hljs-selector-tag">Job</span>: 2<span class="hljs-selector-tag">f633f86-f252-4a57-958e-6b46ac8d69d1</span> <span class="hljs-selector-tag">-</span> <span class="hljs-selector-tag">Starting</span> <span class="hljs-selector-attr">[6b46ac8d69d1]</span> <span class="hljs-selector-attr">[android-ssl-pinning-bypass]</span> <span class="hljs-selector-tag">Custom</span>, <span class="hljs-selector-tag">Empty</span> <span class="hljs-selector-tag">TrustManager</span> <span class="hljs-selector-tag">ready</span> <span class="hljs-selector-tag">Job</span>: 2<span class="hljs-selector-tag">f633f86-f252-4a57-958e-6b46ac8d69d1</span> – <span class="hljs-selector-tag">Started</span> |
最后,开发人员可能会选择提供自己的SSL库,而不是依赖系统库来处理SSL证书验证。如果是这种情况,我们可能想要提取APK并将smali转换回Java,以便我们可以查找负责处理证书验证的代码。
使用'dex2jar',语法如下:
1 2 |
C:\>d2j-dex2jar.bat "C:\test_app.apk" dex2jar C:\test_app.apk -> .\test_app-dex2jar.jar |
生成的.jar文件应该可以在您最喜欢的Java反转工具(如JD-GUI)中打开。
一旦确定了负责证书验证的代码,您就可以选择完全打补丁或使用Frida钩住所需的功能。为了避免重新构建整个应用程序,钩住负责证书验证的功能通常更高效。使用技术#3中的步骤将允许您对应用程序进行测试 - 从那里,您应该能够使用Frida命令行工具或Objection接口(无论哪个更适合您)来挂接函数。
上面提到的技术应该允许您拦截Android SSL流量并绕过开发人员采用的一些更常见的防御措施。此外,这个博客还提供了对异议和Frida的简要介绍 - 绕过SSL固定和其他防御措施的能力只是抓住了这些工具提供的功能惊人的表面。我希望这个博客能够介绍各种可用于Android 移动应用程序安全测试的技术,并说明有多种方法绕过给定的安全控制的重要性。
参考:https://blog.netspi.com/four-ways-bypass-android-ssl-verification-certificate-pinning/
本文由安全周翻译。转载请注明出处
您必须[登录] 才能发表留言!