CVE-2018-9445 —— Android挂载外设的目录穿越

简介

Android的USB有host和Accessory两种模式,处于Host模式下的Android设备可以对外围设备进行交互,如读取U盘数据、USB键盘鼠标等。当有外设插入到Android设备上时,系统读取外设的一些硬件信息(如LABEL、UUID等)并自动为其挂载。

常见的USB设备有以下几种:

  • USB Sticks
    即U盘,用于手机和U盘之间拷贝数据,即使处于锁屏状态,系统也会自动mount。(Android 9之后,系统阻止了锁屏情况下的mount)
  • USB keyboards
    USB键盘或鼠标,可以用来操控手机,在锁屏下也会自动mount。
  • USB ethernet adapters
    USB网卡,插入手机后,通过DHCP可以获取ip地址联通网络,锁屏下也可以使用

Android系统中由Vold进程(Volume Daemon)负责管理和控制外设,Vold进程如果在挂载外设时没有正确的对硬件信息进行解析,则有可能出现安全问题。CVE-2018-9445就是Vold在挂载外设时,由于未正确获取UUID导致的目录穿越问题。

漏洞细节

当USB设备插到Android手机上时,Vold进程(Volume Daemon)会自动为USB设备进行mount,即使手机处于锁屏状态。Vold进程在mount之前需要知道所连接设备的一些硬件信息,如UUID、type、Label等,其具体实现在system/vold/Utils.cpp的readMetadata()函数中:

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
/*
https://android.googlesource.com/platform/system/vold/+/master/Utils.cpp#212
*/

std::vector<std::string> cmd;
cmd.push_back(kBlkidPath);
cmd.push_back("-c");
cmd.push_back("/dev/null");
cmd.push_back("-s");
cmd.push_back("TYPE");
cmd.push_back("-s");
cmd.push_back("UUID");
cmd.push_back("-s");
cmd.push_back("LABEL");
cmd.push_back(path);

std::vector<std::string> output;
status_t res = ForkExecvp(cmd, output, untrusted ? sBlkidUntrustedContext : sBlkidContext);
if (res != OK) {
LOG(WARNING) << "blkid failed to identify " << path;
return res;
}

char value[128];
for (const auto& line : output) {
// Extract values from blkid output, if defined
const char* cline = line.c_str();
const char* start = strstr(cline, "TYPE=");
//读取127个字符,遇到引号停止
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsType = value;
}

start = strstr(cline, "UUID=");
if (start != nullptr && sscanf(start + 5, "\"%127[^\"]\"", value) == 1) {
fsUuid = value;
}

start = strstr(cline, "LABEL=");
if (start != nullptr && sscanf(start + 6, "\"%127[^\"]\"", value) == 1) {
fsLabel = value;
}
}

该函数通过调用了blkid命令,从结果中依次按行读取字符串分别对TYPE、UUID和LABEL进行解析。
在Android中运行blkid命令(需要root权限)结果如下图:

upload successful

默认情况下,Android系统将USB设备mount在/mnt/media_rw/目录下,且以UUID为文件名,例如:

upload successful

通常情况下UUID字段不会包含特殊字符,因为它是blkid经过格式化输出的二进制字符串,TYPE字段是固定的几种文件系统格式类型,如fat、ext、ntfs等,而LABEL字段可以自定义,该字段即外设的名称,允许用户自定义。

需要注意的是Android上运行blkid后会首先输出LABEL,通过构造特殊的LABEL,我们可以实现注入攻击:

根据readMetadata()函数的代码,UUID由运行”blkid -c /dev/null -s TYPE -s UUID -s LABEL”后的结果解析得出,因此,如果设备的LABEL的值为“aaa UUID=”../../sdcard””,运行上述命令后,结果为:

1
LABEL="aaa UUID="../../sdcard"" UUID="4823-0029" TYPE="vfat"

此时被sscanf解析后的UUID值变成了“../../sdcard”,被系统mount后就会创建/mnt/media_rw/../../sdcard目录,导致了目录穿越。

漏洞利用

直接在操作系统中更改USB外设的LABEL值相当于修改文件名,在操作系统中修改文件名为包含“../”的字符串是不可行的,我们需要通过文件系统进行修改。

