Android Intent Sender Verification

This article is part 6 of 11 in the series Android Development

Following up on the Intent theme from the previous post, here’s another tidbit about Intents: apparently you cannot reliably tell where an Intent is being sent from.

Intents can be launched in various ways:

  1. Intents can be broadcast to any receiver.
  2. Intents can be fired off to a particular receiver, and results will be ignored.
  3. Intents can be fired off to a particular receiver, and the sender gets notified of results.

All three methods have their place. What’s striking is that Intents carry information about the receiver, the action, and tons of optional metadata — but they don’t seem to carry any sender information. As an application developer, then, you have to trust Intents more or less blindly.

I should note that if the sender expects results, then one can find out what that sender is — there’s a function called getCallingPackage() for that. But that function has two major drawbacks:

  1. It’s part of the Activity class, which means senders for broadcast Intents can’t be detected.
  2. It returns null if the sender does not expect a result.

All of which means that, unfortunately, if your Intent is supposed to do something sensitive, there’s no easy way for you to ensure the Intent is not malicious.

Imagine you were to define an Intent that sent your own contact card as a vCard via Bluetooth. A malicious app could fire off that Intent periodically, and a second app could be used to sniff such transfers. Spend enough time on the tube, and you can harvest a fair amount of data1. Clearly security should be built into this Intent system in Android.

There are a few things you can do to deal with the problem; unfortunately, none of them are pretty.

  • You can always ask for confirmation before executing whatever the Intent requests. That’s easy enough to do for non-broadcast Intents, at any rate.
  • You can count Intents received per time unit, and if they’re always the same, start ignoring them — but better ask the user before doing so, just in case they’re legitimate.
  • You can hack up some authentication protocol.

That last suggestion can get tricky. It all hinges on the fact that while you can’t determine an Intent sender, you can verify if an Intent sender is who they claim they are.

Before I go into that, though, let me quickly state that I might be missing an API that would make the following simpler. I’ve looked before, though, and haven’t found anything, so I’m somewhat certain I’m not.

The first thing you pretty much have to do is require that an Intent sender sends their package name as an Intent extra — that alone might be a hurdle for some Intents, so this solution may not be universally applicable. But if you can require that, it’s a start.

Of course any malicious app could send innocent looking package names instead, so we’ll need to verify they’re telling the truth. Unless the Intent is sent from the same process, Binder‘s getCallingUid() should get you the user ID of the calling process.

You can also get the process ID, but unfortunately that doesn’t seem to be resolvable to a package name. However, PackageManager then lets you list all allowed package names for the given UID. At the first level of verification, you can at least ensure that a malicious app is using the same UID as the package they claim to be. With that, you’re pretty much at the point of trusting vendors, rather than apps.

To get to the point of trusting an app, you can now browse the app’s claimed package information until you find a Service or ContentProvider with a pre-defined name of your choosing that the package author must implement. Bind to that, and perform a key exchange handshake via a series of API calls, which will probably be similar in content and the steps performed as the handshake in handshake in TLS.

Following the handshake, the apps can exchange a session-specific token that is attached to all further Intents being sent. This token at least, but probably all Intent data, is signed by the sender and encrypted with the recipients public key. That’ll ensure that only the recipient knows what the Intent data is2, but also that the recipient can find out who the sender is by mapping the token back to the package it contacted for the key exchange.

I told you it’s not pretty, didn’t I?

  1. Yes, it’s a bit random, but the concept can be refined. []
  2. Which may be beyond the scope of authenticating an app; just drop the encryption part if you don’t care. []

  • http://commonsware.com/ Mark Murphy

    “ if your Intent is supposed to do something sensitive, there’s no easy way for you to ensure the Intent is not malicious.” — use permissions.

    • http://www.unwesen.de/ unwesen

      Would be nice if it were that simple.

      Permissions let you define that a receiver requires the sender to have a certain permission, but it’s up to the user to grant that permission.

      As the developer of an app that handles an Intent, you’re then trusting that the user granted this permission to a trustworthy app. It’s true that from your point of view as an app developer that’s “good enough”, but it can still compromise the user’s security if they’re not very careful.

      Android’s permission handling isn’t particularly good. Once an app is installed, it’s impossible to revoke permissions (unless you uninstall the app). It’s impossible to grant permissions selectively to an app.

      It’s also impossible to understand the context in which a permission you’re granting is used – an app might request a permission at install time for perfectly valid-seeming reasons, and then use the same permission for malicious reasons while you’re not watching.

      Permission handling in Android should be better than that. Thanks, though, you’ve given me a topic for another post :)

      • Kacper Nazwisko

        If your applications are signed with the same certificate, you can use permission with:

        android:protectionLevel= ”signature”  

        Then, only apps with the same certificate can communicate with a receiver.

        • http://www.unwesen.de/ unwesen

          Sure, but that works only if you control the certificate for both apps. The whole point of intents is to engender cross-vendor cooperation in apps, though. In that scenario, signature protection is useless.

  • Readercn

    getCallingUid() or 
    getCallingPid() does not work in an Activity or Service, only get right Uid when in ContentProvider, do you notice this?

  • http://twitter.com/SevenPluAndroid Seven+ Android

    Great post! For less-secure transfer mechanisms like APIs for integrating with other applications using a normal level permission is “good enough.” The Binder methods are especially useful if your app is always running and you want to create a simple API. Android provides APIs to know when an Activity is backgrounded and when a process dies. This way you can create an API that gives an app priority only while it is in the foreground and automatically releases itself afterward. Thanks!

    • http://www.unwesen.de/ unwesen

      Thanks!

      Yes, there are other methods. I think the post was originally motivated by reading that OI Safe ends up sending plaintext passwords via Intent.

      With the sort of security that Intents provide (i.e. very little), I can’t find that very reassuring.

  • Rawnsley

    Summary of the story so far: getting the calling package name is critical, and I think this is only possible for Activities started with startActivityForResult() or BOUND Services (this excludes IntentService classes for example).

    Once you have that you still need to establish that the package was created by the expected vendor and is not a side-loaded package from a 3rd party with the same package name. Armed with the package name you can ask the PackageManager for the key that was used to sign it and compare it with the known public key of the vendor. This method avoids token exchange and a dedicated identity API, both of which are vulnerable to reverse engineering. If PackageManager itself has been compromised then all bets are off, but I think the user has bigger problems at that point anyway.