-
Notifications
You must be signed in to change notification settings - Fork 1
feature: add ANTI_SIDELOADING present/missing KB entries for Android #290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
10 changes: 10 additions & 0 deletions
10
MOBILE_CLIENT/ANDROID/_HARDENING/ANTI_SIDELOADING_MISSING/description.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
36 changes: 36 additions & 0 deletions
36
MOBILE_CLIENT/ANDROID/_HARDENING/ANTI_SIDELOADING_MISSING/meta.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| ] | ||
| } | ||
| } |
72 changes: 72 additions & 0 deletions
72
MOBILE_CLIENT/ANDROID/_HARDENING/ANTI_SIDELOADING_MISSING/recommendation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
3 changes: 3 additions & 0 deletions
3
MOBILE_CLIENT/ANDROID/_SECURE/ANTI_SIDELOADING_PRESENT/description.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
38
MOBILE_CLIENT/ANDROID/_SECURE/ANTI_SIDELOADING_PRESENT/meta.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
| "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" | ||
| ] | ||
| } | ||
| } | ||
1 change: 1 addition & 0 deletions
1
MOBILE_CLIENT/ANDROID/_SECURE/ANTI_SIDELOADING_PRESENT/recommendation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| The implementation is secure, no recommendation applies. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider setting
security_issuetofalsefor_SECUREentries. A_SECUREfinding indicates the protection is present and the app is properly hardened — this is a positive result, not a security issue. The iOS equivalent_SECUREentry (IPA_ANTI_TAMPERING_DETECTED) uses"security_issue": false, which is semantically more correct. Note: the AndroidDYNAMIC_FRIDA_PROTECTION_PRESENTentry also has"security_issue": true, so this may be an ongoing inconsistency worth clarifying across the repo.