Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
The application ran normally after being installed from an unofficial source instead of the store it was published through, without detecting or responding to the sideloaded installation.

A sideloaded build is the typical delivery vector for a repackaged app: an attacker decompiles the original APK, injects malicious code or strips out security controls, re-signs it with their own key, and distributes it through third-party stores, file-sharing sites, or social engineering. Because the app never checks where it was installed from, the tampered copy runs with full functionality.

**Common attack scenarios:**

- **Repackaged malware distribution:** A trojanized clone of the app is published on a third-party store; users who sideload it expose their credentials and data to the attacker.
- **Ad/SDK swapping:** The original APK is rebuilt with the attacker's analytics or ad SDK to hijack revenue and exfiltrate user data.
- **Control stripping:** Root detection, certificate pinning, or license checks are removed from the repackaged build before redistribution.
- **Bypassing store-side protections:** Sideloading skips the publishing channel's malware scanning and integrity guarantees entirely.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"risk_rating": "hardening",
"short_description": "The application does not verify its installation source and runs normally when sideloaded from an unofficial channel.",
"title": "Missing Sideloading Detection",
"privacy_issue": false,
"security_issue": true,
"references": {
"OWASP MASVS - MASVS-RESILIENCE-2": "https://mas.owasp.org/MASVS/controls/MASVS-RESILIENCE-2/",
"Android Developers - PackageManager.getInstallSourceInfo": "https://developer.android.com/reference/android/content/pm/PackageManager",
"Android Developers - Play Integrity API overview": "https://developer.android.com/google/play/integrity/overview",
"Android Developers - Play Install Referrer Library": "https://developer.android.com/google/play/installreferrer"
},
"categories": {
"OWASP_MASVS_RESILIENCE": [
"MSTG_RESILIENCE_3"
],
"OWASP_MASVS_v2_1": [
"MASVS_RESILIENCE_2"
],
"PCI_STANDARDS": [
"REQ_6_2",
"REQ_6_3"
],
"SOC2_CONTROLS": [
"CC_7_1",
"CC_7_2"
],
"HIPAA_CONTROLS": [
"SECURITY212",
"SECURITY213"
],
"OWASP_MOBILE_TOP_10": [
"M7_2024"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
Verify at runtime that the app was installed by an authorized source and respond when it was not. Combine a local installer-package check (cheap, but spoofable) with a server-verified Play Integrity attestation (authoritative).

**Detection approaches:**

1. **Installer package verification:** Query which package installed the app and confirm it matches an allow-list of trusted installers (e.g. the Google Play Store, `com.android.vending`). On API 30+ use `getInstallSourceInfo`; fall back to `getInstallerPackageName` on older releases. A `null` installer indicates a sideloaded `adb install` or a direct APK install.

=== "Kotlin"
```kotlin
private val TRUSTED_INSTALLERS = setOf(
"com.android.vending", // Google Play Store
"com.google.android.feedback"
)

fun isFromTrustedInstaller(context: Context): Boolean {
val pm = context.packageManager
val installer = try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pm.getInstallSourceInfo(context.packageName).installingPackageName
} else {
@Suppress("DEPRECATION")
pm.getInstallerPackageName(context.packageName)
}
} catch (_: PackageManager.NameNotFoundException) {
null
}
return installer in TRUSTED_INSTALLERS
}
```

=== "Java"
```java
private static final Set<String> TRUSTED_INSTALLERS = new HashSet<>(Arrays.asList(
"com.android.vending", "com.google.android.feedback"
));

public static boolean isFromTrustedInstaller(Context context) {
PackageManager pm = context.getPackageManager();
String installer = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
installer = pm.getInstallSourceInfo(context.getPackageName())
.getInstallingPackageName();
} else {
installer = pm.getInstallerPackageName(context.getPackageName());
}
} catch (PackageManager.NameNotFoundException ignored) {
}
return installer != null && TRUSTED_INSTALLERS.contains(installer);
}
```

2. **Play Integrity attestation (authoritative):** The local installer string can be spoofed on a rooted device, so back it with a server-side check. Request an integrity token from the Play Integrity API, send it to your backend, and verify on the server that `appRecognitionVerdict` is `PLAY_RECOGNIZED` and the device meets `MEETS_DEVICE_INTEGRITY`. A repackaged or sideloaded build will not be recognized by Play.

=== "Kotlin"
```kotlin
val manager = IntegrityManagerFactory.create(context)
manager.requestIntegrityToken(
IntegrityTokenRequest.builder()
.setNonce(serverProvidedNonce)
.build()
).addOnSuccessListener { response ->
// Send response.token() to your backend; never trust the verdict on-device.
sendTokenToServer(response.token())
}
```

**Additional hardening:**

- Treat the installer check as a hint and the server-verified Play Integrity verdict as the decision — never gate access on the on-device result alone.
- Implement the installer/source checks in native code (JNI) so the comparison and the allow-list cannot be trivially patched at the Java layer.
- Pair with APK signature verification so a re-signed repackaged build is rejected even if it spoofs the installer string.
- On a failed verdict, fail server-side: refuse to issue session tokens or unlock sensitive features rather than only showing a client-side warning.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The application verified its installation source and detected that it had been sideloaded from an unofficial channel rather than the store it was published through, responding by terminating or displaying a security warning.

This indicates the app performs installer-source verification, raising the bar against repackaged builds distributed through third-party stores or direct APK installs that strip out security controls or inject malicious code.
38 changes: 38 additions & 0 deletions MOBILE_CLIENT/ANDROID/_SECURE/ANTI_SIDELOADING_PRESENT/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"risk_rating": "secure",
"short_description": "The application verifies its installation source and detects when it has been sideloaded from an unofficial channel.",
"title": "Sideloading Detection Implemented",
"privacy_issue": false,
"security_issue": true,
"targeted_by_malware": false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider setting security_issue to false for _SECURE entries. A _SECURE finding indicates the protection is present and the app is properly hardened — this is a positive result, not a security issue. The iOS equivalent _SECURE entry (IPA_ANTI_TAMPERING_DETECTED) uses "security_issue": false, which is semantically more correct. Note: the Android DYNAMIC_FRIDA_PROTECTION_PRESENT entry also has "security_issue": true, so this may be an ongoing inconsistency worth clarifying across the repo.

Suggested change
"targeted_by_malware": false,
"security_issue": false,

"targeted_by_ransomware": false,
"targeted_by_nation_state": false,
"has_public_exploit": false,
"references": {
"OWASP MASVS - MASVS-RESILIENCE-2": "https://mas.owasp.org/MASVS/controls/MASVS-RESILIENCE-2/",
"Android Developers - Play Integrity API overview": "https://developer.android.com/google/play/integrity/overview"
},
"categories": {
"OWASP_MASVS_RESILIENCE": [
"MSTG_RESILIENCE_3"
],
"OWASP_MASVS_v2_1": [
"MASVS_RESILIENCE_2"
],
"PCI_STANDARDS": [
"REQ_6_2",
"REQ_6_3"
],
"SOC2_CONTROLS": [
"CC_7_1",
"CC_7_2"
],
"HIPAA_CONTROLS": [
"SECURITY212",
"SECURITY213"
],
"OWASP_MOBILE_TOP_10": [
"M7_2024"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The implementation is secure, no recommendation applies.
Loading