Mobile applications are often viewed as safe storage by some developers.
When you look at an application’s compiled code, it seems that it's not readable and no hard-coded values can be found. However, an experienced and motivated pentester will find it really easy to extract such keys and even automate the process of extracting hard-coded values. That's why you should never store or hard-code sensitive keys inside your app.
The problem of considering compiled mobile applications as safe storage is real. This article claims that 0.5% of mobile applications contain AWS API keys which has resulted in the exposure of 100M+ users. That number is for sure bigger than 0.5%, as in this article they only consider AWS API keys. There are much more sensitive keys that should not be stored directly in code and you can find a lot of articles describing such cases. There are also CWE issues describing the subject.
Hard-coded, sensitive data in application binary can always leak and could be used to harm your business. API keys may provide access to third-party services like AWS storage, SMS gateway, payments API, or analytics. While analytics keys don't lead to much risk, leaking any of the other mentioned keys may lead to serious consequences.
Let’s see some examples.
- Keys providing read access for public data from a free API
In such cases, there is no real risk, as API requests are free and the stored data is public. An attacker can also generate such a key for themselves, so there is no reason for them to use your key.
- Keys providing read access to confidential data
One of the best examples here is the AWS API key. It may contain read access permissions, which allows an attacker to download the whole database stored there. As a result, you will leak confidential data and may get a fine for breaking GDPR or other data protection laws.
You can see that many companies received big penalties for violating the GDPR. The biggest fine was €50m.
There are a lot of articles about AWS API key leaks on the internet, e.g. you can read about it here.
- Keys providing access to SMS gateways
Let’s imagine a service in which an attacker could send an SMS using your hard-coded key and subscribe to a spam service that will make them money at your expense. It could result in losing large amounts of money in a short time from your account connected to the SMS gateway key.
- Keys providing access to payment services
Let’s consider an application that has a key that allows sending money to or from a Bitcoin wallet.An attacker could find this key and send all the money out of your account, or even use it as a chain for money laundering.
- Firebase cloud functions server key
From the official documentation:
“Important: Do not include the server key anywhere in your client code. Also, make sure to use only server keys to authorize your app server. Android, iOS, and browser keys are rejected by FCM.”
Using the Firebase cloud functions server key, an attacker could send notifications to every app user. Such notifications could contain malicious links and install malware applications, just as one example.
Do not store these keys in the code.
If you need to communicate with a sensitive, external API, ask the backend to create an endpoint to do that. This endpoint should be authenticated with a user token and implement proper security requirements. In this way, attackers should never get those API keys, and only you will be able to communicate with your backend.
How hard is it to extract such secrets?
It may be really hard, if the app is using paid solutions to obfuscate the code and encrypt every string in the app. But in the end, it will still be possible to get them.
On the other hand, it might be really easy. If the app doesn’t have any obfuscation, an attacker could automate a secret keys search. After creating such a script, an attacker would be able to automatically test all the apps and find a lot of these secret values. They only need a pattern to look for. In a simple scenario, they could create a dictionary of sensitive keywords like key, secret, etc., and look for those keys inside compiled app code or files stored inside the app like plists or xml files. This is simple and only one of many scenarios. Other scripts may be really advanced and cover better-protected secrets. That’s why it is so important to not store secret keys that may harm you.
In order to fully understand that in many cases it is not that hard to get those secrets, I recommend solving the Uncrackable challenges provided by OWASP.
For Android, you can check out my tutorials, where I explain everything from reconnaissance of Android apps to an automated Frida script that finds hidden secrets in Uncrackable Level 1.
You can also check out other tutorials in the OWASP repository.
This section contains a tutorial on how to find a hard-coded secret key inside an iOS app.
Secret stored as variable
A common solution used by many developers is to store a secret key as a property.
private let topSecretKey = "top_secret_key_value"
It is the simplest solution to store a secret key in iOS, but it can be easily extracted using a proper tool, e.g. Hopper Disassembler.
Load your ipa project file to Hopper and select the Str button in the left menu.
Then search for the value of the provided secret.
You can see that it can easily be found, especially when a stored secret has a specific pattern and you can search for strings containing that pattern.
Secret stored in array of Ints
A common solution for storing hard-coded strings is to encrypt and encode them to an array of Ints. In this way, we won’t see them in the str section in Hopper. However, we can execute code of the class that contains it to get its properties using a dynamic analysis tool like Frida.
For ObjC class, the Frida code would look like this:
var instance = ObjC.classes["ClassWithKeys"].alloc().init(); console.log("Class properties: " + instance["- _ivarDescription"]());
It would return us all class properties and methods parsed to String.
Storing these secrets in encrypted form and using another class for decryption would make it a little bit harder to get the secrets, but in the end the app would still need a place where this secret is in the memory in decrypted form.
You cannot make sure that hard-coded keys in your binary file won’t leak. Even if they are stored using some advanced encryption, obfuscations, etc. Very motivated attackers will always be able to extract them.
That’s why the best solution for storing secrets is to not store them at all :)
If you need to communicate with a third-party API, create a separate endpoint on your backend to do that.