Storing sensitive information in memory
MEDIUM | |||
Detection method | DAST HEAPDUMP |
Description
Memory analysis can help developers to identify the sources of several problems that arise during runtime of an application on a device. But such analysis can also be used for gaining access to confidential data. When an application runs on a device, user data or application-specific data can be stored in RAM and not cleared properly after a user exits the system or the application. Since Android stores applications in memory (even after exit) until it is restored, confidential information can stay in memory for an indefinite period of time. A malicious person who finds or steals the device can connect a debugger and upload the memory dump.
Recommendations
Don't store confidential data (such as encryption keys) in RAM for more time than necessary. Clear all variables containing confidential information once they have been used. Avoid using constant objects (such as Android java.lang.String) when creating cryptographic keys or passwords.
Store confidential information in primitive data arrays, such as byte arrays (byte []) and char arrays (char []). This helps to correctly clear confidential information from memory.
There are some methods of clearing memory information, one of them is to overwrite the contents by zeros.
Example: Java
byte[] secret = null;
try{
//get or generate the secret, do work with it, make sure you make no local copies
} finally {
if (null != secret) {
Arrays.fill(secret, (byte) 0);
}
}
Example: Kotlin
val secret: ByteArray? = null
try {
//get or generate the secret, do work with it, make sure you make no local copies
} finally {
if (null != secret) {
Arrays.fill(secret, 0.toByte())
}
}
This problem doesn't have an ideal solution. For example, you can perform additional calculations (such as XOR for data in a dummy buffer), but you won't know if the compiler decided to remove those operations. On the other hand, using data outside the compiler (for example, serializing the data in a temporary file) guarantees that the data will be overwritten, but, obviously, this affects performance.
Moreover, using Arrays.fill to overwrite data might be a bad idea because this method could be intercepted, which is often done by various instruments for analysis. Another problem in the example given above is that the contents are overwritten with zeros only. While ideal would be to overwrite objects containing confidential information with random data or with contents of other variables.
Example: Java
byte[] nonSecret = somePublicString.getBytes("ISO-8859-1");
byte[] secret = null;
try{
//get or generate the secret, do work with it, make sure you make no local copies
} finally {
if (null != secret) {
for (int i = 0; i < secret.length; i++) {
secret[i] = nonSecret[i % nonSecret.length];
}
FileOutputStream out = new FileOutputStream("/dev/null");
out.write(secret);
out.flush();
out.close();
}
}
Example: Kotlin
val nonSecret: ByteArray = somePublicString.getBytes("ISO-8859-1")
val secret: ByteArray? = null
try {
//get or generate the secret, do work with it, make sure you make no local copies
} finally {
if (null != secret) {
for (i in secret.indices) {
secret[i] = nonSecret[i % nonSecret.size]
}
val out = FileOutputStream("/dev/null")
out.write(secret)
out.flush()
out.close()
}
}
Links
- https://mobile-security.gitbook.io/mobile-security-testing-guide/android-testing-guide/0x05d-testing-data-storage
- https://cwe.mitre.org/data/definitions/316.html
- https://www.pentestpartners.com/security-blog/how-to-extract-sensitive-plaintext-data-from-android-memory/
- https://securitygrind.com/dumping-and-analyzing-android-application-memory/
- https://developer.android.com/studio/profile/memory-profiler