Background
I am currently trying to finish 15 car rentals this year in order to get the President's Circle membership from Hertz. Since I frequently rent the car, I need to find a gas station with the cheapest gas before I return it everytime. Searching gas prices on Google Maps takes time, so why not get the gas price from each gas station's mobile app and write an API by myself? I could also learn something about API security along the way.
This post is only for study and research purposes. The related party also has been notified regarding this vulnerability.
Initial Analysis
The app I will be working with looks like this.
Sniffing Web Traffic
Setting up the environment
It's time to set up the tools. Personnaly, I prefer to use mitmproxy
over other tools like Charles or etc because it's not
only cross-platform but also has the WireGuard mode which be used to set up the Android device easily.
mitmweb --mode wireguard
By running the command above, mitmproxy
will start and open up a browser window like the following one:
Finding the right request
After connect to the mitmproxy
, we can now open the app and watch the network requests it sends.
As of 2023, most app sends HTTPS requests. Thus, you need to install and trust the SSL certicate provided by mitmproxy. However, based on the version of your device's operating system, you may need to root or jailbreak that device first.
Based on the domain name and HTTP method used, it seems that the app first sets up the analytics and crash report collection services and then make several
GET
request to retrive the gas station data.
The following requests stands out as it looks suspicious and it may contain our gas price data.
If we look its response content, we could even find something called <REDUCTED>Stores.json
.
Since the response content type is listed as application/octet-stream
, we can't know its file type directly.
To detect what kind of file it is, I searched the first few bytes on Wikipedia.
Oh look, it's a zip file!
It looks like the App is downloading a zip archive that include a file called <REDUCTED>Stores.json
.
To figure out its content, I donwloaded that archive and unzipped it.
Here is what I've got in that JSON file:
[
{
"latitude": <REDUCTED>,
"longitude": <REDUCTED>,
"fuelItems": [
{
"description": "Unleaded",
"price": 3.559,
"guaranteePrice": 0,
"displayName": "Unleaded",
"cashPrice": 3.559
},
...
],
...
},
...
]
By now, we can get the data we need.
However, it I try to download multiple times in a short period of time, I get the following response:
<html>
<head>
<META NAME="robots" CONTENT="noindex,nofollow">
<script src="/_Incapsula_Resource?SWJIYLWA=5074a744e2e3d891814e9a2dace20bd4,719d34d31c8e3a6e6fffd425f7e032f3">
</script>
<body>
</body></html>
Ahh, it looks like I have triggered some request rate-limit mechanism and the server refused my request.
Next Steps
There is just one question left: what does 3210XXXX-XXXX-XXXX-XXXX-XXXXXXXXC2F7
mean in that GET
request?
Based on its format, it looks like an UUID. However, according to common API design patterns, it could be the ID of my device, the signature of the request or just the fixed API key.
To figure out what it really means, I need to see the source of the app.
Checking the Source Code
To check out the source code, I need to use a tool called jadx
to open the classes.dex
file stored inside the app's APK file.
After some search, I've found an interesting method signature within a class:
private final native String getApiKey(char[] cArr);
Within that class, it contains the following code:
public final class <REDUCTED>Native {
static {
System.loadLibrary("<REDUCTED>Native");
}
}
There is also few shared object files in the following path:
/lib/${ARCH}/lib<REDUCTED>Native.so
It looks like the app uses Android NDK (Native Development Kit) to hide the implementation of this sensitive method.
To figure out its implementation, we now need to use IDA
software to examinine the shared object files.
After opening the shared object file in IDA
, we can now know how does that method implemented by checking its assembly code.
However, our tool can also generate pseudo-code based on the assembly.
If we also check the data section of the shared object file, we can also find some interesting strings stored in it. It has some URLs and also some really long string.
By cross referencing the Hex View and the pseudo-code, it looks like the API key has been hardcoded in that shard object file.
Based on the name of other methods, the getApiKey()
method seems only checks the current app environment type by comparing the
current app package signature with some hard-coded one and then returns the appropriate API key.
Thus, the function can be rewritten as the following:
public String getApiKey(JNIEnv *env, jobject obj, jcharArray signature) {
if (getDevelopmentModeInt(env, obj, signature) <= 0) {
return "";
} else {
return "3210XXXX-XXXX-XXXX-XXXX-XXXXXXXXC2F7";
}
}
Summary
As of now, we have figured out what endpoint to request to and what parameters we need to make the request in order to get our gas price data.
Happy Hacking!