diff --git a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0023.md b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0023.md index fa4848010dc..87943a31d72 100644 --- a/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0023.md +++ b/knowledge/android/MASVS-PLATFORM/MASTG-KNOW-0023.md @@ -4,8 +4,29 @@ platform: android title: Enforced Updating --- -Starting from Android 5.0 (API level 21), together with the Play Core Library, apps can be forced to be updated. This mechanism is based on using the `AppUpdateManager`. Before that, other mechanisms were used, such as doing http calls to the Google Play Store, which are not as reliable as the APIs of the Play Store might change. Alternatively, Firebase could be used to check for possible forced updates as well (see this [blog](https://medium.com/@sembozdemir/force-your-users-to-update-your-app-with-using-firebase-33f1e0bcec5a "Force users to update the app using Firebase")). -Enforced updating can be really helpful when it comes to public key pinning (see the Testing Network communication for more details) when a pin has to be refreshed due to a certificate/public key rotation. Next, vulnerabilities are easily patched by means of forced updates. +Starting with Android 5.0 (API level 21), developers can implement enforced updates using the Play In‑App Updates API (Play Core). See the official documentation: [In‑App Updates](https://developer.android.com/guide/playcore/in-app-updates "In‑App Updates"). This mechanism is far more reliable than legacy methods such as scraping Play Store pages or calling undocumented endpoints, which are unstable and unsupported. -Please note that newer versions of an application will not fix security issues that are living in the backends to which the app communicates. Allowing an app not to communicate with it might not be enough. Having proper API-lifecycle management is key here. -Similarly, when a user is not forced to update, do not forget to test older versions of your app against your API and/or use proper API versioning. +Enforced updating can be particularly useful for maintaining security when public key pins need to be rotated or when critical vulnerabilities must be patched quickly. Requiring users to install an updated version ensures that old, insecure builds are no longer active in the field. + +Keep in mind that updating the app does not resolve vulnerabilities residing on backend systems. A secure update mechanism should complement proper API and service lifecycle management. Similarly, if users are not forced to update, test older app versions against your backend and apply API versioning and deprecation policies to maintain security and stability across all supported releases. + +## Google Play In‑App Updates API + +The [Play In‑App Updates API](https://developer.android.com/guide/playcore/in-app-updates) (Play Core) is part of the Google Play ecosystem and exposes the [`AppUpdateManager`](https://developer.android.com/reference/com/google/android/play/core/appupdate/AppUpdateManager "AppUpdateManager") class, which lets apps check for available updates and initiate update flows directly within the app. + +The API supports two primary modes: + +- **Immediate updates**, which require the user to update before using the app further. +- **Flexible updates**, which allow users to continue using the app while the update downloads in the background. + +Use `startUpdateFlowForResult(...)` with [`AppUpdateOptions`](https://developer.android.com/reference/com/google/android/play/core/appupdate/AppUpdateOptions "AppUpdateOptions") or an `ActivityResultLauncher`. Typical code paths evaluate [`UpdateAvailability`](https://developer.android.com/reference/com/google/android/play/core/install/model/UpdateAvailability "UpdateAvailability") and select an [`AppUpdateType`](https://developer.android.com/reference/com/google/android/play/core/install/model/AppUpdateType "AppUpdateType") (for example, `IMMEDIATE` vs. `FLEXIBLE`). + +## Custom Backend-Gated Flows + +For **apps distributed outside Google Play**, developers must design custom mechanisms to check for updates, such as querying a self‑hosted update API or leveraging distribution frameworks like Firebase Remote Config to enforce minimum version requirements. See this [blog post](https://medium.com/@sembozdemir/force-your-users-to-update-your-app-with-using-firebase-33f1e0bcec5a "Force users to update with Firebase") for an example of using Firebase for forced updates. + +Practical guidance for backend‑gated flows: + +- Include the app version (for example, `X-App-Version`, `version`, or `build`) in early requests and have the backend return a `minVersion` (or equivalent) policy. +- Enforce the policy on the client by presenting a blocking UI (for example, a non‑dismissible dialog or gating screen) and disabling navigation until the update is completed. +- Consider integrity and tamper resistance: avoid trusting only client‑provided data, sign responses where appropriate, and handle offline scenarios (for example, cache policy with a reasonable TTL and a safe fallback). diff --git a/knowledge/ios/MASVS-PLATFORM/MASTG-KNOW-0074.md b/knowledge/ios/MASVS-PLATFORM/MASTG-KNOW-0074.md index 52163d43e8c..b1787c53053 100644 --- a/knowledge/ios/MASVS-PLATFORM/MASTG-KNOW-0074.md +++ b/knowledge/ios/MASVS-PLATFORM/MASTG-KNOW-0074.md @@ -4,9 +4,20 @@ platform: ios title: Enforced Updating --- -Enforced updating can be helpful when it comes to public key pinning (see the Testing Network communication for more details) when a pin has to be refreshed due to a certificate/public key rotation. Additionally, vulnerabilities are easily patched by means of forced updates. +Enforced updating can be useful for maintaining security when critical components such as public key pins need to be rotated or when severe vulnerabilities must be patched quickly. In these cases, requiring users to install a new version can ensure that old, insecure builds are no longer in use. -The challenge with iOS however, is that Apple does not provide any APIs yet to automate this process, instead, developers will have to create their own mechanism, such as described at various [blogs](https://mobikul.com/show-update-application-latest-version-functionality-ios-app-swift-3/ "Updating version in Swift 3") which boil down to looking up properties of the app using `http://itunes.apple.com/lookup\?id\` or third party libraries, such as [Siren](https://github.com/ArtSabintsev/Siren "Siren") and [react-native-appstore-version-checker](https://www.npmjs.com/package/react-native-appstore-version-checker "Update checker for React"). Most of these implementations will require a certain given version offered by an API or just "latest in the appstore", which means users can be frustrated with having to update the app, even though no business/security need for an update is truly there. +On iOS, however, Apple does not provide a built-in API to enforce updates or to perform in-app update flows similar to Android's Play Core library. The App Store is the only official distribution channel, and Apple's guidelines prohibit applications from downloading or installing code outside it. Developers therefore need to implement their own logic to check for new versions and prompt users accordingly. -Please note that newer versions of an application will not fix security issues that are living in the backends to which the app communicates. Allowing an app not to communicate with it might not be enough. Having proper API-lifecycle management is key here. -Similarly, when a user is not forced to update, do not forget to test older versions of your app against your API and/or use proper API versioning. +The typical approach is to query the [App Store Lookup API](https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI/index.html), for example using the endpoint: + +```txt +https://itunes.apple.com/lookup?bundleId= +``` + +This returns metadata including the latest version number. The app can then compare that value with its current version (`CFBundleShortVersionString`) and, if necessary, prompt the user to update. Apple provides [`SKStoreProductViewController`](https://developer.apple.com/documentation/storekit/skstoreproductviewcontroller) in StoreKit, which allows developers to present the App Store product page directly within the app for a smoother update experience. Alternatively, the app can open the App Store page using its URL. + +Open-source libraries such as [Siren](https://github.com/ArtSabintsev/Siren) (archived on Apr 2, 2025) or [react-native-appstore-version-checker](https://www.npmjs.com/package/react-native-appstore-version-checker) automate these lookups and comparisons. Regardless of the method, update prompts should be used carefully to avoid frustrating users when no real security or functional benefit exists. + +For more details on managing builds and versions, see Apple's [App Store Connect documentation](https://developer.apple.com/help/app-store-connect/manage-builds/upload-builds/). To ensure compliance, review the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/). + +Keep in mind that updating the app does not resolve vulnerabilities that reside on backend systems. A secure update mechanism must be part of a broader API and service lifecycle management strategy. Likewise, if users are not forced to update, test older app versions against your backend and apply proper API versioning and deprecation policies to maintain overall platform security. diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80-1.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80-1.md new file mode 100644 index 00000000000..cf4a4d2f809 --- /dev/null +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80-1.md @@ -0,0 +1,35 @@ +--- +platform: android +title: Testing Enforced Updating +id: MASTG-TEST-0x80-1 +type: [dynamic] +weakness: MASWE-0075 +profiles: [L2] +--- + +## Overview + +This test verifies whether the app enforces an update (@MASTG-KNOW-0023) when directed by the backend. In a backend-gated flow, the app typically sends the current app version (for example, via `BuildConfig.VERSION_NAME`/`BuildConfig.VERSION_CODE`) and receives a response indicating whether the version is supported. Alternatively, the app may use the [Google Play In-App Updates APIs](https://developer.android.com/guide/playcore/in-app-updates) for an immediate update (for example, [`AppUpdateManager`](https://developer.android.com/reference/com/google/android/play/core/appupdate/AppUpdateManager)). + +## Steps + +1. Apply @MASTG-TECH-0011 (MITM) to capture launch traffic and initial API calls. Filter for headers, parameters, or body fields carrying version information (for example, `X-App-Version`, `version`, `build`, `minVersion`). +2. Use @MASTG-TECH-0033 (dynamic instrumentation) to hook relevant classes or methods that retrieve the app version (such as `BuildConfig.VERSION_NAME`) or that are specifically related to update flows (for example, `AppUpdateManager#getAppUpdateInfo`, `AppUpdateManager#startUpdateFlowForResult`, or the code that evaluates `minVersion`). + +## Observation + +The output should contain: + +- a network traffic trace showing version values in requests and corresponding backend responses for different versions +- a method trace showing which APIs were called + +## Evaluation + +The test case fails if the app does not implement enforced updating. For example, if it neither uses the Play In-App Updates API nor performs backend-gated version checks or if it implements them incorrectly. + +**Additional Verification:** + +Validate whether the backend indicates that an update is required but the app still allows you to continue using it (this may require manual testing). For example: + +- Try to dismiss any update prompts or navigate around them. +- Modify requests to present an older version (for example, change `version`/`build`), replay the request, and observe whether the backend response changes (for example, an error or a field indicating an update is required). diff --git a/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80.md b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80.md new file mode 100644 index 00000000000..164eced1ff5 --- /dev/null +++ b/tests-beta/android/MASVS-CODE/MASTG-TEST-0x80.md @@ -0,0 +1,40 @@ +--- +platform: android +title: Testing Enforced Updating +id: MASTG-TEST-0x80 +type: [static] +weakness: MASWE-0075 +profiles: [L2] +--- + +## Overview + +This test verifies whether the app enforces an update (@MASTG-KNOW-0023) when directed by the backend. The app should either send its current version to the backend or retrieve the minimum supported version and prevent usage until the app is updated. + +On Android, enforced updates are commonly implemented using the Google Play In-App Updates API or a custom backend-gated flow that evaluates the app version retrieved via `BuildConfig.VERSION_NAME`/`BuildConfig.VERSION_CODE` (or `PackageInfo` via `PackageManager`). + +Specifically, look for: + +- Google Play In-App Updates classes and methods: `AppUpdateManagerFactory.create`, `AppUpdateManager#getAppUpdateInfo`, `UpdateAvailability.UPDATE_AVAILABLE`, `AppUpdateType.IMMEDIATE`/`FLEXIBLE`, `startUpdateFlowForResult`/`requestUpdateFlow`. +- Version retrieval points: `BuildConfig.VERSION_NAME`, `BuildConfig.VERSION_CODE` (or `PackageInfo` via `PackageManager`). +- Strings like `X-App-Version`, `version`, `minVersion` that may indicate version checks in network requests or other parts of the code. + +## Steps + +1. Apply @MASTG-TECH-0014 (static analysis) and search for Android update/version APIs used before authentication (for example, in `Application.onCreate`, splash/bootstrap flows, or initial `Activity.onCreate`). + +## Observation + +The output should contain a list of code locations where the app retrieves or sends its version (for example, `BuildConfig.VERSION_NAME` or `PackageInfo`) and uses the Google Play In-App Updates APIs (for example, `AppUpdateManager`, `startUpdateFlowForResult`), or evaluates a backend `minVersion` response, along with a call graph snippet showing these checks execute before authentication. + +## Evaluation + +The test case fails if no code paths implement an enforced update before authentication, if the identified logic is not reachable prior to authentication, or if the app displays a mandatory update message but still allows you to continue using the app (for example, dismissing the dialog or navigating around it). + +Note that this evaluation requires manual review of the identified code paths in the reverse engineered code to confirm whether they implement enforced updating correctly. + +For example, you should try to trace the control flow to confirm that version evaluation leads to an enforced update path, such as: + - Immediate update flow (`AppUpdateType.IMMEDIATE`), or + - A custom blocking UI (for example, a full-screen dialog/`Activity` that disables navigation) when backend `minVersion` > current version. + +Alternatively, you can use dynamic analysis (see @MASTG-TEST-0x80-1) to confirm the identified code paths execute before authentication and enforce updating as expected. diff --git a/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-2.md b/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-2.md new file mode 100644 index 00000000000..09e58a28c10 --- /dev/null +++ b/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-2.md @@ -0,0 +1,36 @@ +--- +platform: ios +title: Testing Enforced Updating +id: MASTG-TEST-0x80-2 +type: [dynamic] +weakness: MASWE-0075 +profiles: [L2] +--- + +## Overview + +This test verifies whether the app enforces an update when directed by the backend. On iOS, apps typically read `CFBundleShortVersionString`/`CFBundleVersion` (for example, via `Bundle.main.infoDictionary`), send the version to a backend using [`URLSession`](https://developer.apple.com/documentation/foundation/urlsession "URLSession"), and enforce a minimum supported version returned by the backend. Some apps also query the App Store using the iTunes Search API lookup endpoint (for example, `https://itunes.apple.com/lookup?bundleId=` or `https://itunes.apple.com/lookup?id=`) to retrieve the latest published version from the App Store response (for example, `results[0].version`). If an update is required, the app should block usage and optionally redirect you to the App Store using [`UIApplication.open`](https://developer.apple.com/documentation/uikit/uiapplication/open(_:options:completionhandler:) "UIApplication.open") or present a StoreKit view (for example, [`SKStoreProductViewController`](https://developer.apple.com/documentation/storekit/skstoreproductviewcontroller "SKStoreProductViewController")). + +## Steps + +1. Apply @MASTG-TECH-0063 (MITM) to capture launch traffic and initial API calls. Filter for headers, parameters, or body fields carrying version information (for example, `X-App-Version`, `version`, `build`, `minVersion`). Additionally, look for requests to `https://itunes.apple.com/lookup` with `bundleId` or `id` (and optional `country`) parameters and note fields like `resultCount`, `results[0].version`, and `results[0].trackViewUrl` in the JSON response. +2. Use dynamic instrumentation to hook relevant classes or methods that retrieve the app version (for example, `Bundle.main.infoDictionary["CFBundleShortVersionString"]`/`["CFBundleVersion"]`) or that are specifically related to update flows (for example, `URLSession` request builders/`resume`, the code that evaluates `minVersion`, `UIApplication.open`, or the parsing logic that reads `results[0].version` from the iTunes lookup response). Also, hook any StoreKit presentation code (for example, `SKStoreProductViewController`). + +## Observation + +The output should contain: + +- a network traffic trace showing version values in requests and the corresponding backend responses for different versions, including any requests to `https://itunes.apple.com/lookup` and the parsed fields (for example, `results[0].version`) +- a method trace showing which APIs were called (for example, `URLSession` request execution, version retrieval from `Bundle`, iTunes lookup parsing, and any redirection via `UIApplication.open` or StoreKit) + +## Evaluation + +The test case fails if the app does not implement enforced updating. For example, if it neither performs backend-gated version checks nor blocks usage when the backend (or the iTunes lookup result) requires an update or if it implements these checks incorrectly (see below). + +**Additional Verification:** + +Validate whether the backend indicates that an update is required but the app still allows you to continue using it (this may require manual testing): + +- Try to dismiss any update prompts or navigate around them. +- Modify requests to present an older version (for example, change `version`/`build`), replay the request, and observe whether the backend response changes (for example, an error or a field indicating an update is required). +- If the app uses the iTunes lookup endpoint, stub or replay the lookup response to advertise a higher `results[0].version` and verify that the app enforces the update (for example, by blocking usage or redirecting to the App Store). diff --git a/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-3.md b/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-3.md new file mode 100644 index 00000000000..3153a63efe6 --- /dev/null +++ b/tests-beta/ios/MASVS-CODE/MASVS-TEST-0x80-3.md @@ -0,0 +1,36 @@ +--- +platform: ios +title: Testing Enforced Updating +id: MASTG-TEST-0x80-3 +type: [static] +weakness: MASWE-0075 +profiles: [L2] +--- + +## Overview + +This test verifies whether the app enforces an update (@MASTG-KNOW-0074) when directed by the backend. On iOS, apps typically read `CFBundleShortVersionString`/`CFBundleVersion` (for example, via `Bundle.main.infoDictionary`) and compare against a minimum supported version returned by the backend over `URLSession`. Some apps also query the App Store using the iTunes Search API lookup endpoint (for example, `https://itunes.apple.com/lookup?bundleId=` or `https://itunes.apple.com/lookup?id=`) to retrieve the latest published version from the response (for example, `results[0].version`). If an update is required, the app should block usage and optionally redirect to the App Store (for example, via `UIApplication.open` or a StoreKit product view). + +## Steps + +1. Apply @MASTG-TECH-0066 (static analysis) and examine startup code paths that run before authentication (for example, `AppDelegate.application(_:didFinishLaunchingWithOptions:)`, `SceneDelegate.scene(_:willConnectTo:)`, initial view controllers). +2. Locate version retrieval and comparison logic (for example, reads of `CFBundleShortVersionString`/`CFBundleVersion` and comparisons against a constant or a value from a backend response). +3. Identify `URLSession` calls that: + - send the app version (for example, headers or JSON fields like `version`, `build`), + - request the minimum supported version from the backend, or + - query the iTunes Search API lookup endpoint (`https://itunes.apple.com/lookup`) using `bundleId` or `id` (optionally `country`) and parse fields like `results[0].version` and `results[0].trackViewUrl`. +4. Trace the code that handles an "update required" condition to verify enforced blocking (for example, non-dismissible `UIAlertController`, disabling navigation, or redirection using `UIApplication.shared.open` or a StoreKit product view). + +## Observation + +The output should contain a list of code locations where the app: + +- retrieves its version (`CFBundleShortVersionString`/`CFBundleVersion`) and performs version comparisons, +- makes `URLSession` calls that carry or request version information or query `https://itunes.apple.com/lookup` (including parsing of `results[0].version`), and +- executes the enforced update path (for example, blocking UI or redirection). + +Include a call graph snippet showing that these checks execute before authentication. + +## Evaluation + +The test case fails if no code paths implement an enforced update before authentication, if version checks (including handling of the iTunes lookup result) are present but do not lead to blocking behavior when required, or if the app informs you that you must update but still allows you to continue using it. diff --git a/tests/android/MASVS-CODE/MASTG-TEST-0036.md b/tests/android/MASVS-CODE/MASTG-TEST-0036.md index 41625857ee7..a9f42bb231d 100644 --- a/tests/android/MASVS-CODE/MASTG-TEST-0036.md +++ b/tests/android/MASVS-CODE/MASTG-TEST-0036.md @@ -8,6 +8,9 @@ title: Testing Enforced Updating masvs_v1_levels: - L2 profiles: [L2] +status: deprecated +covered_by: ['MASTG-TEST-0x80', 'MASTG-TEST-0x80-1'] +deprecation_note: New version available in MASTG V2 --- ## Overview diff --git a/tests/ios/MASVS-CODE/MASTG-TEST-0080.md b/tests/ios/MASVS-CODE/MASTG-TEST-0080.md index b30d3572a3c..5d9a9a8a49f 100644 --- a/tests/ios/MASVS-CODE/MASTG-TEST-0080.md +++ b/tests/ios/MASVS-CODE/MASTG-TEST-0080.md @@ -8,6 +8,9 @@ title: Testing Enforced Updating masvs_v1_levels: - L2 profiles: [L2] +status: deprecated +covered_by: ['MASTG-TEST-0x80-2', 'MASTG-TEST-0x80-3'] +deprecation_note: New version available in MASTG V2 --- ## Overview