有隙可乘 - Android 序列化漏洞分析实战
作者:vivo 互联网大前端团队 - Ma Lian
本文主要描述了FileProvider,startAnyWhere实现,Parcel不对称漏洞以及这三者结合产生的漏洞利用实战,另外阐述了漏洞利用的影响和修复预防措施,这个漏洞波及了几乎所有的Android手机,希望能带给读者提供一些经验和启发。
一、背景
大家应该看到过一篇《2022年的十大安全漏洞与利用》的文章,文章中提到一个漏洞:
利用Android Parcel序列化和反序列不匹配,借助应用FileProvider未限制路径,可以获取系统级startAnyWhere能力,从而获取用户敏感信息,修改系统配置,获取系统特权等等。
这里面有三个关键词:
Parcel不匹配漏洞
startAnyWhere
FileProvider未限制路径
看到以上,大家可能会就其中涉及到的几个点有些疑问:
startAnyWhere是什么意思,是什么样的能力?
Parcel不匹配漏洞是什么原理,是如何产生的?
FileProvider的作用是什么,未限制路径又是什么问题?
这几者之间存在什么关联,又会带来哪些风险?
二、FileProvider
2.1 功能简介
首先我们来简单讲一下FileProvider,FileProvider其实就是用来进程间共享文件的。
上方左侧图是早期的应用间共享文件的方案,就是A应用把文件存在外置存储,然后把文件的物理地址给到B应用,B应用去这个地址去取。
那么这样的方式存在哪些问题呢?有以下几点:
权限无法控制:文件存放的位置,要保证都能访问,这样无法精确控制权限;
权限无法回收:文件一旦共享,无法撤销;
目录结构暴露:文件共享需要公开原始的文件地址,暴露了目录结构;
隐私内容泄露:部分私有目录文件共享存在安全隐私泄露的风险。
基于以上问题,google基于ContentProvider设计了FileProvider,如上方右侧图,文件共享必须基于FileProvider,由AMS来管控权限,提供的协议也是定制的content协议。
2.2 使用简介
了解了FileProvider出现的背景,下面介绍一下FileProvider的使用,使用FileProvider需要提供四个参数:
Uri(文件地址)
Action(接收方信息)
Type(文件类型)
Flags(授予权限)
如下面代码,最终通过startActivity来发起共享,记住这个startActivity,很重要。
Intent intent = new Intent();
intent.setAction("");
Uri uri = FileProvider.getUriForFile(getContext(), "", file);
intent.setType(getContext().getContentResolver().getType(uri));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivity(intent);
2.3 URI简介
content URI和普通的Http协议一样,也拥有scheme,authorities,path。
示例:content://authorities /XXX/xxx.txt。
Android提供了xml配置,如下代码所示,把实际的路径映射成一个虚拟的名称,这样的优势就是限制了路径,可以把指定目录的路径共享出去。
看到这里,大家就可以理解未限制路径的含义了,简单讲就是把系统根目录给共享出去了,正确的做法是只共享需要使用的目录。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.file"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/******_paths" />
</provider>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<paths>
<files-path name="test_in" path="/test/file" />
<external-path name="test_external" path="/test/file" />
</paths>
</resources>
2.4 权限简介
FLAG_GRANT_READ_URI_PERMISSION:文件读权限;
FLAG_GRANT_WRITE_URI_PERMISSION:文件写权限;
FLAG_GRANT_PERSISTABLE_URI_PERMISSION:持久授权,直至设备重启或者主动调用revokeUriPermission;
FLAG_GRANT_PREFIX_URI_PERMISSION:相同前缀路径统一授权。
2.5 授权方式
//第一种授权方式
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//第二种授权方式
getContext().grantUriPermission("packageName", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
2.5.1 第一种授权方式:
一次授权,用完即止。
2.5.2 第二种授权方式:
不含持久授权flag的,权限依附于进程存活;
包含持久授权flag的,重启或者主动拒绝权限才会消失。
2.6 小结
上面主要简单介绍了一下FileProvider的设计思想、技术方案、使用方式,由此我们可以对文章开头提出的一些疑问进行解答。
1、FileProvider的作用
答:跨进程共享文件,一般通过startActivity的方式。
2、未限制路径
答:没有指定需要共享的文件目录,将系统根目录共享出去了。
3、存在什么风险,如何进行攻击
答:单针对FileProivider来看,风险较小,光依赖FileProvider这个问题还是没法进行攻击的,原因如下:
文件共享需要业务主动通过startActivity才能发起
读写权限交由系统来管理
三、startAnyWhere
接下来讲一下上文中提到的startAnyWhere,顾名思义,就是应用想打开哪个页面就打开哪个页面,那么在Android系统中,谁才有这个能力呢?
3.1 实现原理
能够实现startAnyWhere的只有系统SystemUid应用,这类应用在startActivity进行权限校验的时候是直接放行的,无论Activity是否exported,都能打开,最常见的应用比如系统设置。
下面是一个系统设置打开第三方应用的案例,通过设置可以直接打开第三方的账户登录页。
3.2 实现流程
通过设置页面的添加账号的功能,可以直接拉起对应应用的界面,这个是今天漏洞的核心,我们来看一下系统调用流程。
如下图,首先系统设置调用AccountManager的addAccount,然后通过SystemServer中的AccountManagerService,一直调用到目标APP本身的AddAccount实现。
由APP本身提供一个Bundle,Bundle里面本身包含了一个intent的由设置进行打开。
这个里面其实存在一个风险,第三方应用可以随意提供一个恶意Intent,系统会直接调用startActivity,随之而来的风险很大。
上图中还存在一个第0步,即这个流程的发起方可以是三方应用本身,不一定需要从设置进入,那么这个整个流程就闭环了,完全无需用户介入,用户也可以完全无感知。
不过这个风险呢,google在Android4.4之后已经修复了,4.4之后增加了对intent内容的校验。
代码如下:
if (result != null&& (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
if (!checkKeyIntent(Binder.getCallingUid(),intent)) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,"invalid intent in bundle returned");
return;
}
}
上面代码就是取出外部传入的KEY-INTENT进行校验,这里面已经出现了今天的主角Parcel,整个攻击也是通过Parcel漏洞使得恶意的KEY-INTENT绕过系统的检查。
四、Parcel
下面我们看一下parcel漏洞及原理。
4.1 Parcel 简介
parcel是专门为Android提供的一个序列化的类,parcel的原理其实很简单,就是一个严格的对称读写,如下代码所示。
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mSize);
}
public void readFromParcel(Parcel in) {
mSize = in.readInt();
}
同时序列化遵循基本的TLV格式,也就是Tag-Length-Value,Tag代表类型,Length代表长度,Value代表值,当然一些特殊情况:
Length不描述:有固定长度的类型可以不描述length,比如long,int等等;
Tag不描述:bundle序列化时,key一定是string类型,所以不需要描述Tag。
回到对称读写这一块,如果这个代码不对称了会出现什么情况呢,google曾经在android源码中出现了很多类似不对称的错误,看一下下面几个案例。
4.2 Parcel 不对称读写案例
4.2.1 案例1
如下图,这是一个典型且明显的不对称,writeLog&readInt,为什么不对称,很简单,int和long对应的长度不一样。
4.2.2 案例2
这是一个比较隐晦的不对称案例,是Android原生的WorkSource类,这个不对称一眼无法看出,以致于最近的Android版本这个问题一直存在,这个类也是此次漏洞攻击真正被利用的一个类。
下面简单看一下WorkSource序列化和反序列化的流程。
序列化
如下述代码,WorkSource序列化时,如果mChains是一个长度为0的空list,那么就会走else分支,此时序列化会连续写两个0。
序列化:
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mNum);
dest.writeIntArray(mUids);
dest.writeStringArray(mNames);
if (mChains == null) {
dest.writeInt(-1);
} else { // 当mChains不为空的时候,这时候写了两个0
dest.writeInt(mChains.size());// 写第一个0
dest.writeParcelableList(mChains, flags);// 写第二个0
}
}
- 反序列化
如下述代码,WorkSource反序列化时,当读到第一个0也就是numChains=0的时候,这个对应mChains长度为0,同样也会走else分支,此时mChains直接被置为null,但是序列化其实是写了两个0,这时候后面还有一个0没有读,这样序列化和反序列化就造成了不对称。
反序列化:
WorkSource(Parcel in) {
mNum = in.readInt();
mUids = in.createIntArray();
mNames = in.createStringArray();
int numChains = in.readInt(); // 读第一个0
if (numChains > 0) {
mChains = new ArrayList<>(numChains);
in.readParcelableList(mChains, WorkChain.class.getClassLoader());
} else { // 当读到numChains=0的时候,这时候直接就将mChains置为null,第二个0还没有读
mChains = null;
}
}
当然实际上不对称的类还有很多,大家可以看下网上泄露出来的漏洞利用源码,有很多这样的类,这里就不列出来了,知道了漏洞的本质是因为Parcel读写不对称,我们接下来看一下其中的原理。
4.3 parcel 漏洞原理
了解parcel漏洞真正的原理之前,首先来看一下系统校验intent的序列化流程。
4.3.1 系统校验序列化流程
首先攻击者手动会序列化一次需要传给系统的bundle,然后系统会反序列化一次进行校验,校验完之后又会重新序列化交给设置,然后设置真正去打开页面的时候会再次反序列化,这样就经历了两次序列化与反序列化,因为其中读写不对称,所以给了攻击者有机可趁的机会。
4.3.2 漏洞原理简介
这个漏洞核心就是前后一共经历了两次序列化和反序列化。我们以上面4.2.1案例1的不对称举例(readInt()对应writeLong()),当出现不对称读写之后,两次序列化与反序列化会有什么后果?如下图所示可以看到:
第一次序列化:输入两个int 1;
第二次反序列化:读的时候是readInt(),读出两个int 1;
第三次序列化:写的时候是writeLong(),这是分别写了long 1和int 1,long的长度是int长度的双倍;
第四次反序列化:读的时候是readInt(),第一个long 1会被分成两个int来读,所以就一次读成了101。
而攻击者也正是借助这个不对称,导致实际输入和输出不一样,隐藏了恶意的KEY-INTENT,从而绕过了系统的校验,以此打开任意一个页面,实现startAnyWhere。
4.3.3 漏洞原理实践
因为案例1比较明显,google早已经修复该漏洞,而WorkSource因为比较隐晦,所以该漏洞一直存在,我们接下来看一下如何利用WorkSource来构造攻击实现。
下面一张图带你搞明白如何通过两次序列化和反序列化达到我们的目的:
由上述文章可知,最终给到系统校验的是一个bundle类型的数据结构,bundle是存储key-value类型的,而我们目的就是要将恶意的KEY-INTENT隐藏起来然后绕过系统的校验。接下来详细讲一下实现步骤:
1、手动序列化:
如上图左侧第一列,手动序列化这个bundle,这个bundle序列化时携带了三个key-value:
第一个key-value:WorkSource相关的;
第二个key-value:经过精心构造;
第三个key-value:隐藏恶意的KEY-INTENT。
第一次序列化后的bundle通过16进制打印出来如下图所示:
2、系统进行反序列化
经过系统第一次反序列化,没有触发不对称,系统是读不到这个恶意的KEY-INTENT的,所以自然校验通过。
3、系统重新序列化
系统校验完需要重新序列化,这时候由于读写不对称,最终红色区域【1,-1】两个值变成了【0,0】。
4、setting反序列化
setting再次反序列化,上面也讲到了,由于不对称,原本两个0只读了1个0。
5、解析最终的key-value
读第一个key-value:由于上述WorkSource的不对称,原本两个0只读了1个0;
读第二个key-value:由于读第一个时少读了一个0,剩余的0变成了第二个key-value的内容,整体内容错位,由于遵循TLV的格式,错位之后,0和13变成了第二个key-value的key,恶意KEY-INTENT前的所有值都变成了第二个key-value的value;
读第三个key-value:此时真正恶意的KEY-INTENT变成我们需要的第三个key-value。
五、漏洞攻击实战
通过上面两节,我们可以看到,借助startAnyWhere和parcel漏洞,可以绕过系统校验任意打开一个页面,下面来看两个真实案例:
5.1 实战案例1
可以看到在虚拟机上,通过这个漏洞直接就打开了锁屏密码的设置页面,然后可以直接绕过密码校验将锁屏密码改掉。
5.2 实战案例2
案例1已经足以反应出问题和风险,但是实际上国内的手机经过改造,基本不会存在这个问题,那么我们来看一下真机上的使用案例:
在讲这个案例之前,我们要先额外讲一下XXSDK中存在的一个AsistActivity,里面存在一段代码,如下所示。
这个代码很简单,就是接受外部的intent的然后直接startActivity了,这里面又提到了startActivity,上面文件共享也是这样调用的,正好符合了FileProvider的使用逻辑。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//此处省略部分代码
Intent intent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
int intExtra = intent == null ? 0 : intent.getIntExtra("", 0);
//此处省略部分代码
startActivityForResult(intent, intExtra);
}
借助这个类,我们便可以模拟一个完整的攻击流程,如下图所示:
第一步:攻击APP构造一个intent1,这个intent1的意图是打开上述AssistActivity;intent1中携带了恶意的intent2,这个intent2的意图打开攻击APP的指定页面,然后让应用共享指定文件了;
第二步:调用andorid系统添加账号页面;
第三步:业务APP中由于集成了AssistActivity,接受恶意的intent2会直接startActivity进行共享文件;
经过以上三步,直接就把APP的一些隐私文件共享给攻击APP,同时攻击APP可以在恶意intent中授权直接修改文件。
5.3 恶意intent的代码
下面看一下恶意intent的代码:
private Intent makeFileIntent() {
Intent intent1 = new Intent().setComponent(new ComponentName("XXX", "xxx.xxx.AssistActivity")); // 打开AssistActivity
Uri uri = Uri.parse("content://xxxx/xxx_info");
Intent intent2 = new Intent(mContext, SecondActivity.class); // 打开攻击者的页面并且共享指定URI的文件
intent2.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent2.setType(mContext.getContentResolver().getType(uri));
intent2.setData(uri);
intent1.putExtra("key", intent2);// 恶意intent2放入intent1中
return intent;
}
5.4 恶意序列化的代码
目前漏洞均已修复,为避免风险,不展示所有代码。
private static Bundle makeEvilIntent(Intent intent) {
Bundle bundle = new Bundle();
Parcel obtain = Parcel.obtain();
Parcel obtain2 = Parcel.obtain();
Parcel obtain3 = Parcel.obtain();
obtain2.writeInt(3);// bundle中key-va长度
obtain2.writeString("firstKey");
obtain2.writeInt(4); //VAL_PARCELABLE
obtain2.writeString("android.os.WorkSource");
obtain2.writeInt(-1);//mNum
obtain2.writeInt(-1);//mUids
obtain2.writeInt(-1);//mNames
obtain2.writeInt(1);//mChains.length
obtain2.writeInt(-1);
...此处省略一些构造代码
bundle.readFromParcel(obtain);
return bundle;
}
以下是视频演示,通过上面这一段攻击代码,拿到了手机上某个APP存在应用私有目录下的账号信息, 同样为了隐私,此处部分脱敏。
六、漏洞利用影响
通过上文的介绍,我们知道借助这个漏洞可以实现对系统任意文件的修改,下面列出了漏洞带来的影响:
读取用户隐私信息;
安装恶意应用;
改写动态加载的代码;
改写系统配置;
获取特殊权限。
七、漏洞修复措施
除了发现问题更重要的是解决问题,下面列出了修复这个漏洞对应的一些方案:
系统层:
修复pacel漏洞的不对称;
系统校验的时候,做两次序列化与反序列化;
应用层:
FileProvider增加路径限制;
接受intent的Activity要着重注意校验,设置黑白名单。
八、漏洞预防措施
漏洞其实是不可避免的,下面是面对层出不穷漏洞的一些预防措施:
组件能不导出就不导出;
可导出的组件建议增加签名或者包名校验;
接受intent或者url参数务必校验;
文件共享务必遵循最小化原则;
敏感内容需要进行加密。
九、总结
接下来简单回顾一下,本文主要讲了5方面内容:
第1方面:主要描述了FileProvider,阐述了其出现背景、设计原理、使用方式、优缺点等;
第2方面:主要描述了startAnyWhere,阐述了其实现原理、实现方式;
第3方面:主要描述了Parcel不对称漏洞,阐述了Parcel的设计原理、不对称漏洞、漏洞案例、漏洞原理以及漏洞利用方案;
第4方面:主要描述了漏洞攻击实战,从模拟器到真机,从原理到代码,演示了通过漏洞攻击手机、获取用户隐私信息的流程;
第5方面:主要是讲了漏洞带来的影响、漏洞的修复和预防措施。
整体来讲,这个漏洞波及了所有的Android手机,无论是对用户,对企业都造成了巨大的损失。
作为开发者的我们需要从自身做起,守护好每一个环节,避免让攻击者有隙可乘。
有隙可乘 - Android 序列化漏洞分析实战的更多相关文章
- ECShop全系列版本远程代码执行高危漏洞分析+实战提权
漏洞概述 ECShop的user.php文件中的display函数的模版变量可控,导致注入,配合注入可达到远程代码执行.攻击者无需登录站点等操作,可以直接远程写入webshell,危害严重. 漏洞评级 ...
- Android内存泄漏分析实战
内存泄漏简单介绍 java能够保证当没有引用指向对象的时候,对象会被垃圾回收器回收.与c语言自己申请的内存自己释放相比,java程序猿轻松了非常多.可是并不代表java程序猿不用操心内存泄漏.当jav ...
- Apache Shiro 反序列化漏洞分析
Shiro550 环境搭建 参考:https://www.cnblogs.com/twosmi1e/p/14279403.html 使用Docker vulhub中的环境 docker cp 将容器内 ...
- Zip Slip漏洞审计实战
前言 最近看到许少的推有说到Zip Slip这个漏洞导致的RCE,其实我在代码审计的时候确实发现有不少功能模块都是使用ZIP来解压,其实还是在真实系统中经常见到的. 于是想着好久没有写过博客了,想借着 ...
- Android APP通用型拒绝服务、漏洞分析报告
点评:记得曾经有段时间很多SRC平台被刷了大量APP本地拒绝服务漏洞(目前腾讯金刚审计系统已经可检测此类漏洞),移动安全团队发现了一个安卓客户端的通用型拒绝服务漏洞,来看看他们的详细分析吧. 0xr0 ...
- Android Studio +MAT 分析内存泄漏实战
对于内存泄漏,在Android中如果不注意的话,还是很容易出现的,尤其是在Activity中,比较容易出现,下面我就说下自己是如何查找内存泄露的. 首先什么是内存泄漏? 内存泄漏就是一些已经不使用的对 ...
- 从0开始fastjson漏洞分析
关于fastjson漏洞利用参考:https://www.cnblogs.com/piaomiaohongchen/p/10799466.html fastjson这个漏洞出来了很久,一直没时间分析, ...
- jackjson学习2+CVE-2019-14379漏洞分析
最近想着分析jackson,jackson和fastjson有点相似,浅蓝大神的文章很好,个人受益匪浅 昨天简单说了下jackson的用法,现在继续拓扑,补充前置知识,前置知识补充的足够多,那么漏洞分 ...
- Java反序列化漏洞分析
相关学习资料 http://www.freebuf.com/vuls/90840.html https://security.tencent.com/index.php/blog/msg/97 htt ...
- Android开发之漫漫长途 X——Android序列化
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解And ...
随机推荐
- 使用OHOS SDK构建benchmark
参照OHOS IDE和SDK的安装方法配置好开发环境. 从github下载源码. 执行如下命令: git clone --depth=1 https://github.com/google/bench ...
- 本周二晚19:00战码先锋第5期直播丨深入理解OpenHarmony系统启动,轻松踏上设备软件开发之旅
OpenAtom OpenHarmony(以下简称"OpenHarmony")工作委员会首度发起「OpenHarmony开源贡献者计划」,旨在鼓励开发者参与OpenHarmony开 ...
- Numpy数组变形和轴变换
数组变形(reshape)或轴转换(Transposing Arrays and Swapping Axes)后返回的是非副本视图,对于非副本视图的修改会使原来的数组也同时改变. In [1]: im ...
- Agent内存马分析
什么是Java Agent 我们知道Java是一种强类型语言,在运行之前必须将其编译成.class字节码,然后再交给JVM处理运行.Java Agent就是一种能在不影响正常编译的前提下,修改Java ...
- 初学STM32 SDIO(一)
1. SDIO协议简介 SDIO全称是安全数字输入/输出接口 ,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是SPI接口,另外一种就是SDIO接口. 多媒体卡(MMC).SD卡. S ...
- 一个很好用的ORM库--peewee
发现一个很好用的 ORM 库 -- peewee 以下为简单示例 from peewee import * db = SqliteDatabase('test.db') # 定义表结构 class P ...
- MCM箱模型建模方法及大气O3来源解析
OBM箱模型可用于模拟光化学污染的发生.演变过程,研究臭氧的生成机制和进行敏感性分析,探讨前体物的排放对光化学污染的影响.箱模型通常由化学机理.物理过程.初始条件.输入和输出模块构成,化学机理是其核心 ...
- 重新点亮linux 命令树————网络配置的查看[十一三]
前言 简单整理一下网络配置. 正文 通过ifconfig 查看. 这个就是ip地址. 网卡mac地址. 还有一块信息非常重要: 这个io开头的信息,这里面就是我们127.0.0.1的信息. 那么就来演 ...
- .NET8中的Microsoft.Extensions.Http.Resilience库
接上一篇,https://www.cnblogs.com/vipwan/p/18129361 借助Aspire中新增的Microsoft.Extensions.ServiceDiscovery库,我们 ...
- 【hibernate】使用HQL对页面进行时间校验操作(预约)
[hibernate]使用HQL对页面进行时间校验操作(预约) 预约系统中的时间校验 正好接了一个预约的需求,还需要用java 7和hibernate 1.时间冲突,时间段不能重复,在保存前对数据库进 ...