Tethr:安卓网络共享服务开通状态检查绕过漏洞(CVE-2017-0554)

在绝大多数未Root且使用原厂(StockROM的安卓手机上,当我们启用网络共享(Tethering,官方介绍请参见:https://support.google.com/nexus/answer/2812516?hl=en)功能前,首先需要与无线网络提供商进行服务开通状态检查(Provisioning Check),从而确保我们的数据流量计划是允许共享的。本文主要讲解了Tethr,这是一种在7.1.2版本前能够绕过安卓设备服务开通状态检查的方法。

在发现这一问题后,我向安卓开发团队报告了这一漏洞,并获得了CVE-2017-0554编号,目前在7.1.2版本之后已经对此问题进行了修复。关于该漏洞的具体细节,请参见:https://source.android.com/security/bulletin/2017-04-01#eop-in-telephony

背景介绍

安卓系统中,网络共享的这一功能是由设备的build.prop文件进行控制的,该文件通常位于/system/build.prop。在默认情况下,启动网络共享之前需要进行服务开通状态检查,但我们可以通过添加如下行来绕过这一项检查:

net.tethering.noprovisioning=true

没有Root过的设备并没有权限去编辑/system/build.prop文件,因此,ROM制造商通常会自行设置这个属性。举例来说,Google Nexus 6P就默认设置net.tethering.noprovisioningtrue,绕过了该项检查。然而,这只是一个例外,并不普遍。Google Nexus 5XPixelPixel 2全部都会执行服务开通状态检查。

 

漏洞概述

当我们在安卓系统上启用网络共享时,操作系统将首先与运营商通信,进行服务开通状态检查,以确定用户的套餐计划是否允许共享。如果允许,则立即启用共享,否则会向用户显示相应的错误提示。

在没有插入SIM卡的情况下,不会执行该项检查,会直接允许共享。此外,如果在没有插入SIM卡的手机上先启用网络共享功能,然后再插入SIM卡,则共享会立即被关闭,在这一点上确保了验证的可靠性。

然而,如果我们在无线通信(Radio)连接的过程中启用网络共享,则不会进行服务开通状态检查,并且在无线通信连接建立之后,仍然会保持网络共享启用的状态。

由此,我从中发现了两个问题。第一个问题是:在原厂安卓系统上,用户自行安装的应用程序,具有重置蜂窝调制解调器的权限。第二个问题是,一旦蜂窝调制解调器完成重连接过程,将不会再进行服务开通状态检查。

上述这两个BUG,绕过了“net.tethering.noprovisioning”的服务开通状态项。不论做任何服务开通状态,都能允许安卓系统在“net.tethering.noprovisioning=true”的状态下运行,也就是能允许安卓系统绕过服务开通状态检查。

 

Tethr演示

在我们深入研究细节之前,请大家观看我们的演示视频。在视频中,我们展示了在安卓系统的用户界面中何时应该启用网络共享功能、如何执行的服务开通状态检查以及不允许网络共享的情况。然后,当我们尝试运行Tethr演示应用程序时,调制解调器会进行重置,此时手机暂时失去信号,随后网络共享成功启用。

演示视频:

该项目的源代码请参见:https://github.com/lanrat/tethr

编译后的apk文件请参见:https://github.com/lanrat/tethr/raw/master/build/Tethr.apk

漏洞1: 通过Java反射重置无线通信

在安卓系统中,可以通过Java反射(Java Reflection)来让应用程序调用文档中未记录(Undocumented)或隐藏的API。然而,这一调用并不是官方支持的,并且官方强烈不建议(参考:https://plus.google.com/+RetoMeier/posts/Cz5wQbdaNQB)这样操作。然而,它最终还是可以允许应用程序开发人员执行不受支持的任务,或者在这种情况下绕过权限检查。

CellRefresh.java中,通过调用CellRefresh.Refresh()可以执行蜂窝调制解调器的重置。在大多数安卓版本上,CellRefresh都会重置蜂窝网络连接,但在安卓6及以上版本,使用的是如下反射:

getSystemService(Context.TELEPHONY_SERVICE).getITelephony().setCellInfoListRate();

getSystemService(Context.CONNECTIVITY_SERVICE).mService.setMobileDataEnabled();

较旧的安卓版本,则使用的是如下反射:

getSystemService(Context.CONNECTIVITY_SERVICE).mService.setRadio();

getSystemService(Context.TELEPHONY_SERVICE).getITelephony().disableDataConnectivity();

getSystemService(Context.TELEPHONY_SERVICE).getITelephony().enableDataConnectivity();

这一漏洞的修复方案是:使用这一方法之前,应该检查应用程序是否具有系统权限或特权。

漏洞2: 网络共享服务开通状态检查竞争条件漏洞

为了利用竞争条件(Race Condition)漏洞并绕过网络共享服务开通状态检查,在特定的时间,可以使用安卓中的PhoneStateListenerAccessibilityService来启用网络共享模式。

首先,如上所述,先对网络进行重置。在重置过程中,手机状态监听PhoneStateListenerTetherPhoneStateListener.javahttps://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherPhoneStateListener.java)将会监听蜂窝网络何时断开,随后使用无障碍辅助功能AccessibilityServiceTetherAccessibilityService.javahttps://github.com/lanrat/tethr/blob/master/src/main/java/com/vorsk/tethr/TetherAccessibilityService.java)找到相应的UI开关,启动系统的网络共享功能。

针对这一漏洞利用方式,我们实际上不一定要使用AccessibilityServicePhoneStateListener。用户可以在恰当的时间手动开启网络共享功能,也可以实现相同的结果。但由于可以开启网络共享的这一时间非常短暂,如果借助AccessibilityService来自动完成,会更为简单。

针对这一漏洞,我们建议:除了在启用网络共享时要进行服务开通状态检查之外,还应该在每次无线通信重置后进行一次服务开通状态检查。

 

测试过程

通信运营商:1. Verizon;2. AT&T

测试手机(使用原厂ROM、锁定BootloaderOEM系统):

1. Nexus 5X(系统:Android 6.0.1);

2. Nexus 5X(系统:Android 7.0.0);

3. Nexus 5X(系统: Android 7.1.1);

4. Samsung Galaxy S7(系统:Android 6.0.1)。

未进行测试,但也同样可利用该漏洞的手机有:

1. Pixel (XL)

2. 其他非Nexus品牌但同样执行服务开通状态检查的设备。

特别要提出的是,由于Nexus 6P在其原厂build.prop中,已经将net.tethering.noprovisioning设定为True,因此不需要利用这个漏洞,直接就会绕过服务开通状态检查。

 

修复方案

在我将该漏洞情况提交给安卓开发团队后,Google对这一问题进行了修复,并针对安卓7.1.2版本发布了两个补丁。

其中,第一个补丁增加了对于setCellInfoListRate的权限检查,第二个补丁修复了网络共享服务开通状态检查的逻辑缺陷,并增加了重新校验的过程。

在修复完成后,Google还赠送给我一部Pixel XL手机,用于奖励发现并报告了上述漏洞。

(完)