一、漏洞背景
- 在AOSP的2020-09补丁中,披露了一个Telephony eSIM模块的漏洞,编号为CVE-2020-0396并且为Critical级别。成功利用漏洞可造成eSIM的信息泄露。
二、漏洞细节
- 漏洞位于
com.android.internal.telephony.euicc.EuiccController
中,在构造一个PendingIntent时没有指定接收者,导致Intent变成了一个隐式Intent,任何注册了对应intent-filter的Activity都会进入选择列表。// frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccController.java /** Add a resolution intent to the given extras intent. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void addResolutionIntent(Intent extrasIntent, String resolutionAction, String callingPackage, int resolvableErrors, boolean confirmationCodeRetried, EuiccOperation op, int cardId) { Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR); // Missing receiver of PendingIntent intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION, resolutionAction); intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage); intent.putExtra(EuiccService.EXTRA_RESOLVABLE_ERRORS, resolvableErrors); intent.putExtra(EuiccService.EXTRA_RESOLUTION_CARD_ID, cardId); intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED, confirmationCodeRetried); intent.putExtra(EXTRA_OPERATION, op); PendingIntent resolutionIntent = PendingIntent.getActivity( mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT); extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent); }
-
向上梳理该函数的调用者,有好几个调用者,这里以
onGetDefaultListComplete
为例// frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccController.java @Override public void onGetDefaultListComplete(int cardId, GetDefaultDownloadableSubscriptionListResult result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result.getResult()) { case EuiccService.RESULT_OK: resultCode = OK; List<DownloadableSubscription> list = result.getDownloadableSubscriptions(); if (list != null && list.size() > 0) { extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS, list.toArray(new DownloadableSubscription[list.size()])); } break; case EuiccService.RESULT_MUST_DEACTIVATE_SIM: resultCode = RESOLVABLE_ERROR; addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, mCallingPackage, 0 /* resolvableErrors */, false /* confirmationCodeRetried */, EuiccOperation.forGetDefaultListDeactivateSim( mCallingToken, mCallingPackage), cardId); break; default: resultCode = ERROR; addExtrasToResultIntent(extrasIntent, result.getResult()); break; } sendResult(mCallbackIntent, resultCode, extrasIntent); }
sendResult
方法中可以看到是将extrasIntent添加到了callbackIntent中(Intent.fillIn),并且发送了callbackIntent这个PendingIntent
// frameworks/opt/telephony/src/java/com/android/internal/telephony/euicc/EuiccController.java
/** Dispatch the given callback intent with the given result code and data. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
public void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
try {
callbackIntent.send(mContext, resultCode, extrasIntent);
} catch (PendingIntent.CanceledException e) {
// Caller canceled the callback; do nothing.
}
}
- 通过上述代码可以梳理出来这个callbackIntent的结构,首先callbackIntent自身有Action等字段,这里以[Original PendingIntent Part]来表示,然后extrasIntent是通过Intent.fillIn方法添加进去的,这里因为没有重复的字段,所以就是简单的组合,而extrasIntent中包含了一个字段名为EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT的PendingIntent,也就是
addResolutionIntent
方法中的resolutionIntent,这个PendingIntent携带的Intent就是造成漏洞的那个Intent,Action为ACTION_RESOLVE_ERROR,extras域有六个字段,包含eSIM的Card ID、Confirmation Code,这些应该是敏感信息。callbackIntent (PendingIntent) | [Original PendingIntent Part] extrasIntent(Intent.fillIn) (Intent) Extras: EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT(resolutionIntent) (PendingIntent) | Action: ACTION_RESOLVE_ERROR Extras: EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION EXTRA_RESOLUTION_CALLING_PACKAGE EXTRA_RESOLVABLE_ERRORS EXTRA_RESOLUTION_CARD_ID EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED EXTRA_OPERATION
- 那么这个复杂的PendingIntent在send之后传递给谁了呢,查找EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT字段之后我们发现,是发送给了
android.telephony.euicc.EuiccManager
这边的startResolutionActivity
方法,最终以startIntentSenderForResult
的形式发送出去了// frameworks/base/telephony/java/android/telephony/euicc/EuiccManager.java public void startResolutionActivity(Activity activity, int requestCode, Intent resultIntent, PendingIntent callbackIntent) throws IntentSender.SendIntentException { PendingIntent resolutionIntent = resultIntent.getParcelableExtra(EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT); if (resolutionIntent == null) { throw new IllegalArgumentException("Invalid result intent"); } Intent fillInIntent = new Intent(); fillInIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT, callbackIntent); activity.startIntentSenderForResult(resolutionIntent.getIntentSender(), requestCode, fillInIntent, 0 /* flagsMask */, 0 /* flagsValues */, 0 /* extraFlags */); }
- 这个
startIntentSenderForResult
是什么接口呢?查看定义发现,如果是在Activity对象上调用的,其效果和startActivityForResult
相似,只是参数变成了PendingIntent的内部实现IntentSender。Like startActivityForResult(android.content.Intent, int), but allowing you to use a IntentSender to describe the activity to be started. If the IntentSender is for an activity, that activity will be started as if you had called the regular startActivityForResult(android.content.Intent, int) here; otherwise, its associated action will be executed (such as sending a broadcast) as if you had called IntentSender#sendIntent on it.
- 所以最终eSIM的Card ID、Confirmation Code这些敏感信息,会以隐式Intent的形式被发送出去,这样一来,只要恶意应用编写一个Action为ACTION_RESOLVE_ERROR的intent-filter,就有机会拿到这些数据。
三、漏洞验证
- 漏洞需要eSIM,现在Pixel 3、Pixel 4系列是支持eSIM的,然而国内并没有,所以没办法复现。
四、漏洞影响
- 造成Card ID、Confirmation Code信息泄露,这个Card ID不知道是什么概念,如果类比实体SIM卡的话,应该类似于IMSI,也就是SIM卡的唯一ID,该ID可用于追踪用户,这可能是Google将漏洞定为Critical的原因。
五、漏洞补丁
- 在
com.android.internal.telephony.euicc.EuiccController
的addResolutionIntent
方法显式添加了接收者,使Intent成为显式Intent。intent.setPackage(RESOLUTION_ACTIVITY_PACKAGE_NAME); intent.setComponent(new ComponentName(RESOLUTION_ACTIVITY_PACKAGE_NAME, RESOLUTION_ACTIVITY_CLASS_NAME));
- 这里可以看到实际的接收者应是
com.android.phone.euicc.EuiccResolutionUiDispatcherActivity
/** Restrictions limiting access to the PendingIntent */ private static final String RESOLUTION_ACTIVITY_PACKAGE_NAME = "com.android.phone"; private static final String RESOLUTION_ACTIVITY_CLASS_NAME = "com.android.phone.euicc.EuiccResolutionUiDispatcherActivity";
- 另外Google还给一大堆Telephony模块中的其他PendingIntent增加了
PendingIntent.FLAG_IMMUTABLE
标志,修改位置达到7处之多,该标志的作用是让PendingIntent在send时候添加的额外Intent失效,也就是不允许二次向原本的Intent中添加内容(不允许Intent.fillIn),这几个修改看起来和EuiccController并不直接相关,可能是为了消减一类问题,防止PendingIntent被二次编辑。另外给BluetoothPairingDialog
加了一个PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS标志,防止蓝牙配对对话框被第三方应用使用顶部显示特权遮挡。