When developers choose to use React Native as a platform for their mobile apps, they think about the benefits of one codebase for two platforms, increased development speed and advantages of TypeScript.
But what about application security? Many articles claim that React Native apps are less secure. In this article, we shed light on React Native apps’ security based on our experience, and explain some risks and threats developers should address to prevent typical mistakes.
- Improper platform usage
- React Native is a leaky abstraction
- Should you trust React Native platform?
- Single point of trust or Single point of failure?
Let's start with the basics. What is React Native?
.jsbundle file, while custom JS engines can have various behaviours.
The Bridge is responsible for communication between native platform and JS engine.
Read further to learn security considerations about each component.
The initial security advice for both native and React Native apps is the same: use native platform features appropriately.
OWASP Mobile Top 10 states that the most common security issue is "Improper Platform Usage" that includes misusing of the platform-specific features like biometric authentication, persistent storage, hardware-backed encryption, WebView components, etc.
Why do these issues arise?
Using platform-specific features requires an understanding of platform's risks and threats, OS functioning, and application architecture. OWASP analysis (Mobile Top 10 and MASVS) reveals that app developers have a foggy notion of each platform security specifics. When it comes to React Native developers, it is assumed that they should be aware of security implications for both platforms, so challenges multiply.
Many mobile applications depend on their backend services, which define the app's behaviour and architecture. Configuring security controls on the mobile-side often requires synchronizing them with the backend.
The collaboration of backend and mobile app developers is key to produce secure mobile applications.
The community around React Native platform provides common modules for platform-specific features. They help React Native developers save precious time by avoiding writing native code. The downside is that the additional abstraction layer distances developers from app internals even further.
We help companies with mobile security engineering and end-to-end encryption. Read how we secured notes in Bear iOS and macOS apps using Themis:
Bear: end-to-end encryption and multi-device data synchronisation for 6M users
Case-study: encrypting data for Apple Design Award-winning application while focusing on performance and usability.
iOS and Android applications have similar architecture, but certain features work very differently under the hood. React Native provides an abstraction layer that leaks details of implementation for each platform.
For example, let's see how we can natively store sensitive data with SecureStore.
SecureStore on iOS
SecureStore uses Keychain to store the data in iOS. Keychain is an encrypted system storage that is persistent across app reinstalls. Keychain supports hardware-backed encryption with Secure Enclave starting with iPhone 5s (A5 chip). It means that the devices, running two latest iOS versions (iOS 13 and 14), support hardware-backed encryption mechanisms. Generally, Keychain gets unlocked (decrypted) when the device is unlocked with a passcode, biometrics, or just by pressing the Home button.
SecureStore on Android
Things are different for Android. SecureStore stores the data in SharedPreferences, providing a way to encrypt it using Android KeyStore. Taking into account a wide variety of Android devices, your app may run on the one that doesn't support a hardware-backed KeyStore. Values stored this way are decrypted on demand (when they are referenced). SharedPreferences storage is not persistent across app reinstalls.
|Data stored encrypted||➕||➕|
|Data persists across app reinstalls||➕||➖|
|Hardware-backed encryption||➕||➕ / ➖|
|Data decrypted only before usage||➖||➕|
Comparison of SecureStore internals: it works differently for iOS and Android.
Such significant differences mean that even if developers use SecureStore to abstract away from the native platform, they will still end up implementing platform-specific security features:
● OWASP MASVS L2 recommends erasing sensitive data from iOS Keychain if the app was reinstalled. This is an out-of-the-box feature for Android.
● Hardware-based key management significantly improves the app's security and prevents common mistakes like storing encryption keys in plist/SharedPreferences. While it's available out-of-the-box for all latest iPhones and iOS versions, Android apps require additional work as hardware-based KeyStore is not guaranteed.
● OWASP MASVS L2 suggests encrypting sensitive data before placing it to Keychain. This introduces an additional Defence-in-Depth layer and allows keeping the data encrypted until it is needed in the app.
This example demonstrates the importance of understanding how the internals of React Native components work with native features, especially when they are related to security controls.
React Native platform is a third-party framework developed by Facebook. When making apps for iOS and Android, developers trust functionality provided by the iOS and Android systems, their libraries, their hardware. Adding the React Native framework means adding another party that should be trusted as well.
React Native on iOS
Who knows what Facebook code is inside The Bridge?
React Native on Android
Facebook becomes a part of supply chain for React Native apps on Android.
When a significant number of applications have the same component, this component has a higher chance of being targeted by an attacker.
Trusting React Native platform and its components (the Bridge, Hermes) means that you understand and accept potential security consequences.
Code below shows how to steal all the data from local storage (AsyncStorage) by exploiting eval-based injection and accessing React Native APIs:
Eval-based injection that steals data from local storage. Source.
Linters and static code analysers (f.e. ESLint) can detect usage of dangerous functions like
eval(), and notify developers.
Struggling with mobile app security?
Get assistance from our engineers.
React Native creators promote the "Learn once, write anywhere" approach. As a result, many developers avoid writing features in native code in favour of React Native. Even if the required feature is not presented in the React Native SDK, it is likely to be already implemented in some community-driven modules.
Therefore, React Native applications tend to contain more than a dozen dependencies. Each dependency can have other dependencies underneath. A couple of dozens is turning into hundreds.
A typical day for React Native app developer.
Supporting such infrastructure requires additional efforts: continuous dependency scanning, security patches, and upgrades. A large number of dependencies increases the risk of supply chain attacks and zero-day vulnerabilities.
Continuous dependency scanning and patching is an essential step in Secure-SDLC and automated security testing of React Native apps. However, developers tend to postpone updating dependencies with potential security issues, because dependencies are often heavily interconnected with each other, and updating one causes updates of others.
Many tools help in automating the update process (f.e. Dependabot), but manual work always remains for modules that can't be updated easily.
If one of the app’s dependencies has a reported security issue and can't be easily patched, consider the following:
● Learn more about the security issue: its scope and risks. It can be irrelevant to the project (f.e. vulnerable code that your app never calls). Seek professional advice if the risks are not clear.
● Document the issue to make the team aware. Even if it is irrelevant to the project, it can become relevant in the future.
● Triage the issue with a security team, and schedule updating or replacing the dependency.
● Book time for the team to deal with security issues: some of them appear all of a sudden and require immediate action, others can be solved during scheduled “security sprints”.
React Native is an open-source platform, which means that sometimes developers fork the original repository and build apps using their own React Native fork. The decision to use a forked repo can introduce new bugs and affect the speed of addressing updates from the original repo.
iOS and Android ecosystems themselves are also updated quite often, and it takes time for React Native platform to support the changes. It takes time for forked repos to pull off changes from the original repo. Then it takes time for dependencies to be updated. And only after all these updates developers can start updating their apps.
If developers want to use new iOS/Android features, they need to wait till React Native and libraries are updated.
When choosing React Native as a platform for mobile applications, accept the risk of delayed updates (compared to pure native apps) and increased MTTD/MTTR, and prepare a remediation strategy.
✔ Trusting React Native framework. While mobile developers have to trust Apple and Google by default, using React Native requires trusting Facebook as well.
✔ Having security expertise on every platform. Implementing security controls requires profound knowledge about every platform: iOS, Android, React Native. Your development team should contain highly experienced React Native engineers with expertise in native security controls. Alternatively, you can hire an external security team with expertise in mobile application security.
✔ Minding the React Native specific vulnerabilities. React Native applications introduce new unique threat vectors that should be mitigated. Investing time into a proper risk assessment exercise, having a Risks and Threats Model (based on things you’ve learnt here) will help your team to visualize the security landscape and simplify security decision making.
✔ React Native apps are still mobile apps. In addition to React Native security specifics, OWASP MASVS and MSTG should be used as a foundation of appropriate security measures in React Native apps.
✔ Accepting timing risks. As React Native apps depend on a React Native platform and typically have numerous dependencies, updating them is not as quick as updating native apps (including security updates). Regular “maintenance and updates” sprints and response to critical issues should be a part of product security strategy.
✔ Managing the dependencies. Establish a process for selecting, researching, updating, and replacing the dependencies. Use automated dependency analyzers and code scanning tools to detect known vulnerabilities and patch dependencies.
Mobile apps written with React Native can be well-protected. It has its own cost and additional risks. To make your app secure, you need to follow the best practices of Secure Software Development Lifecycle, define and address possible risks, plan your security controls, and prepare a remediation strategy. Invest your time in building a threat model for your application as it helps to bring together security and usability of the app.
If you’re struggling with app security, you’re not alone, feel free to drop us a line to get some assistance.
This post is authored by Julia. Say hi to her 👋