|
1 | | -# Android Enterprise Work Profile Required-App Replacement |
| 1 | +# Android Enterprise Work Profile Required-App Replacement and Provisioning Race |
2 | 2 |
|
3 | 3 | {{#include ../../banners/hacktricks-training.md}} |
4 | 4 |
|
5 | | -## Attack surface |
| 5 | +## Work Profile attack surface |
6 | 6 |
|
7 | | -Android Enterprise Work Profiles are implemented as **secondary Android users** (BYOD example: user `0` = personal, user `1` = work). Each user has independent `/data/user/<id>` trees, system apps, Play Services instances and policy objects maintained by the MDM. When an MDM such as **Microsoft Intune** marks an app as *required* for the Work Profile, the **Work-Profile Play Store (Finsky)** periodically confirms the package is present and auto-installs it if missing.<sup>[[1]](#references)</sup> |
| 7 | +An Android Enterprise Work Profile is implemented as a **managed secondary Android user**. App data and package state such as installed, enabled and suspended are scoped per user. The APK code path and signing identity are still managed by the device-wide Package Manager. This distinction matters because an install operation can change the state of the same package across several users. |
8 | 8 |
|
9 | | -Even after the **CVE-2023-21257** patch that blocks ADB sideloads when `DISALLOW_INSTALL_APPS` or `DISALLOW_DEBUGGING_FEATURES` are set, the following chain lets an attacker **replace any Intune-required Work Profile app** with arbitrary code:<sup>[[1]](#references)</sup> |
| 9 | +On Intune-enrolled BYOD devices, apps assigned as **Required** are installed in the Work Profile through managed Google Play. The research behind this page found that this repair flow could be combined with the multi-user installer to place attacker-controlled code in the profile.<sup>[[1]](#references)</sup> |
10 | 10 |
|
11 | | -1. Abuse Android Studio's **"Install for all users"** path to stage a malicious APK that looks like an update of the managed package. |
12 | | -2. Let the MDM notice the required app is missing. Intune triggers the Work-Profile Finsky instance to reinstall it. |
13 | | -3. Finsky compares the staged APK version with the Play Store version and silently installs the **highest `versionCode`**, bypassing the original restriction. |
| 11 | +> The required-app chain below is an observed behaviour on specific Intune, Android and Play Store builds. It is not a generic Android rule. Reproduce it on the exact OEM build and management mode before reporting impact. |
14 | 12 |
|
15 | | -## Recon and prerequisite checks |
| 13 | +## Required-app replacement chain |
16 | 14 |
|
17 | | -* Confirm multi-user layout and user IDs:<sup>[[1]](#references)</sup> |
| 15 | +CVE-2023-21257 changed `InstallPackageHelper` so an `INSTALL_ALL_USERS` request does not mark a package as installed for a Work Profile restricted by `DISALLOW_INSTALL_APPS` or `DISALLOW_DEBUGGING_FEATURES`. The later research found a second path on patched devices:<sup>[[1]](#references)</sup> |
| 16 | + |
| 17 | +1. Android Studio submits an install-for-all-users request for an APK whose package name matches an Intune-required Work Profile app. |
| 18 | +2. The request installs the attacker package in the unrestricted personal user. On the tested devices, it also removes the legitimate package from the Work Profile before policy blocks the direct install there. |
| 19 | +3. Intune notices that the required package is absent and asks the Work Profile Play Store, internally called Finsky, to restore it. |
| 20 | +4. The tested Finsky build selects the locally submitted candidate because its `versionCode` is higher than the managed Google Play release. The attacker APK is then installed in the Work Profile. |
| 21 | + |
| 22 | +The original researcher inferred the local-candidate selection from the observed notifications and timing. The temporary storage location and the exact internal Finsky decision path were not demonstrated. Treat those details as a working explanation rather than a proven implementation contract.<sup>[[1]](#references)</sup> |
| 23 | + |
| 24 | +### Preconditions and limits |
| 25 | + |
| 26 | +* The demonstrated target was an **Intune personally owned Work Profile** with an app assigned as **Required**. |
| 27 | +* The attacker needed temporary access to an unlocked device. Developer Options and USB debugging had to be enabled. The host also needed an accepted ADB authorization. |
| 28 | +* The package name of a required app had to be known. A large `versionCode` was used to win the observed Play selection. |
| 29 | +* A normal Android in-place update requires a compatible signing certificate. A matching package name and larger version alone do **not** normally bypass APK signature checks. This chain depends on the unusual per-user removal and managed Play repair behaviour. A signature mismatch at any stage can stop it. |
| 30 | +* Results can change with the OEM framework, Play Store version, Android security update, enrollment backend and MDM policy. The public report reproduced the chain on Android 13 and Android 16 test devices in August 2025.<sup>[[1]](#references)</sup> |
| 31 | + |
| 32 | +## Recon and evidence collection |
| 33 | + |
| 34 | +Enumerate the users, record the build and identify the package in the Work Profile: |
18 | 35 |
|
19 | 36 | ```bash |
20 | 37 | adb shell pm list users |
21 | | -# Expect user 0 = Owner, user 1 = Work profile (or higher if multiple profiles exist) |
| 38 | +adb shell getprop ro.build.version.release |
| 39 | +adb shell getprop ro.build.version.security_patch |
| 40 | + |
| 41 | +WORK_USER=10 |
| 42 | +PKG=com.workday.workdroidapp |
| 43 | +adb shell pm list packages --user "$WORK_USER" | grep -F "$PKG" |
| 44 | +adb shell dumpsys package "$PKG" | grep -E 'versionCode=|User [0-9]+:' |
22 | 45 | ``` |
23 | 46 |
|
24 | | -* Direct installs into the work user fail under policy (expected error): |
| 47 | +Do not assume that the Work Profile is user `1`. Profile IDs such as `10` or `11` are common. A direct post-enrollment install should fail when policy is active: |
25 | 48 |
|
26 | 49 | ```bash |
27 | | -adb install --user 1 legit.apk |
28 | | -# java.lang.SecurityException: Shell does not have permission to access user 1 |
| 50 | +adb install --user "$WORK_USER" payload.apk |
| 51 | +# Expected on a restricted profile: SecurityException or INSTALL_FAILED_USER_RESTRICTED |
29 | 52 | ``` |
30 | 53 |
|
31 | | -* You must have **temporary physical access to an unlocked BYOD** to enable Developer Options + USB debugging.<sup>[[1]](#references)</sup> |
32 | | -* Identify the **package name** of a Work-Profile app marked as *required* (e.g. `com.workday.workdroidapp`). |
| 54 | +Collect the policy and package evidence before testing. `dumpsys user` exposes effective user restrictions. `dumpsys package` shows per-user package state. The host-side `apksigner` command records the payload certificate: |
33 | 55 |
|
34 | | -## Weaponising the Android Studio multi-user installer |
| 56 | +```bash |
| 57 | +adb shell dumpsys user > users-before.txt |
| 58 | +adb shell dumpsys package "$PKG" > package-before.txt |
| 59 | +apksigner verify --print-certs payload.apk |
| 60 | +adb logcat -c |
| 61 | +adb logcat -v time | grep -Ei 'PackageManager|Finsky|DevicePolicy|ManagedProvisioning' |
| 62 | +``` |
| 63 | + |
| 64 | +## Weaponising the multi-user installer |
35 | 65 |
|
36 | | -Android Studio's Run/Debug configuration can still push builds with the **`INSTALL_ALL_USERS`** flag. Before running, enable *Deploy as instant app* → *Install for all users*.<sup>[[1]](#references)</sup> |
| 66 | +In Android Studio open the app Run/Debug configuration and select **Install for all users**. The exact menu position depends on the Android Studio version. This option is separate from instant-app deployment. |
37 | 67 |
|
38 | | -Build the malicious payload with the **same package name** as the managed app and a **much larger `versionCode`** so PackageManager/Finsky treats it as a newer release:<sup>[[1]](#references)</sup> |
| 68 | +Build the test APK with the required app package name and a version code above the managed Google Play build:<sup>[[1]](#references)</sup> |
39 | 69 |
|
40 | 70 | ```gradle |
41 | 71 | android { |
42 | 72 | namespace = "com.workday.workdroidapp" |
43 | 73 | defaultConfig { |
44 | 74 | applicationId = "com.workday.workdroidapp" |
45 | 75 | versionCode = 900000004 |
46 | | - versionName = "9000000004.0" |
| 76 | + versionName = "900000004.0" |
47 | 77 | } |
48 | 78 | } |
49 | 79 | ``` |
50 | 80 |
|
51 | | -When Android Studio deploys:<sup>[[1]](#references)</sup> |
| 81 | +Run the configuration while the device is attached. On a vulnerable combination, the immediate Work Profile installation is denied but the legitimate required app disappears. Keep `logcat` running and wait for the Work Profile Play Store notification. The reported repair interval was roughly 1–10 minutes.<sup>[[1]](#references)</sup> |
52 | 82 |
|
53 | | -1. **Personal user (0)** installs the malicious package normally. |
54 | | -2. **Work Profile user (1)** receives the APK in a temporary staging area and tries to treat it as an update. |
55 | | -3. CVE-2023-21257's logic sees the user is restricted → **install is denied**, but the legitimate managed app is marked uninstalled and the staged APK remains cached. |
| 83 | +Verify the result per user instead of relying on the launcher icon: |
56 | 84 |
|
57 | | -## Intune/Finsky auto-install bypass |
| 85 | +```bash |
| 86 | +adb shell pm list packages --user 0 | grep -F "$PKG" |
| 87 | +adb shell pm list packages --user "$WORK_USER" | grep -F "$PKG" |
| 88 | +adb shell pm path --user "$WORK_USER" "$PKG" |
| 89 | +adb shell dumpsys package "$PKG" | grep -E 'versionCode=|installerPackageName|User [0-9]+:' |
| 90 | +``` |
58 | 91 |
|
59 | | -Within ~1–10 minutes (policy refresh interval):<sup>[[1]](#references)</sup> |
| 92 | +A useful negative control is the same APK with a lower `versionCode`. Also repeat with a non-required package. These controls distinguish the managed Play repair path from a general multi-user installation mistake. |
60 | 93 |
|
61 | | -1. Intune/Company Portal detects the *required* package is missing from the Work Profile. |
62 | | -2. The Work-Profile **Finsky** instance is asked to reinstall it. |
63 | | -3. During version resolution Finsky compares: |
64 | | - * Play Store metadata for `com.workday.workdroidapp`. |
65 | | - * The locally staged APK from the previous install attempt. |
66 | | -4. Because the local build has the **highest `versionCode`**, Finsky trusts it as the most recent release and installs it into the restricted Work Profile **without re-applying `DISALLOW_INSTALL_APPS` / `DISALLOW_DEBUGGING_FEATURES` checks**. |
| 94 | +## Provisioning-time ADB race |
67 | 95 |
|
68 | | -The malicious binary now resides inside the Work Profile under the genuine package name and is considered compliant by the MDM.<sup>[[1]](#references)</sup> |
| 96 | +A different technique targets **profile creation**, not required-app repair. CVE-2025-22442 describes a race in which a new managed profile exists before the effective debugging restriction protects it. If ADB was authorized before enrollment, a tester controlling the enrollment flow can watch for the new user ID and immediately install an APK into that user. |
69 | 97 |
|
70 | | -## Post-exploitation opportunities |
| 98 | +The following lab loop illustrates the timing. Adapt the profile label if the device language or OEM changes it: |
71 | 99 |
|
72 | | -* **Work-profile data access** – other enterprise apps keep trusting Intents/content providers bound to the replaced package, enabling internal data theft and covert exfiltration from the Work Profile to attacker infrastructure.<sup>[[1]](#references)</sup> |
73 | | -* **Per-app VPN hijack** – if the replaced package is mapped to an Intune per-app VPN (MS Tunnels + Defender), the malicious build automatically inherits the VPN profile, giving direct access to internal hosts from an attacker-controlled process. |
74 | | -* **Persistence** – because the MDM now believes the required app is installed, it will **reinstall the malicious build** whenever the user or defender removes it, providing long-term foothold on BYOD Work Profiles. |
| 100 | +```bash |
| 101 | +APK=payload.apk |
| 102 | +while :; do |
| 103 | + WORK_USER=$(adb shell pm list users | |
| 104 | + sed -n 's/.*UserInfo{\([0-9][0-9]*\):Work profile.*/\1/p' | tr -d '\r') |
| 105 | + [ -n "$WORK_USER" ] && |
| 106 | + adb install --user "$WORK_USER" "$APK" && break |
| 107 | + sleep 0.25 |
| 108 | +done |
| 109 | +``` |
75 | 110 |
|
76 | | -## References |
| 111 | +This race only applies while the profile is being provisioned. After the DPC applies `DISALLOW_DEBUGGING_FEATURES`, the same `adb install --user` request should fail. It also does not require the payload to impersonate an existing package. |
| 112 | + |
| 113 | +The AOSP hardening adds `DISALLOW_DEBUGGING_FEATURES` to the restrictions enabled by default for managed profiles. It deliberately removes that restriction for profiles whose owner is provisioned through ADB so CTS and development flows continue to work. Do not validate the fix using an ADB-created Test DPC profile because that exercises the exception instead of the enterprise path.<sup>[[2]](#references)</sup> |
| 114 | + |
| 115 | +{% hint style="warning" %} |
| 116 | +In February 2026, Android documentation removed CVE-2025-22442 from the April 2025 bulletin's list of addressed vulnerabilities. Therefore, a displayed `2025-04-01` or newer security patch level is not sufficient evidence that the exact OEM build closes the provisioning window. Verify that a real EMM-created profile rejects the loop above from the moment its user ID becomes visible.<sup>[[3]](#references)</sup> |
| 117 | +{% endhint %} |
| 118 | + |
| 119 | +## Impact validation |
| 120 | + |
| 121 | +Being present in the Work Profile does **not** let the payload read every other app's private directory. Android UID isolation still applies. Validate concrete trust relationships instead: |
77 | 122 |
|
78 | | -- [1] [Bypassing CVE-2023-21257 via Intune Required-App Auto-Install](https://jgnr.ch/sites/android_enterprise.html) |
| 123 | +* **Intent and provider trust:** look for exported components, implicit Intents, URI grants or workflows that send documents to the replaced package. |
| 124 | +* **Per-app VPN inheritance:** the original research observed that replacing a package selected for Microsoft Tunnel per-app VPN made the attacker process eligible for that route. Confirm the route with a harmless internal test endpoint because VPN assignment and identity checks vary by deployment.<sup>[[1]](#references)</sup> |
| 125 | +* **Package-only compliance:** determine whether the MDM checks only presence and version or also evaluates Play provenance, signing certificate, Play Integrity and app-specific authentication. |
| 126 | +* **Lifecycle:** deleting the payload, syncing policy, rebooting and removing the Work Profile are separate tests. Do not claim guaranteed persistence. A later repair can select the legitimate Play build once the local candidate is gone. |
| 127 | + |
| 128 | +## Detection and mitigation notes |
| 129 | + |
| 130 | +* For the required-app chain, compare reported package versions and signing certificates with the approved release. Alert on unexpected high version codes and packages whose installer or provenance changes. |
| 131 | +* Capture app inventory immediately after enrollment. This is especially useful for finding APKs inserted during the provisioning race. |
| 132 | +* Test the exact supported enrollment method and OEM image. Require a negative ADB installation test as part of acceptance testing rather than trusting only the security patch string. |
| 133 | +* Where operationally possible, assign sensitive apps as **Available** instead of **Required** until the required-app repair behaviour has been verified. This removes the auto-repair trigger used by the demonstrated Intune chain.<sup>[[1]](#references)</sup> |
| 134 | +* Do not map a high-value per-app VPN only by package name. Add application-layer authentication and device or app integrity checks. |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +## References |
79 | 139 |
|
| 140 | +- [1] [Arbitrary App Installation on Intune Managed Android Enterprise BYOD](https://jgnr.ch/sites/android_enterprise.html) |
| 141 | +- [2] [AOSP: Disable Developer options by default for managed profiles](https://android.googlesource.com/platform/frameworks/base/+/2095d130b4d7f2ba1a3284abb58ca894817f5f4a) |
| 142 | +- [3] [AOSP site updates: February 2026 security documentation changes](https://source.android.com/docs/whatsnew/site-updates#february-2026) |
80 | 143 | {{#include ../../banners/hacktricks-training.md}} |
0 commit comments