Note: This is part of my @vr_progress journal. Also, subscribe to my new @SideQuest_256 channel and I might post videos about the Android journey too :D
This is a story about how I wasted my weekend over a bug that was categorized as a High/EoP but then couldn’t find a clever way to elevate privileges with it.
It all started when I decided to revisit an old testing device - my trusty OnePlus phone that had been sitting in a drawer, untouched and unpatched for years. Since I hadn’t updated it in ages, I thought it would be fun to try exploiting a 1-day vulnerability that affected the device.
Scrolling through the Android advisories, CVE-2020-0238 caught my attention, and I thought, “Why not?” After all, the bug was labeled as High severity, with the potential for Elevation of Privilege (EoP). Also, it’s a logic bug so I might even get a quick win(unlike memory corruptions that usually take more time and special engineering to exploit)
What followed was a weekend full of debugging, writing PoC code, and realizing that, while the bug seemed interesting on the surface, its real-world exploitability has more pre-requisites than I thought.
- What I expected: Direct EoP from a
untrusted_app
to a privilegedsettings.apk
. - What I got: Potential/Indirect EoP from attacker apk to a victim apk.
Here’s what I learned about CVE-2020-0238, its fix, and its impact.
Important
After this blogpost was published, it got spread and I managed to find the original reporter of the bug via a Telegram discussion about it! I had the opportunity to speak to him about it. You can find him on X at @vulnano and LinkedIn at Dzmitry Lukyanenko
Please see my updated conclusions at Update (05, Dec, 2024)
The Fix
Below is the fix:
- Advisory: https://source.android.com/docs/security/bulletin/2020-08-01
- Commit: 33dd3187d0246a0425a41f76888a369c16dc9379 :
Analysis
The issue revolves around the AccountTypePreferenceLoader
class in Android’s Settings app, specifically in how it resolved and validated activities associated with an account type. The problem is triggered from the AccountDetailDashboardFragment
class(in com.android.settings.accounts
), where user accounts are managed.
AbstractAccountAuthenticator
: This is a base class that app developers use to create custom authenticators for managing accounts on Android. It defines methods likeaddAccount()
orgetAuthToken()
.AccountTypePreferenceLoader
: This class is responsible for loading UI elements (preferences) for account types in the Settings app. It would erroneously allow launching activities from other apps if certain conditions were met.- The Bug: The check for
resolvedActivityInfo.exported
allowed activities not owned by the authenticator to be launched if they were exported and did not require specific permissions.
The fix ensures that only activities belonging to the same uid
as the authenticator’s app can be launched, reducing the risk of privilege escalation.
Re-produce
To exploit this bug, a malicious app needs to manipulate the account_preferences.xml
file and register a custom service that interacts with the vulnerable component.
- Create a Service: Define a malicious service in the app’s manifest to mimic an authenticator:
- Implement
addAccount
: Implement a basicaddAccount()
method in your malicious authenticator service to register an account. - Craft
account_preferences.xml
: Create a customaccount_preferences.xml
file with entries designed to trigger the vulnerable code path.
When the Settings app attempts to load preferences for this malicious account type, the bug allows launching unauthorized activities.
When clicking on the account, the AccountDetailDashboardFragment
is loaded, together with our xml:
Then, a click on the “Trigger bug :D” button, triggers updatePreferenceIntents()
, which is calling the isSafeIntent()
function(the one that the commit patched)
Impact
I’m not really sure how exploitable this is, because the attacker APK needs to have the same permission as the victim APK(at least on my testing device).
Also, if the activity is already exported, why would the attacker app need to use the Settings app to trigger the exported activity? It’s already exported.
The only scenario I could think of is one where the victim app has an exported activity but it verifies which app has triggered the activity (using stuff like getCallingUid()
and getCallingPackage()
), allowing only the Settings app to trigger it.
The fix mitigates these risks by enforcing that only activities owned by the authenticator can be launched. This change effectively ties activity execution to the authenticator’s uid
, preventing unauthorized apps from exploiting this pathway.
Conclusion
In the end, CVE-2020-0238 demonstrates the subtlety of Android security issues - how seemingly innocuous permissions or checks can open doors for unintended behavior. This particular bug was a little bit disappointing, but, whatever. I hope I missed something and find out that it’s more exploitable than I initially thought.
Either way, I learned some new things, which is nice :^)
Thanks for tuning in, and see you in the next attempt.
Update (05, Dec, 2024)
This bug can be exploited to launch privileged activities, however, as I suspected it requires chaining another bug of a race condition which did not affect my OnePlus device because my Android Security Path Level was more than the one of the bug reporter.
After speaking with the original bug reporter @vulnano, he taught me a pretty awesome trick he used to launch un-exported activities of other apps.
The setup goes as follows:
- You create a Dummy activity with the same name of intent of the target app, for example,
DummyActivity
will have an intent with a name ofcom.NotYourApp.gmail.SEND_EMAIL
. - You create a Content Provider
HackyContentProvider
that will count the number of timesgetType()
was called.- Then, after a few times(around 3~)
getType()
was called, you callActivityManager
and disable theDummyActivity
.
- Then, after a few times(around 3~)
- In the
account_preferences.xml
, you don’t specify a target APK or target class. Instead:- you specify an intent that your malicious app
- you specify a
<data>
attribute of the content provider created at step 2, this will triggergetType()
couple of times when the activity will be launched by the settings app.
The attack flow goes as follows:
isSafeIntent()
returns true.- The
mFragment.getActivity().startActivityAsUser()
function is called- It looks in the
account_preferences.xml
file → no activity is specified, only an implicit intent - Android will look up what apps can handle that implicit intent, it gets two results: the ‘email’ app, and your app(specifically the
DummyActivity
, that also has the same intent) - At that point of time(before the activity was launched and after the
isSafeIntent()
returned true: yourHackyContentProvider
will disable the DummyActivity, turning it into an activity that can’t be launched. - As a result, Android will launch the other option from the two it found earlier(the first one was your app, the second was the email app) with the permissions of the
settings.apk
app!
- It looks in the
- profit