To some, native applications are rudimentary. Why write an application specific to one platform when you can build one that is cross-platform compatible? After all, expanding the user base is one of the most fundamental objectives for software development teams.
Doing this quickly with the current “build apps for any screen” approach is the obvious choice. A single codebase that can be deployed as a mobile, web, desktop, and embedded application cannot get any better from a business point of view.
But let’s take a step back and look at what we are leaving behind. Before jumping on the hybrid mobile apps bandwagon, let’s review the native features that simply work better. In this blog post, we’ll look at the popular Flutter framework to understand the difference. We will then explore how these features can be replicated in hybrid applications, or at least how to minimize the negative effects of the absence of these features.
Backed by hardware components like Secure Enclave or Trusted Execution Environment, native frameworks offer security that includes tightly coupled cryptography. This ensures the confidentiality that sensitive operations require. Further, native apps have direct access to hardware-specific APIs. This, coupled with a strong permission system, allows fine-grained control and isolation between applications and hardware.
Native apps also seamlessly integrate hardware features with platform-specific security features such as biometric authentication. For example, when implementing biometric authentication, the secure approach involves using a cryptographic signature from the keychain as well as wiping that signature upon biometric re-enrollment. This accounts for scenarios in which, for example, a new user enrolls new biometrics on the same device, potentially leading to authentication bypass attacks. Native operating systems like iOS provide a defense against such security implications through options like “setInvalidatedByBiometricEnrollment.” Cross-platform frameworks and their libraries may not follow suit. For example, the Flutter library for biometric authentication, “local_auth,” does not support this option as of today.
Typically, native operating systems are held to higher security standards. Hence, they tend to have better support for the latest protocols and cryptographic algorithms compared to plugins. For example, on Android, communication between applications is performed via tried-and-tested native APIs with additional security options such as the “verifyURL” option. This option verifies the target application before launching a page through an Android Intent, thereby avoiding malicious and unauthorized intents. Flutter’s popular library for handling intents, “android_intent_plus,” may not emulate such features or carry over such APIs, leaving the door open for interapplication attacks.
In general, frameworks backed by native operating systems such as Swift actively patch vulnerabilities, update software, and provide secure alternatives to developers. As part of these updates, APIs considered risky are communicated as deprecated. Developers are encouraged, if not forced, to upgrade to better and safer APIs. Open source libraries of cross-platform frameworks that expose such APIs may not do the same, at least not immediately or until subsequent releases. This creates a tiny but dangerous attack surface.
These native features are theoretically easy to “lift” and expose in cross-platform frameworks. To utilize them, developers need only check if the framework or its open source community offer these APIs via plugins. This usually involves three steps. First, expose the APIs in native code. Second, register these APIs as a plugin. Third, define the plugin’s APIs using the hybrid framework’s code. This creates a bridge between the native and hybrid code. Consequentially, this bridge can make or break the security of the hybrid application.
Let’s take Flutter as an example to understand this bridge. Dart, the language that Flutter employs, does not need the platform’s SDK, per se. Typically, the native operating system uses ViewControllers to display different screens of an application. A Flutter app is hosted in one such ViewController. Flutter provides platform channels that help communicate with this ViewController. Access to native features is made possible through these channels and thus, these channels act as the bridge between native and hybrid code. Every channel has two ends, the receiver and the sender. In this case, the receiver is the Swift or Java code, and the sender is the Dart code. Depending on the functionality needed for an application, such channels may already exist as a Flutter community plugin. Alternatively, one could build it from scratch.
Turns out, a “bad” bridge (i.e., a “bad” channel in the case of Flutter) could cause plenty of insecure effects. Not bad development practices such as unvalidated user input—those scenarios apply the same to native applications as they do to cross-platform applications. But the foundational concepts that form the backbone of cross-platform frameworks diminish the tried-and-tested protection of a native operating system. These concepts bring cross-platform compatibility, but they jeopardize security through loose coupling with every supported platform. Subsequently, sole reliance on cross-platform plugins introduces loopholes.
Despite these challenges, hybrid applications without a doubt win the race due to the ease and speed they offer. But can you also have security? By incorporating additional or alternate measures, a trade-off between ease and security can be achieved.
The ecosystem of cross platform frameworks for mobile development is continually growing, making it difficult to manually ensure the safety and security of these libraries as part of the SDLC. With the latest releases in Black Duck SAST solutions, mobile applications built using native or hybrid languages and frameworks can be automatically assessed against these measures. Checks for API safety, hardcoded secrets, and, IaC misconfigurations, are a core component of the engine that forms the backbone of these solutions.