Android attacks – information leakage from file intents

And how to stop them

Daniel Llewellyn
2 min readOct 9, 2021

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 Uri
val 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.

Safe to run on github

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.

--

--

No responses yet