由于romfs文件格式比较简单(如下图),我们直接使用echo就可以对其LABEL值进行修改,

upload successful
修改LABEL(上图中的volume name字段)值为TYPE=”vfat” UUID=”../../data”

1
echo '-rom1fs-########TYPE="vfat" UUID="../../data"' > /dev/block/sda1

更改后,运行blkid命令:

upload successful

插入修改后的设备后,发现并没有mount成功。查看logcat,发现UUID确实会被识别为“../../data”,但fsck_msdos出现了错误

upload successful

这是因为虽然我们提供的type为vfat,但内核在读取文件头部时,获取的文件格式为romfs。而fsck_msdos是用来处理vfat格式,因此出现了错误。

为了不产生fsck_msdos错误,我们创建一个文件格式为vfat,LABEL为自定义的真实USB设备:

  1. 修改USB设备文件格式为vfat,设置label为AAAAAAAAAAA(经测试vfat的label最多为11个字符,超过11个将会自动截断)

    1
    mkfs.vfat -n 'AAAAAAAAAAA' /dev/block/sda1
  2. 通过dd命令查找AAAAAAAAAAA字符串并替换为’UUID=”../aa’(替换后的字符长度必须和之前相同,UUID=”../aa为11个字符)

    1
    dd if=/dev/block/sda1 bs=1M count=200 | sed 's|AAAAAAAAAAA|UUID="../aa|g' | dd of=/dev/block/sda1 bs=1M
  3. 插入修改后的usb设备,我们可以看到/mnt/目录下多了aa文件

upload successful

运行mount|grep aa进一步验证:
upload successful

上述过程完成了漏洞的验证,但局限在于vfat的LABEL最多只有11个字符,由于UUID=”本身占用了6个字符,只剩下5个字符可以控制。这意味着我们只能向上穿越一层,且mount后的目录只有2个字符。考虑到romfs格式的LABEL允许更大的长度(romfs格式中的LABEL字段遇到\0才会结束),因此,为了实现更大的攻击,需要在USB协议上进行攻击:

即实现一个USB硬件,使系统读取blkfs时外设获取的为romfs格式,mount操作时外设告知系统自己为vfat格式。(vfat为了保证外设可以被mount为外部存储设备,romfs为了保证LABEL有足够长进而可以穿越到其他路径

漏洞原作者使用树莓派开启USB模式作为一个USB硬件,实现了从Pixel 2设备中窃取照片的PoC(https://github.com/offensive-security/exploit-database-bin-sploits/raw/master/bin-sploits/45192.zip),这个PoC假设了一种场景,即存在一个app在收到外设mount事件后会自动把本机照片写入外设。为实现该场景,需要修改AOSP源码编译到Pixel设备,新增了部分代码实现一个MountReceiver,在收到mount事件后自动将DCIM目录下的照片复制到data/exfiltrate-photo目录。利用漏洞构造一个USB外设使外设被mount到/data目录,进而实现照片文件的窃取。

漏洞修复

CVE-2018-9445这个漏洞出现的根本原因有2个:

  1. Vold进程在获取TYPE、UUID和LABEL时,使用不严格的字符串匹配方式来读取内容;
  2. Android系统中的blkid并未按照期待的顺序(TYPE、UUID和LABEL)输出,而是最先输出可自定义的LABEL值;

结合了上述两个问题,最终导致了mount时的目录穿越。

漏洞修复时也是针对这2点分别进行了修复:

  1. 在blkid的输出内容里过滤掉了引号
    upload successful

  2. 使用strstr是增加了对引号的匹配
    upload successful

总结

CVE-2018-9445是一个典型的字符串注入攻击,一般的注入漏洞常常发生在exec的参数中,即命令注入,这个漏洞虽然也和exec有关,但是由exec的结果导致下游解析出现了目录穿越。关注解析字符串的代码和exec相关的代码,可能还会有其他的问题。

参考:

  1. https://bugs.chromium.org/p/project-zero/issues/detail?id=1583
  2. https://www.exploit-db.com/exploits/45192/