Improper Verification of Intent by Broadcast Receiver
Description
Improper Verification of Intent by Broadcast Receiver is an Android-specific vulnerability where applications using Broadcast Receivers fail to verify that received Intents originate from authorized sources. While the Android operating system restricts certain implicit system intents (like ACTION_BOOT_COMPLETED) to be sent only by the OS itself, malicious applications can send explicit intents directly targeting a vulnerable application's Broadcast Receiver. Even receivers registered for implicit system intents will also receive explicit intents, potentially causing unintended behavior if the receiver doesn't validate the intent's source.
Risk
This vulnerability allows malicious applications to trigger actions in vulnerable applications by sending crafted intents. Attackers can cause denial of service by triggering shutdown or cleanup procedures. Sensitive operations intended only for system events can be triggered at will. Data may be exfiltrated if receivers process and respond to malicious intents. Application state can be corrupted by unexpected intent handling. In severe cases, attackers might trigger privileged operations that should only occur in response to legitimate system events.
Solution
Always verify the source of received intents in Broadcast Receivers. For receivers that should only respond to system intents, check that the intent is implicit (has no explicit component set). Use signature-level permissions to restrict who can send intents to your receivers. Consider using LocalBroadcastManager for internal application broadcasts. Set android:exported="false" for receivers that don't need to receive external intents. Validate all intent extras before processing. For sensitive operations, implement additional authorization checks beyond just receiving the intent.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Gain Privileges or Assume Identity - Malicious applications can impersonate the operating system and trigger actions intended only for system events. |
| Availability | Scope: Availability DoS: Crash/Exit/Restart - Attackers can trigger shutdown or cleanup procedures, causing denial of service. |
| Integrity | Scope: Integrity Modify Application Data - Unexpected intent handling may corrupt application state or data. |
Example Code
Vulnerable Code
// Vulnerable: BroadcastReceiver without intent verification
public class VulnerableShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Vulnerable: No verification that this is from the system
if (Intent.ACTION_SHUTDOWN.equals(action)) {
// Attacker can trigger this with explicit intent!
performShutdownCleanup(context);
deleteTemporaryFiles(context);
saveStateAndClose(context);
}
}
private void performShutdownCleanup(Context context) {
// Clear caches, save data, etc.
}
}
// AndroidManifest.xml
// <receiver android:name=".VulnerableShutdownReceiver"
// android:exported="true">
// <intent-filter>
// <action android:name="android.intent.action.ACTION_SHUTDOWN"/>
// </intent-filter>
// </receiver>
// Attacker's malicious app
public class AttackerApp {
public void triggerShutdown(Context context) {
// Sending explicit intent to vulnerable receiver
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SHUTDOWN);
intent.setComponent(new ComponentName(
"com.vulnerable.app",
"com.vulnerable.app.VulnerableShutdownReceiver"
));
context.sendBroadcast(intent);
// Vulnerable app performs shutdown cleanup!
}
}
// Vulnerable: Battery monitoring receiver
public class VulnerableBatteryReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Vulnerable: Trusting battery level from any intent
if (Intent.ACTION_BATTERY_LOW.equals(action)) {
// Attacker can trigger low-battery mode
enablePowerSavingMode(context);
disableBackgroundSync(context);
notifyUser("Battery low!");
}
if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
// Attacker can disable power saving
disablePowerSavingMode(context);
}
}
}
// Vulnerable: Package installation receiver
public class VulnerablePackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Vulnerable: No source verification
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Uri data = intent.getData();
String packageName = data.getSchemeSpecificPart();
// Attacker could trigger this with malicious package name
logPackageInstallation(packageName);
grantPermissionsToNewPackage(packageName); // Dangerous!
}
}
}
Fixed Code
// Fixed: BroadcastReceiver with intent source verification
public class FixedShutdownReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Fixed: Verify this is a system broadcast, not explicit intent
if (!isSystemBroadcast(intent)) {
Log.w(TAG, "Rejecting explicit intent attempting to trigger shutdown");
return;
}
String action = intent.getAction();
if (Intent.ACTION_SHUTDOWN.equals(action)) {
performShutdownCleanup(context);
deleteTemporaryFiles(context);
saveStateAndClose(context);
}
}
private boolean isSystemBroadcast(Intent intent) {
// Fixed: Check if this is an implicit broadcast (no explicit target)
// Explicit intents have component set
return intent.getComponent() == null;
}
private void performShutdownCleanup(Context context) {
// Safe to proceed
}
}
// AndroidManifest.xml - Add permission requirement
// <receiver android:name=".FixedShutdownReceiver"
// android:exported="true"
// android:permission="android.permission.SHUTDOWN">
// <intent-filter>
// <action android:name="android.intent.action.ACTION_SHUTDOWN"/>
// </intent-filter>
// </receiver>
// Fixed: Using signature permission for sensitive receivers
// In AndroidManifest.xml:
// <permission
// android:name="com.myapp.permission.SENSITIVE_ACTION"
// android:protectionLevel="signature"/>
//
// <receiver android:name=".SensitiveReceiver"
// android:exported="true"
// android:permission="com.myapp.permission.SENSITIVE_ACTION">
public class FixedSensitiveReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Fixed: Even with explicit intent, only apps signed with
// our key can have this permission
// Additional verification for system intents
if (isSystemAction(intent.getAction())) {
if (!isFromSystem(context, intent)) {
Log.w(TAG, "Ignoring non-system intent for system action");
return;
}
}
handleIntent(context, intent);
}
private boolean isSystemAction(String action) {
return Intent.ACTION_SHUTDOWN.equals(action) ||
Intent.ACTION_BOOT_COMPLETED.equals(action) ||
Intent.ACTION_BATTERY_LOW.equals(action);
}
private boolean isFromSystem(Context context, Intent intent) {
// Implicit broadcasts don't have component set
return intent.getComponent() == null;
}
}
// Fixed: Use LocalBroadcastManager for internal broadcasts
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class FixedInternalCommunication {
private LocalBroadcastManager localBroadcastManager;
private BroadcastReceiver receiver;
public void setup(Context context) {
// Fixed: Local broadcasts cannot be received by other apps
localBroadcastManager = LocalBroadcastManager.getInstance(context);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// Fixed: Only receives intents from within our app
handleInternalMessage(intent);
}
};
IntentFilter filter = new IntentFilter("com.myapp.INTERNAL_MESSAGE");
localBroadcastManager.registerReceiver(receiver, filter);
}
public void sendInternalBroadcast(Intent intent) {
// Fixed: This broadcast stays within the app
localBroadcastManager.sendBroadcast(intent);
}
public void cleanup() {
if (receiver != null) {
localBroadcastManager.unregisterReceiver(receiver);
}
}
}
// Fixed: Comprehensive intent validation
public class FixedBatteryReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Fixed: Multiple validation checks
if (!validateIntent(context, intent)) {
Log.w(TAG, "Invalid intent rejected");
return;
}
String action = intent.getAction();
if (Intent.ACTION_BATTERY_LOW.equals(action)) {
// Additional check: verify battery actually is low
IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, filter);
if (batteryStatus != null) {
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPct = level * 100 / (float) scale;
// Fixed: Only respond if battery is actually low
if (batteryPct < 15) {
enablePowerSavingMode(context);
}
}
}
}
private boolean validateIntent(Context context, Intent intent) {
// Check 1: Must be implicit broadcast
if (intent.getComponent() != null) {
Log.w(TAG, "Rejecting explicit intent");
return false;
}
// Check 2: Verify action is expected
String action = intent.getAction();
if (!Intent.ACTION_BATTERY_LOW.equals(action) &&
!Intent.ACTION_BATTERY_OKAY.equals(action)) {
return false;
}
// Check 3: Additional security checks as needed
return true;
}
}
// Fixed: Package receiver with proper validation
public class FixedPackageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Fixed: Verify system broadcast
if (intent.getComponent() != null) {
Log.w(TAG, "Rejecting explicit package intent");
return;
}
String action = intent.getAction();
if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
Uri data = intent.getData();
if (data == null) {
return;
}
String packageName = data.getSchemeSpecificPart();
// Fixed: Validate package actually exists
try {
PackageManager pm = context.getPackageManager();
PackageInfo info = pm.getPackageInfo(packageName, 0);
// Fixed: Only log, don't grant permissions
logPackageInstallation(packageName, info);
// Granting permissions should require explicit user action
// Not automatic based on broadcast
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Package not found: " + packageName);
}
}
}
}
// AndroidManifest.xml
// <receiver android:name=".FixedPackageReceiver"
// android:exported="false"> <!-- Not exported if possible -->
// <intent-filter>
// <action android:name="android.intent.action.PACKAGE_ADDED"/>
// <data android:scheme="package"/>
// </intent-filter>
// </receiver>
Related CWEs
- CWE-940: Improper Verification of Source of a Communication Channel (parent)
- CWE-926: Improper Export of Android Application Components (related)
- CWE-927: Use of Implicit Intent for Sensitive Communication (related)
References
- MITRE Corporation. "CWE-925: Improper Verification of Intent by Broadcast Receiver." https://cwe.mitre.org/data/definitions/925.html
- Android Developers. "Broadcasts overview." https://developer.android.com/guide/components/broadcasts
- Android Developers. "Security tips." https://developer.android.com/training/articles/security-tips