DataStore is the new SharedPreferences, old vulns still apply
Neither DataStore nor SharedPreferences should be used to persist sensitive data ... but as we know, Insecure Data Storage is one of the most common vulnerabilities found in mobile applications.
Banner from https://medium.com/huawei-developers/jetpack-datastore-in-a-nutshell-88006b38fa68.
I've been out of the Android development game for a while, so I didn't know that Jetpack's DataStore was being commonly used instead of SharedPreferences:
SharedPreferences comes with several drawbacks: a synchronous API that can appear safe to call on the UI thread, no mechanism for signaling errors, lack of transactional API, and more.
DataStore is a replacement for SharedPreferences that addresses most of these shortcomings.
DataStore includes a fully asynchronous API using Kotlin coroutines and Flow, handles data migration, guarantees data consistency, and handles data corruption. [Source]
Neither DataStore nor SharedPreferences should be used to persist sensitive data ... but as we know, Insecure Data Storage is one of the most common vulnerabilities found in mobile applications.
So, I was auditing an application, and I ended up stumbling upon a app-name.preferences_pb
file under /data/data/app-name/files/datastore/
- I later learned that this is where the files generated by Proto DataStore are saved.
These files are serialized using Google's Protocol Buffers, which means we can use the protoc binary to read them.
protoc --decode_raw < appname.preferences_pb
Here, I found something interesting, a field that specified the maximum number of failed PIN code attempts that the user could perform.
There were definitely other ways to bypass this PIN feature but I wanted to prove a point: I wanted to show developers that trusting a local (unencrypted) file to make security decisions was not a good idea.
So, I thought I would just change the value on the file, and show them how easily we could brute-force all possible combinations.
However, changing this value compromised the ProtoBuf serialization. So, this is the only straightforward solution I've found so far:
- create a new Android application;
- import the DataStore library;
- add all the boilerplate code needed in order to write to the DataStore;
- set all of the same key/value combinations that were on the original (audited) application;
- change the values you want to manipulate;
- run the application;
- copy the DataStore file from the application to the application you want to manipulate:
~ adb pull /data/data/test-app/files/datastore/test-app.preferences_pb modified.preferences_pb
~ adb push modified.preferences_pb /data/data/audited-app/files/datastore/audited-app.preferences_pb
And that's it!
The maximum number of failed PIN code attempts was increased, it was possible to brute-force the PIN code, and my developers learned their lesson :)