Third-Party Package Context Code Execution
ID |
kotlin.android_third_party_package_context_code_execution |
Severity |
high |
Resource |
Injection |
Language |
Kotlin |
Tags |
CWE:470, CWE:829, CWE:94, MASWE-0085, OWASP:2021:A03, PCI-DSS:6.5.1, android, code-injection, dynamic-loading |
Description
Loading and executing code from third-party packages or external sources without proper verification allows attackers to inject arbitrary code that executes with the app’s permissions and security context. Research shows at least 1 in 50 popular Android apps are vulnerable to these attacks.
Rationale
Code injection through third-party packages enables:
-
Arbitrary code execution with app’s permissions
-
Data theft from app’s private storage
-
Account compromise and credential theft
-
Malware installation and propagation
-
Bypass of app security controls
Two main attack vectors exploit unsafe code loading:
-
Package Squatting: Attackers claim unclaimed package names that apps expect to exist
-
Code Injection: Attackers tamper with DEX/APK files loaded from untrusted sources
Using package names or file paths from external input to select code creates unsafe reflection vulnerabilities.
This detector identifies unsafe code loading patterns, such as:
createPackageContext with Dangerous Flags
Using CONTEXT_INCLUDE_CODE plus CONTEXT_IGNORE_SECURITY flags in createPackageContext without verification, and using class loading with that package context allows the execution of potentially malicious code.
// VULNERABLE: Package squatting attack
fun loadPlugin(packageName: String) {
// Loose package name matching
if (packageName.startsWith("com.myapp.plugin.")) {
// FLAW: Dangerous flags without signature verification
val flags = Context.CONTEXT_INCLUDE_CODE or
Context.CONTEXT_IGNORE_SECURITY
val context = createPackageContext(packageName, flags)
val classLoader = context.classLoader// FLAW
// Execute untrusted code
val pluginClass = classLoader.loadClass("$packageName.PluginMain")
pluginClass.newInstance()
}
}
An attacker can create a malicious app com.myapp.plugin.malicious that executes in victim’s context.
DexClassLoader from External Storage
Loading code from world-readable/writable storage:
// VULNERABLE: External storage
fun loadDynamicCode() {
// FLAW: Loading from external storage (attackers can replace file)
val dexPath = File(Environment.getExternalStorageDirectory(), "plugin.apk")
.absolutePath
val optimizedDir = getDir("dex", 0).absolutePath
// FLAW: No integrity verification
val classLoader = DexClassLoader(dexPath, optimizedDir, null, javaClass.classLoader)
val clazz = classLoader.loadClass("com.plugin.Main")
}
PathClassLoader from Network
Downloading and executing code without verification:
// VULNERABLE: Network download without verification
suspend fun loadFromNetwork() {
// FLAW: HTTP download (MITM attack possible)
val url = "http://example.com/plugin.dex"
val dexBytes = downloadFile(url)
// FLAW: No signature or hash verification
val classLoader = InMemoryDexClassLoader(
ByteBuffer.wrap(dexBytes),
javaClass.classLoader
)
}
Remediation
-
Verify Package Signatures
Always verify package signature before loading code:
// SECURE: Signature verification fun loadPlugin(packageName: String) { // Exact package name (not pattern matching) if (packageName == "com.myapp.plugin.official") { // Verify signature matches val pm = packageManager if (pm.checkSignatures(packageName, this.packageName) == PackageManager.SIGNATURE_MATCH) { // Use CONTEXT_RESTRICTED (safer) or verify before CONTEXT_INCLUDE_CODE val context = createPackageContext(packageName, Context.CONTEXT_RESTRICTED) // Safe to use classLoader after verification val classLoader = context.classLoader val pluginClass = classLoader.loadClass("$packageName.PluginMain") } else { throw SecurityException("Package signature mismatch") } } } -
Use Internal Storage Only
Load code from protected internal storage:
// SECURE: Internal storage with verification fun loadDynamicCode() { // Load from internal storage (protected by app sandbox) val dexFile = File(filesDir, "plugin.apk") if (!dexFile.exists()) { return } // Verify file integrity if (!verifyFileHash(dexFile, EXPECTED_HASH)) { throw SecurityException("Plugin integrity check failed") } val optimizedDir = getDir("dex", 0).absolutePath val classLoader = DexClassLoader( dexFile.absolutePath, optimizedDir, null, javaClass.classLoader ) } private fun verifyFileHash(file: File, expectedHash: String): Boolean { val digest = MessageDigest.getInstance("SHA-256") val actualHash = digest.digest(file.readBytes()) .joinToString("") { "%02x".format(it) } return actualHash == expectedHash } -
Implement Digital Signature Verification
// SECURE: Verify code signature private fun verifyCodeSignature(apkFile: File): Boolean { val packageInfo = packageManager.getPackageArchiveInfo( apkFile.absolutePath, PackageManager.GET_SIGNATURES ) packageInfo?.signatures?.forEach { signature -> val md = MessageDigest.getInstance("SHA-256") val certHash = md.digest(signature.toByteArray()) if (certHash.contentEquals(EXPECTED_CERT_HASH)) { return true } } return false } -
Use HTTPS with Certificate Pinning
If loading from network (discouraged by Google Play):
// SECURE: HTTPS with verification suspend fun loadFromNetworkSecurely() { // HTTPS with certificate pinning val url = "https://secure.example.com/plugin.dex" val dexBytes = downloadWithCertificatePinning(url) // Verify digital signature if (!verifyDexSignature(dexBytes)) { throw SecurityException("DEX signature verification failed") } // Save to internal storage as read-only (Android 14+ requirement) val tempFile = File(filesDir, "temp_plugin.dex") tempFile.writeBytes(dexBytes) tempFile.setReadOnly() val classLoader = DexClassLoader( tempFile.absolutePath, getDir("dex", 0).absolutePath, null, javaClass.classLoader ) } -
Prefer Static Alternatives
The best option is to avoid dynamic code loading entirely: Use Android App Bundles for modular features, include all code directly in APK, and use Play Feature Delivery for on-demand modules.
References
-
Android Developer: Dynamic Code Loading Risks
-
Oversecured: Package Squatting Attacks
-
Android Developer: createPackageContext Security Risks
-
OWASP MASWE-0085 : Unsafe Dynamic Code Loading.
-
CWE-94: Improper Control of Generation of Code
-
CWE-829: Inclusion of Functionality from Untrusted Control Sphere
-
CWE-470: Use of Externally-Controlled Input to Select Classes