Skip to content

[share] Broadcast receiver registered as RECEIVER_EXPORTED without permission guard (CWE-926) #2591

Description

@jyc66

Bug Report

Plugin(s)

@capacitor/share 8.0.1

Capacitor Version

Latest Dependencies:

  @capacitor/cli: 8.5.1
  @capacitor/core: 8.5.1
  @capacitor/android: 8.5.1
  @capacitor/ios: 8.5.1

Installed Dependencies:

  @capacitor/core: 8.4.0
  @capacitor/cli: 8.4.0
  @capacitor/android: 8.4.0
  @capacitor/ios: 8.4.0

Platform(s)

Android

Current Behavior

SharePlugin.load() registers the EXTRA_CHOSEN_COMPONENT broadcast receiver with ContextCompat.RECEIVER_EXPORTED and no permission string or sender check:

https://github.com/ionic-team/capacitor-plugins/blob/main/share/android/src/main/java/com/capacitorjs/plugins/share/SharePlugin.java#L44

ContextCompat.registerReceiver(
    getContext(),
    broadcastReceiver,
    new IntentFilter(Intent.EXTRA_CHOSEN_COMPONENT),
    ContextCompat.RECEIVER_EXPORTED
);

Because the receiver is exported, any app on the device can broadcast an intent with action Intent.EXTRA_CHOSEN_COMPONENT and overwrite chosenComponent. That value is returned to JS as activityType from Share.share(), so the reported target app can be spoofed.

Impact is low. The field is informational only and nothing else consumes it. Still, static analysis (Veracode) flags it as CWE-926 (Improper Export of Android Application Components), which blocks security sign-off for apps that depend on this plugin.

Expected Behavior

The receiver should only accept the callback from the system chooser. That broadcast is delivered through a PendingIntent created by this app, so it carries the app's own identity and a non-exported receiver will still receive it. The registration should use ContextCompat.RECEIVER_NOT_EXPORTED.

Code Reproduction

Not required to observe the issue; it is visible in the plugin source at the line above. To confirm at runtime, install any second app that sends:

Intent i = new Intent(Intent.EXTRA_CHOSEN_COMPONENT);
i.putExtra(Intent.EXTRA_CHOSEN_COMPONENT, new ComponentName("com.spoofed", "com.spoofed.Activity"));
sendBroadcast(i);

while the Capacitor app is running, then call Share.share() and cancel or complete the share. activityType reflects the spoofed package.

Other Technical Details

Additional Context

Suggested fix, one line:

ContextCompat.RECEIVER_NOT_EXPORTED

If there is concern about OEM or third-party share sheets that fire the broadcast directly rather than through the PendingIntent, an alternative is to keep the receiver exported but include a random nonce as an extra on the PendingIntent and ignore any broadcast that does not echo it back.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions