Use of Implicit Intent for Sensitive Communication
Description
Use of Implicit Intent for Sensitive Communication is an Android-specific vulnerability where applications use implicit intents to transmit sensitive data. Unlike explicit intents that specify the exact target component, implicit intents declare only the action to perform, allowing any application registered to handle that action to receive the intent. When sensitive information such as credentials, personal data, or authentication tokens is sent via implicit intents, any installed application with a matching intent filter can intercept this data, potentially leading to data theft or manipulation.
Risk
This vulnerability exposes sensitive data to all applications on the device. Malicious applications can register intent filters matching the implicit intent and intercept sensitive communications. Attackers can steal credentials, session tokens, or personal information transmitted through implicit intents. For intents expecting a response, attackers can return malicious data that the vulnerable application processes without verification. PendingIntents created from implicit intents may be hijacked by malicious applications. The attack is particularly effective because users may have unknowingly installed malicious applications that silently intercept communications.
Solution
Use explicit intents for all sensitive communications by specifying the exact target component. Set the package name using intent.setPackage() for implicit intents that must be used. For responses to implicit intents, validate the source before processing. Use signature-level permissions for sensitive inter-component communication. Consider using LocalBroadcastManager for internal application broadcasts. When creating PendingIntents, use FLAG_IMMUTABLE to prevent modification. Validate all data received from intent responses. Avoid putting sensitive data in intents when possible—use secure storage and pass references instead.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Unauthorized applications can intercept and read sensitive data transmitted through implicit intents. |
| Integrity | Scope: Integrity Modify Application Data - Applications may process responses from untrusted sources, leading to unexpected or unauthorized actions. |
Example Code
Vulnerable Code
// Vulnerable: Sending credentials via implicit intent
public class VulnerableLoginActivity extends Activity {
public void sendLoginRequest(String username, String password) {
// Vulnerable: Implicit intent with sensitive data
Intent intent = new Intent("com.example.USER_LOGIN");
intent.putExtra("username", username);
intent.putExtra("password", password); // Password in intent!
sendBroadcast(intent);
// Any app with matching intent filter receives this!
}
public void createUser(String username, String password) {
// Vulnerable: Broadcast with credentials
Intent intent = new Intent();
intent.setAction("com.example.CreateUser");
intent.putExtra("Username", username);
intent.putExtra("Password", password);
sendBroadcast(intent);
}
}
// Malicious app intercepting the vulnerable broadcast
public class MaliciousReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Intercepts credentials!
String username = intent.getStringExtra("Username");
String password = intent.getStringExtra("Password");
// Send stolen credentials to attacker's server
sendToServer(username, password);
}
}
// Malicious AndroidManifest.xml
// <receiver android:name=".MaliciousReceiver">
// <intent-filter>
// <action android:name="com.example.CreateUser"/>
// </intent-filter>
// </receiver>
// Vulnerable: Implicit intent for sensitive action
public class VulnerablePaymentActivity extends Activity {
public void processPayment(String creditCard, double amount) {
// Vulnerable: Sending payment data via implicit intent
Intent intent = new Intent("com.example.PROCESS_PAYMENT");
intent.putExtra("card_number", creditCard);
intent.putExtra("amount", amount);
startActivityForResult(intent, PAYMENT_REQUEST);
// Any payment-handling app can receive this intent
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PAYMENT_REQUEST && resultCode == RESULT_OK) {
// Vulnerable: Trusting response from unknown source
String transactionId = data.getStringExtra("transaction_id");
markPaymentComplete(transactionId);
}
}
}
// Vulnerable: Creating mutable PendingIntent from implicit intent
public class VulnerablePendingIntent {
public void createNotificationWithAction(Context context) {
// Vulnerable: Implicit intent for PendingIntent
Intent intent = new Intent("com.example.CONFIRM_ACTION");
intent.putExtra("user_token", getUserToken());
// Vulnerable: Mutable PendingIntent
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Malicious app could modify the PendingIntent before it's fired
}
}
// Vulnerable: Sharing sensitive file via implicit intent
public class VulnerableFileShare {
public void shareDocument(Context context, File sensitiveDoc) {
// Vulnerable: Any file handling app receives sensitive file
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_STREAM,
FileProvider.getUriForFile(context, "com.example.provider", sensitiveDoc));
// Grants read permission to receiving app
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
}
}
Fixed Code
// Fixed: Using explicit intent for sensitive communication
public class FixedLoginActivity extends Activity {
public void sendLoginRequest(String username, String password) {
// Fixed: Use explicit intent with specific target
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.example.authservice",
"com.example.authservice.LoginReceiver"
));
intent.putExtra("username", username);
// Fixed: Don't send password in intent - use secure channel
// Instead, store temporarily in secure storage and pass token
String tempToken = SecureStorage.storeTemporarily(password);
intent.putExtra("auth_token", tempToken);
sendBroadcast(intent, "com.example.permission.AUTH_SERVICE");
}
}
// Fixed: Using LocalBroadcastManager for internal communication
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
public class FixedInternalBroadcast {
private LocalBroadcastManager localBroadcastManager;
public void setup(Context context) {
localBroadcastManager = LocalBroadcastManager.getInstance(context);
}
public void createUser(String username, String password) {
// Fixed: Local broadcast only reaches our app's components
Intent intent = new Intent("com.example.CreateUser");
intent.putExtra("Username", username);
intent.putExtra("Password", password);
localBroadcastManager.sendBroadcast(intent);
// Cannot be intercepted by other apps
}
}
// Fixed: Explicit intent for sensitive actions with validation
public class FixedPaymentActivity extends Activity {
private static final String TRUSTED_PAYMENT_PACKAGE = "com.trusted.payment";
public void processPayment(String creditCard, double amount) {
// Fixed: Explicit intent to trusted payment processor
Intent intent = new Intent();
intent.setPackage(TRUSTED_PAYMENT_PACKAGE);
intent.setAction("com.trusted.payment.PROCESS");
// Fixed: Verify package is installed and correct
if (!isPackageValid(TRUSTED_PAYMENT_PACKAGE)) {
showError("Payment processor not available");
return;
}
intent.putExtra("amount", amount);
// Fixed: Tokenize card instead of sending raw number
String cardToken = tokenizeCard(creditCard);
intent.putExtra("card_token", cardToken);
startActivityForResult(intent, PAYMENT_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == PAYMENT_REQUEST && resultCode == RESULT_OK) {
// Fixed: Verify the response came from trusted source
String callingPackage = getCallingPackage();
if (!TRUSTED_PAYMENT_PACKAGE.equals(callingPackage)) {
Log.w(TAG, "Response from untrusted package: " + callingPackage);
return;
}
String transactionId = data.getStringExtra("transaction_id");
// Fixed: Verify transaction with backend before marking complete
verifyAndCompletePayment(transactionId);
}
}
private boolean isPackageValid(String packageName) {
try {
PackageInfo info = getPackageManager().getPackageInfo(packageName, 0);
// Optionally verify signature
return verifyPackageSignature(info);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}
// Fixed: Immutable PendingIntent with explicit target
public class FixedPendingIntent {
public void createNotificationWithAction(Context context) {
// Fixed: Explicit intent with specific component
Intent intent = new Intent(context, ConfirmActionReceiver.class);
intent.setPackage(context.getPackageName());
// Don't put sensitive tokens in intent
intent.putExtra("action_id", "confirm_action");
// Fixed: Use FLAG_IMMUTABLE
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
// Build notification with pendingIntent
}
}
// Fixed: Secure file sharing with chooser and restrictions
public class FixedFileShare {
private static final String TRUSTED_VIEWER_PACKAGE = "com.trusted.docviewer";
public void shareDocumentSecurely(Context context, File sensitiveDoc) {
// Option 1: Explicit intent to trusted app
if (isPackageInstalled(context, TRUSTED_VIEWER_PACKAGE)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setPackage(TRUSTED_VIEWER_PACKAGE);
intent.setDataAndType(
FileProvider.getUriForFile(context, "com.example.provider", sensitiveDoc),
"application/pdf"
);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(intent);
return;
}
// Option 2: Use chooser but warn user
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(
FileProvider.getUriForFile(context, "com.example.provider", sensitiveDoc),
"application/pdf"
);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Show warning about sharing sensitive document
new AlertDialog.Builder(context)
.setTitle("Share Document")
.setMessage("You are about to share a sensitive document. " +
"Only share with apps you trust.")
.setPositiveButton("Continue", (dialog, which) -> {
Intent chooser = Intent.createChooser(intent, "Open with");
context.startActivity(chooser);
})
.setNegativeButton("Cancel", null)
.show();
}
}
// Fixed: Using bound service for sensitive IPC
public class FixedServiceCommunication {
private ISecureService secureService;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
secureService = ISecureService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
secureService = null;
}
};
public void bindToSecureService(Context context) {
// Fixed: Explicit binding to our own service
Intent intent = new Intent(context, SecureDataService.class);
context.bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
public void sendSensitiveData(String data) throws RemoteException {
if (secureService != null) {
// Communication only with our bound service
secureService.processSensitiveData(data);
}
}
}
CVE Examples
- CVE-2022-4903: Android application fails to use FLAG_IMMUTABLE when creating a PendingIntent, allowing intent hijacking.
Related CWEs
- CWE-285: Improper Authorization (parent)
- CWE-668: Exposure of Resource to Wrong Sphere (parent)
- CWE-925: Improper Verification of Intent by Broadcast Receiver (related)
- CWE-926: Improper Export of Android Application Components (related)
References
- MITRE Corporation. "CWE-927: Use of Implicit Intent for Sensitive Communication." https://cwe.mitre.org/data/definitions/927.html
- Android Developers. "Intents and Intent Filters." https://developer.android.com/guide/components/intents-filters
- Android Developers. "Security tips." https://developer.android.com/training/articles/security-tips#IPC