All Advisories

Element X Android

Denial of Service via a Malformed OIDC Redirect Intent

The OAuth redirect parser calls Kotlin's error() for any URL that matches the redirect scheme but carries neither code= nor error=access_denied, and nothing on the call chain catches the resulting IllegalStateException. The redirect scheme is exported with the BROWSABLE category, so any installed application, holding no permissions at all, and any web page the user taps a link on can terminate the messenger process on demand.

SeverityMediumCVSS 6.5CVSS 3.1 VectorAV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:HCWECWE-248 (Uncaught Exception)ProductElement X AndroidAffected VersionsAll versions with OIDC support, up to and including 26.06.1Fixed In26.06.2CVECVE-2026-55080GHSAGHSA-r4f9-46vw-v7g3

Description

The parser recognizes exactly two shapes of redirect URL, the cancel case and the success case, and treats everything else as unreachable. The comment says so:

OAuthUrlParser.kt:36-43 (v26.06.1)

override fun parse(url: String): OAuthAction? {
if (url.startsWith(oAuthRedirectUrlProvider.provide()).not()) return null
if (url.contains("error=access_denied")) return OAuthAction.GoBack()
if (url.contains("code=")) return OAuthAction.Success(url)
// Other case not supported, let's crash the app for now
error("Not supported: $url")
}

View source →

Kotlin's error() throws IllegalStateException. No handler exists on the path from the exported activity down to the parser:

Call chain

MainActivity.onNewIntent()
-> MainNode.handleIntent() [lifecycleScope.launch, no CoroutineExceptionHandler]
-> RootFlowNode.handleIntent()
-> IntentResolver.resolve()
-> DefaultOAuthIntentResolver.resolve()
-> DefaultOAuthUrlParser.parse()
-> error("Not supported: $url")

MainNode.handleIntent() launches the resolution on lifecycleScope with no CoroutineExceptionHandler, so the exception reaches the default handler and the process is killed.

The redirect scheme is registered as an exported intent filter, and the BROWSABLE category makes it reachable from the browser as well as from other applications:

AndroidManifest.xml:80-87 (v26.06.1)

<!--
OAuth redirection
-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/login_redirect_scheme" />
</intent-filter>

View source →

The resulting crash, from a device running the affected release:

Crash output (v26.03.3, Android 15)

FATAL EXCEPTION: main
Process: io.element.android.x, PID: 4800
java.lang.IllegalStateException: Not supported: io.element.android:/?state=...
at io.element.android.libraries.oauth.impl.DefaultOAuthUrlParser.parse(OAuthUrlParser.kt:42)

All build variants are affected through their own schemes: io.element.android for release, io.element.android.debug for debug, io.element.android.nightly for nightly. The comment above the call and the accompanying unit test show the exception was the expected outcome for this path before the fix. The code dates back to the initial OIDC implementation in April 2023 and was carried unchanged through the rename of the OIDC components to OAuth in April 2026.

Reported to Element on 2026-04-01. The fix landed on 2026-06-10 and shipped in 26.06.2. The error() call became a log line and a null return, which the caller already treats as an unrecognized URL:

OAuthUrlParser.kt:37-45 (v26.06.2, fixed)

override fun parse(url: String): OAuthAction? {
if (url.startsWith(oAuthRedirectUrlProvider.provide()).not()) return null
if (url.contains("error=access_denied")) return OAuthAction.GoBack()
if (url.contains("code=")) return OAuthAction.Success(url)
// Other cases are not supported, log an error and return null
Timber.w("Unsupported OAuth url")
return null
}

View source →

Impact

  • Any application installed on the device can crash Element X Android whenever it chooses, with no Android permission and no user interaction. A web page does the same when the user taps a link. The messenger stays unavailable for as long as the attacking application keeps sending the intent.
  • The failure mode matters more than the individual crash. An application that re-fires the intent on a timer makes the messenger unusable without ever touching its data, and the person using the device gets no indication of which installed application is responsible. Timing the crash to the moment the messenger is opened is also possible, but that variant needs the user-granted PACKAGE_USAGE_STATS permission to read the foreground app; the untimed version needs none. We confirmed both against Element X Android v26.03.3 from the Play Store on Android 15.
  • No message content, key material, or account data is exposed. The impact is availability only, and it lands on the user rather than on the homeserver.

Mitigation

Update Element X Android to 26.06.2 or later. The fix replaces the error() call with a log line and a null return, which the caller already handles as an unrecognized URL. There is no configuration-level workaround for earlier versions: the intent filter has to stay exported for the OIDC login flow to work at all.

Defender's Checklist

  • Update to Element X Android 26.06.2 or later.

    Every earlier release with OIDC support is affected, across the release, debug, and nightly schemes.

  • Do not wait for a workaround.

    The OIDC redirect intent filter has to stay exported for login to work, so no configuration closes this without the code fix. Fleet-managed deployments should treat the update as required rather than optional.

  • Audit forks for the same pattern.

    Applications built on the Element X Android codebase inherit the parser. Grep for error( on any path reachable from an exported intent filter: an IllegalStateException on an attacker-supplied URL is a crash, not a guard.

  • Review your other exported entry points.

    The root cause is an error() call standing in for an unhandled case on a path that later became externally reachable. Any exported activity, receiver, or provider that parses attacker-supplied data deserves the same review, including the ones whose unit tests currently assert that they throw.

Severity Reasoning

AV:NThe BROWSABLE category makes the redirect scheme reachable from a web page, not only from a local application. Scored consistently with CVE-2025-27599, which used the same exported intent-filter pattern in this application.AC:LA single intent carrying an unrecognized parameter set. No timing or environmental conditions.PR:NThe sending application needs no Android permission, and no account on any homeserver.UI:RThe web-triggered path needs the user to tap a link. The local app-to-app path needs no interaction at all, but scores lower overall (AV:L/UI:N gives 6.2), so the network vector is the one scored.S:UThe crash is confined to the application process.C:NNothing is read.I:NNothing is modified.A:HThe process terminates, and repeated delivery keeps it terminated.

References

How We Can Help

Who We Are

The security researchers behind this advisory.

Dr. Simon Weber Profile

Dr. rer. nat. Simon Weber

Senior Pentester & MedSec Researcher

I evaluate your SaMD with the same industry-defining security insight I contributed to the BAK MV for the revision of the B3S standard.

  • PhD on Hospital Cybersecurity
  • Critical vulnerabilities found in hospital systems
  • Alumni of THB MedSec Research Group
  • gematik Security Hero
Volker Schönefeld Profile

Dipl.-Inf. Volker Schönefeld

Senior Application Security Expert

As a former CTO and developer turned pentester, I work alongside your team to uncover vulnerabilities and find solutions that fit your architecture.

  • 20+ years as CTO, 50M+ app downloads
  • Architected and secured large-scale IoT fleets
  • Certified Web Exploitation Specialist
  • gematik Security Hero

Looking for a Penetration Test?

Machine Spirits specializes in security assessments for medical devices and healthcare IT. From MDR penetration testing to C5 cloud compliance, we help MedTech companies meet regulatory requirements.