Android attacks – information leakage from file intents
And how to stop them
In the last few weeks, I’ve uncovered a number of issues in apps which I would describe as information leakage of private directory. Here’s what that means, and here’s how to stop it
Intents to information leaking
This can be quite a subtle bug and depends a lot on what your application does. Take an exposed intent that is meant to share data, that is defined like this:
<activity android:name=".intents.ShareFileActivity">
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
And lets say the code is like this:
if (intent.action == Intent.ACTION_SEND) {
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as Urival fileToUse = contentResolver.openInputStream(uri)
?.readBytes()sendFileSomewhere(fileToUse)}
Obviously this depends somewhat on what your ‘send file somewhere’ function does, but one thing you probably don’t want to do (in most cases) is load and send a file from your “private” app directory — which might contain account credentials, databases (etc).
Like this example here, where an attacker was able to upload a users account details:
Fixing the issue
The obvious thing to do, is not allow files in your private directory to be sent, but this is trickier than you might thing. As the above example shows, you need to think about symlinks, as well as directory traversal and a handful of other edge cases.
In this case, we can use a library — safe to run — to help us validate files.
Let’s first add the library to build.gradle (Latest version can be found here: https://github.com/Safetorun/safe_to_run)
implementation "com.safetorun:safetorun:2.1.1"
implementation "com.safetorun:safeToRunCore:2.1.1"
implementation "com.safetorun:inputverification:2.1.1"
Going back to our example before, we can use safe to run to ‘verify’ the URI before we use it.
if (uri.verifyFile(context) { }) {val fileToUse = contentResolver.openInputStream(uri)
?.readBytes()sendFileSomewhere(fileToUse)}
That’s actually it for this example. We can be more permissive than this however. Say we want to allow a particular file
val isFileSafeToOpen = uri.verifyFile(this) {File(context.filesDir, "safe_to_read.txt").allowExactFile()}
Or, if we want to add a directory:
uri.verifyFile(this) {addAllowedParentDirectory(context.filesDir.allowDirectory())}
Including subdirs:
uri.verifyFile(this) {addAllowedParentDirectory(context.filesDir.allowDirectoryAndSubdirectories())
}
Conclusion
Verifying URIs and Files before using them is important to prevent the occurrence of subtle and difficult to track bugs. There are a number of ways which basic file checks can be bypassed, and so using a well-tested library like safe to run as well as using a “default deny” approach is the best way of protecting yourself from these types of attacks.