writeup
AndroGoat Static Analysis: Reading a Deliberately Vulnerable App with MobSF and JADX
Most Android writeups start with a malicious sample. This one starts with a training target. AndroGoat is an app built on purpose to be broken, which makes it ideal for practising Static Application Security Testing (SAST) without touching anything you are not allowed to touch.
The goal here is not to find something new. It is to build the habit that matters in real work: let the scanner point, then go read the code yourself before you believe it.
AndroGoat is a deliberately vulnerable app for learning. Everything below happened in a local lab on a file I downloaded for that purpose.
Tools and scope
The analysis is static only. The APK is never executed.
- Kali Linux as the working environment.
- MobSF for automated manifest, certificate, permission, and code analysis.
- JADX for decompiling and reading the source by hand.
MobSF runs locally:
./run.sh
Then open http://127.0.0.1:8000 and upload the APK through Upload & Analyze.
One honest limitation: I did not build the project from source in Android Studio, because the SDK and emulator images did not fit in the storage I had available. That only removes dynamic testing. Everything in this post comes from the APK itself, which is exactly what an analyst usually receives anyway.
Identifying the target
File : AndroGoat.apk
Size : 6.77 MB
MD5 : 8a38254ba65fa4ad4c6982f69ac72666
SHA1 : 7bd6736e38f9cede5accc5351b2f8d9373687fb9
Package : owasp.sat.agoat
Main activity: owasp.sat.agoat.SplashActivity
Target SDK : 33 Min SDK: 19
Score : 48/100 Trackers: 0/432
Two things are worth noting before any deeper analysis.
Min SDK 19 means the app still supports Android 4.4. Supporting an OS that old drags along weak defaults and disables several modern platform protections.
Trackers 0/432 is a useful contrast. A real malicious app usually carries advertising or analytics SDKs. A clean tracker count is a reminder that a low security score and malicious intent are two different things.
Permissions
| Permission | Level | Why it matters |
|---|---|---|
CAMERA | dangerous | Capture images or video at any time |
READ_EXTERNAL_STORAGE | dangerous | Read shared storage |
WRITE_EXTERNAL_STORAGE | dangerous | Write and delete in shared storage |
INTERNET | normal | Open network sockets |
USE_BIOMETRIC | normal | Biometric prompt |
USE_FINGERPRINT | normal | Deprecated since API 28 |
Three dangerous permissions on a demo app is already a lot. The pairing that should always catch your eye is external storage plus INTERNET: it is the minimum toolkit for reading local files and shipping them somewhere else.
USE_FINGERPRINT being deprecated is a small but real signal. Deprecated
security APIs tend to indicate code that has not been revisited in years.
Signing and certificate
This is where MobSF returns its most severe finding.
- Signed with a debug certificate. A production build must never ship this way. The debug key is shared and well known, so anyone can resign a modified build and it will still look legitimate.
- v1 signature scheme only. This exposes the app to the Janus vulnerability on Android 5.0 through 8.0, where a DEX payload can be prepended to the APK without breaking the v1 signature. The app installs as an update to the real one and keeps its identity and permissions.
The fix is straightforward and belongs in the build pipeline: sign with a real release key and enable the v2 or v3 scheme so the whole archive is covered.
Exported components
Activities: 1/30 Services: 1/1
Receivers : 2/2 Providers: 1/2
Exported components are reachable by other apps on the same device. An exported content provider is the one to check first, because that is how local data leaks to a neighbouring app without any network involvement.
What MobSF flags in code
The automated code analysis groups its findings against CWE and OWASP MASVS:
| Finding | CWE |
|---|---|
| SQL query built from user input | CWE-89 |
| Sensitive information written to logs | CWE-532 |
| Insecure default permissions on files | CWE-276 |
| Insufficiently random values | CWE-330 |
| WebView configured unsafely | CWE-919 |
| Cleartext storage of sensitive data | CWE-312 |
| Certificate validation disabled | CWE-295 |
This list is a map, not a verdict. Scanners match patterns, so they raise issues that are unreachable in practice and miss logic bugs entirely. The next step is the one that actually decides.
Confirming the findings in JADX
SQL injection
In SQLInjectionActivity, the query is assembled by string concatenation:
String qry = "SELECT * FROM users WHERE username='" + username.getText() + "'";
User input becomes part of the query structure, not just its data. Entering
' OR '1'='1 turns the condition into something always true and returns every
row. The same pattern you would exploit on a web target works identically
against a local SQLite database.
The fix is parameter binding, which keeps input as a value:
Cursor c = db.rawQuery(
"SELECT * FROM users WHERE username = ?",
new String[] { username.getText().toString() }
);
Input filtering is not the answer here. Separating code from data is.
Hardcoded sensitive data
In HardCodeActivity:
private final String promoCode = "NEW2019";
A promo code sounds harmless, and in this demo it is. The pattern is not. Anything compiled into an APK is readable, because the APK is just an archive that ships to the user's device. Pulling this string took one decompile and one search. Had it been an API key, a signing secret, or a backend password, the effort would have been identical.
Obfuscation does not solve this either. It raises the reading cost and nothing more. Secrets belong on a server, behind an authenticated request.
Takeaways
- The scanner points, you confirm. MobSF listed the SQL injection, but only JADX showed the concatenation that makes it real. Reporting a finding you have not read in code is how false positives spread.
- Debug certificates and v1 signatures are release problems, not code problems. They are caught by build configuration, not by better coding.
- Nothing shipped inside an APK is secret. Treat every embedded string as public from the moment you build.
- A low score is not proof of malice. AndroGoat scores 48/100 with zero trackers. Judge behaviour, not just the number.
If you want to see the same two tools applied to something that is actually hostile, the fake Pos Indonesia SMS trojan walks through a real OTP forwarding sample.
// RELATED FILES
writeup/3 min read
Static Analysis of a Fake Pos Indonesia APK: an SMS-to-Telegram OTP Forwarding Trojan
writeup/3 min read
PortSwigger XSS Labs: Reflected, Stored, DOM, and Filter Bypass
writeup/4 min read