Data Storage On Android
Data Storage On Android
Protecting authentication tokens, private information, and other sensitive data is key
to mobile security. In this chapter, you will learn about the APIs Android offers for
local data storage and best practices for using them.
The guidelines for saving data can be summarized quite easily: Public data should
be available to everyone, but sensitive and private data must be protected, or, better
yet, kept out of device storage.
This chapter is broken into two sections, the first of which focuses on the theory of
data storage from a security perspective as well as a brief explanation and example
of the various methods of data storage on Android.
The second section focuses on the testing of these data storage solutions through
the usage of test cases that utilize both static and dynamic analysis.
Theory Overview
Storing data is essential to many mobile apps. Conventional wisdom suggests
that as little sensitive data as possible should be stored on permanent local storage.
In most practical scenarios, however, some type of user data must be stored. For
example, asking the user to enter a very complex password every time the app starts
isn't a great idea in terms of usability. Most apps must locally cache some kind of
authentication token to avoid this. Personally identifiable information (PII) and other
types of sensitive data may also be saved if a given scenario calls for it.
Sensitive data is vulnerable when it is not properly protected by the app that is
persistently storing it. The app may be able to store the data in several places, for
example, on the device or on an external SD card. When you're trying to exploit these
kinds of issues, consider that a lot of information may be processed and stored in
different locations.
Next to protecting sensitive data, you need to ensure that data read from any storage
source is validated and possibly sanitized. The validation usually ranges from
checking for the correct data types to using additional cryptographic controls, such
as an HMAC, you can validate the integrity of the data.
Shared Preferences
SQLite Databases
Firebase Databases
Realm Databases
Internal Storage
External Storage
Keystore
In addition to this, there are a number of other functions in Android built for various
use cases that can also result in the storage of data and respectively should also be
tested, such as:
Logging Functions
Android Backups
Processes Memory
Keyboard Caches
Screenshots
It is important to understand each relevant data storage function in order to correctly
perform the appropriate test cases. This overview aims to provide a brief outline of
each of these data storage methods, as well as point testers to further relevant
documentation.
Shared Preferences
The SharedPreferences API is commonly used to permanently save small collections
of key-value pairs. Data stored in a SharedPreferences object is written to a plain-text
XML file. The SharedPreferences object can be declared world-readable (accessible
to all apps) or private. Misuse of the SharedPreferences API can often lead to
exposure of sensitive data. Consider the following example:
editor.putString("username", "administrator");
editor.putString("password", "supersecret");
editor.commit();
editor.putString("username", "administrator")
editor.putString("password", "supersecret")
editor.commit()
Once the activity has been called, the file key.xml will be created with the provided
data. This code violates several best practices.
<map>
<string name="username">administrator</string>
<string name="password">supersecret</string>
</map>
root@hermes:/data/data/sg.vp.owasp_mobile.myfirstapp/shared_prefs # ls -la
Databases
SQLite is an SQL database engine that stores data in .db files. The Android SDK has
built-in support for SQLite databases. The main package used to manage the
databases is android.database.sqlite. For example, you may use the following code to
store sensitive information within an activity:
Example in Java:
SQLiteDatabase notSoSecure = openOrCreateDatabase("privateNotSoSecure",
MODE_PRIVATE, null);
notSoSecure.close();
Example in Kotlin:
notSoSecure.close()
Once the activity has been called, the database file privateNotSoSecure will be created
with the provided data and stored in the clear text file /data/data/<package-
name>/databases/privateNotSoSecure.
The database's directory may contain several files besides the SQLite database:
Journal files: These are temporary files used to implement atomic commit
and rollback.
Lock files: The lock files are part of the locking and journaling feature,
which was designed to improve SQLite concurrency and reduce the writer
starvation problem.
Sensitive information should not be stored in unencrypted SQLite databases.
secureDB.close();
Example in Kotlin:
secureDB.close()
Asking the user to decrypt the database with a PIN or password once the app
is opened (weak passwords and PINs are vulnerable to brute force attacks)
Storing the key on the server and allowing it to be accessed from a web
service only (so that the app can be used only when the device is online)
Firebase is a development platform with more than 15 products, and one of them is
Firebase Real-time Database. It can be leveraged by application developers to store
and sync data with a NoSQL cloud-hosted database. The data is stored as JSON and
is synchronized in real-time to every connected client and also remains available
even when the application goes offline.
https://_firebaseProjectName_.firebaseio.com/.json
The firebaseProjectName can be retrieved from the mobile application by reverse
engineering the application. Alternatively, the analysts can use Firebase Scanner, a
python script that automates the task above as shown below:
Realm Databases
The Realm Database for Java is becoming more and more popular among
developers. The database and its contents can be encrypted with a key stored in the
configuration file.
//the getKey() method either gets the key from the server or from a KeyStore, or
is derived from a password.
.encryptionKey(getKey())
.build();
If the database is not encrypted, you should be able to obtain the data. If the
database is encrypted, determine whether the key is hard-coded in the source or
resources and whether it is stored unprotected in shared preferences or some other
location.
Internal Storage
You can save files to the device's internal storage. Files saved to internal storage are
containerized by default and cannot be accessed by other apps on the device. When
the user uninstalls your app, these files are removed. The following code snippets
would persistently store sensitive data to internal storage.
try {
fos.write(test.getBytes());
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
fos.write(test.toByteArray(Charsets.UTF_8))
fos.close()
You should check the file mode to make sure that only the app can access the file.
You can set this access with MODE_PRIVATE. Modes such as
MODE_WORLD_READABLE (deprecated) and MODE_WORLD_WRITEABLE (deprecated)
may pose a security risk.
Search for the class FileInputStream to find out which files are opened and read
within the app.
External Storage
FileOutputStream fos;
fos.write(password.getBytes());
fos.close();
file.appendText(password)
The file will be created and the data will be stored in a clear text file in external
storage once the activity has been called.
It's also worth knowing that files stored outside the application folder
(data/data/<package-name>/) will not be deleted when the user uninstalls the
application. Finally, it's worth noting that the external storage can be used by an
attacker to allow for arbitrary control of the application in some cases. For more
information: see the blog from Checkpoint.