Training
/
Masterclass
/
React Native

Attack Surfaces Static Analysis in React Native Applications

10
min read
Payatu Research Team
Beginner
Four people working with laptops around a futuristic transparent table at sunset, with an illuminated blue atom-like digital graphic and floating lines of code in the background.

The React Native Pentesting for Android Security Masterclass has taught us how to reverse engineer react native applications by now, so we’ll explore both methods for decompiling APK files and analyzing their structure.

In React Native applications, the “index.android.bundle” file plays a crucial role. It contains all the app’s compiled JavaScript code in a single minified file. This file is generated when the application is built and includes everything from the main components to the various libraries and dependencies used in the app.

As a result, this file can become a significant security concern because it may contain sensitive hardcoded information such as credentials, API keys, or URLs that attackers can easily extract if they decompile the app. Understanding what kind of sensitive information can be stored in this file and how to analyze it is essential for securing React Native apps.

Sensitive information in the “index.android.bundle” file

What is the “index.android.bundle” file?

  • In React Native, App.js acts like Main.java. When React Native apps are compiled into an APK file, the React Native index files and components are converted into JS code via a JS bridge.
  • In React Native applications, all of the Javascript code written in the project gets compiled into the “index.android.bundle” file when the application is built. Thus, this file contains the application’s Javascript code in minified format.
  • When you decompile the React Native apk, the contents of the main ‘App.js’ file and all other components will be bundled in JS format in the “index.android.bundle” file mentioned above. This means the “index.android.bundle” file contains all of the source code of the React Native application. We can search for hardcoded stuff in this file.
Comparison of code structure during development and after compilation into native code: On the left, a file tree shows 'components' folder with JavaScript files Login.js, Payment.js, Tracking.js, UserProfile.js, and an App.js file under ASYNCSTORAGE. On the right, a terminal displays the compiled output location in asynchronousStorage/assets folder with the file index.android.bundle.

Steps:

  • Decompile application using APKtool.
  • Locate “index.android.bundle” file in /assets folder. /<appfolder>/assets/

There is a lot of sensitive information that might be hardcoded in the application component files, which are later compiled into the “index.android.bundle” file. We will look at some of the information that we can find in this file.

1. Hardcoded credentials and tokens:

Poor management of credentials and tokens is a naïve mistake in many Android applications, and the React Native application is no exception. In fact, hardcoding is much higher in React Native than in regular native Java apps, and “index.android.bundle” is a goldmine for hardcoding.

You can search for keywords such as “secrets, tokens, password, apikey, username, login” etc. to find such goofy hidden secrets in the “index.android.bundle” file.

Screenshot of a code snippet in JavaScript showing a function resetError, a variable declaration for fsxToken containing a long string token, and a state object with text, placeholder, and valid properties.

2. Third-party database credentials:

Most React Native applications store information in third-party databases such as Firebase. Numerous instances of hardcoded credentials for these third-party databases have occurred, and many credentials are too permissive within the React Native application.

The following keywords can be used to grab these credentials within the target React Native application:

‍

Code snippet of a JavaScript file showing Firebase configuration including an apiKey, authDomain, databaseURL, and storageBucket, with some sensitive information redacted in red boxes, followed by initialization of Firebase using firebase.initializeApp and module export statements.

3. Hidden backdoors and URLs

Developers tend to add backdoors or URLs in the code for various purposes, such as debugging, shortcuts to the functionality for convenience, etc. Sometimes, they forget to remove those URLs and shortcuts while deploying the code to production. We can scratch through the file to find these hidden URLs and shortcuts.

Screenshot of a code snippet showing a JavaScript object with a property httpServerLocation set to a URL in quotes: https://92.135.43.12/dev/prod-server/, surrounded by other properties such as width, height, scales, hash, name, and type.

The “index.android.bundle” file contains the application’s core code, which can sometimes be huge to analyze. Depending on the type of application, technology, frameworks used in the application, etc., we can be more creative with custom keywords.

Splitting “index.android.bundle” code into multiple JS components

As we saw above, all JS code is crunched into one “index.android.bundle” file. Navigating through this bulk code is a headache. Fortunately, there is a way to break down this bundle code into multiple JS files with the help of the following npm module.

react-native-decompiler

Steps:

1. Install the “react-native-decompiler” module. The installation instructions are in the module URL above.

2. Unzip the contents of the vulnerable application into a folder and go to the “assets” folder.

3. Now open the command prompt in “assets” folder and type the following command:

npx react-native-decompiler -i ./index.android.bundle -o ./output

4. Wait for the process to complete. The “index.android.bundle” file will be decompiled into multiple JS modules in the “output” folder.

Screenshot of a file directory showing JavaScript files named with numbers and the .js extension, arranged in four columns on a black background.

5. Unfortunately, unlike React JS web applications, most React Native Android applications do not generate a source map file. Thus, we have to manually navigate through various application components.

Navigating through multiple decompiled JS modules:

You got decompiled JS files, but things are still messy. Let’s simplify things. We will navigate through these files to reach the right code.

1. Once you decompile the “index.android.bundle” file, you will see multiple .js files in the “output” folder. We can start with the “0.js” file. Consider this file as the main component file of the application. (App.js)

Screenshot showing a dark-themed file explorer window with 20 JavaScript files named sequentially, from 0.js to 404.js, arranged in four columns, each with a green icon resembling a script file.

2. Open this file and check which files are imported at the beginning of the file. It should look like the following:

Code snippet in JavaScript showing ReactNative variable requiring 'react-native' module, module397 assigned by requiring './397', and ReactNative.AppRegistry registering a component from './404' that returns module397.default.

or in case of multiple imported components:

JavaScript code snippet showing ReactNative import and multiple module requires, followed by StatusBar style settings and conditional background color for Android, enforcing a module, and registering a component with a return statement.

3. We can spot these files from the list of multiple js files.

Screenshot of a file explorer window displaying JavaScript files named with numbers, including 0.js, 23.js, 24.js, 25.js, 26.js, 27.js, 28.js, 76.js, 397.js, 398.js, 399.js, 400.js, 401.js, 402.js, 403.js, and 404.js; the file 397.js is highlighted with a red border.

4. Open this file, and you will see the bundled JS code (bundled via webpack).

Code snippet of a JavaScript React function using hooks, async functions, switch cases, and module imports for handling state and saving data with alerts and console logging.

5. We will try to make more sense of this stuff in the dynamic exploitation section.

Decompiling Hermes bytecode

As we saw above, the “index.android.bundle” file contains the core logic of the entire application. Thus, the React Native team created their own JavaScript engine called Hermes. This engine runs React Native applications. The JS source code is often compiled into the Hermes bytecode, somewhat obstructing the JS code.

What is Hermes?

Hermes is an open-source JavaScript engine optimized for React Native. For many apps, enabling Hermes will result in improved start-up time, decreased memory usage, and smaller app size. Refer: https://reactnative.dev/docs/hermes

Thus, when you decompile the React Native application that used Hermes during compilation, the code in the file “index.android.bundle” will be converted into Hermes byte. The contents of the file will look like this:

Two stacked images: The top shows corrupted or scrambled text resembling software code or data. The bottom image features a man outdoors with a concerned expression, standing near a car with an open door, with the caption 'What the hell happened here?'.

Fortunately, there is a way to convert this mess into a human-readable format. Shoutout to *https://github.com/bongtrop* for creating hbctool. This tool lets us disassemble the encrypted bundle files back to the Hermes instruction set, which is in human-readable bytecode.

Pre-requisite:

hbctool: https://github.com/bongtrop/hbctool

Challenge APK: https://github.com/ErbaZZ/hermes-reversing-lab/blob/main/HermesReversingLab.apk

Steps:

1. Decompile APK and go to the “/assets” folder.

2. There you will find the “index.android.bundle” file.

3. Install the hbctool with the following command

pip install hbctool

4. Open the command terminal in the “/assets” folder and type the following command to disassemble Hermes bytecode into human-readable format.

hbctool disasm <index.android.bundle> <output_folder_name>

5. A folder will be created containing disassembled Hermes bytecode. Now go to the output folder (dis_code)

Command prompt window showing directory listing of C:\Users\payatu\Desktop\HermesReversingLab\assets\dis_code with three files: instruction.hasm (6,717,740 bytes), metadata.json (3,049,996 bytes), and string.json (478,952 bytes), along with two directories and free space information.
  • metadata.json: stores the important information of Hermes bytecode file
  • instruction.hasm: stores the application instructions or logic in HASM format (edit application logic in this file)
  • string.json: store the application strings or texts (edit strings in this file)

6. Open the “instructions.hasm” file and analyze instructions sets.

Screenshot of code instructions showing various operations including GetByIdShort, GetById, LoadConstInt, LoadFromEnvironment, LoadConstString, and functions handling strings like 'state', 'counter', 'alert', 'decrypt', and 'onPress' with associated registers and memory addresses in a text editor window.

7. You can find secrets stored in String constants by searching for specific keywords such as password, tokens, secret, apikey, etc.

8. You can use keyword: Oper[1]: String( to grep all of the strings in the bytecode.

Screenshot of a code editor showing lines of assembly-like code with highlighted string values related to an 'Increase button has already been broken' message and multiple string search results below with similar keywords and character data.

Hermes is a custom JavaScript engine created by Facebook. Therefore, the only way to understand this bytecode is to analyze the code patterns.

Grabbing files stored using AsyncStorage

What is AsyncStorage in React Native?

According to the official React native document,

AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.

AsyncStorage is also asynchronous, i.e. its methods run concurrently with your code It is also persistent, meaning that the stored data will always be available globally even if you log out or restart the application.

When does it become a concern?

The data stored via AsyncStorage is unencrypted; thus, it is accessible to anyone with access to the device who can get it in cleartext. If the application stores any credentials of services, user’s session token, passwords, or any other sensitive information via AsyncStorage, then it is easy for an attacker with access to the device to access this data.

Where do these files get stored on the device?

On Android, AsyncStorage will use either SQLite or RocksDB based on availability. You can find the databases in the following locations:

/data/data/<Your-Application-Package-Name>/databases/<your-data-base-name>

How to Test it:

Pre-requisite:

  • Vulnerable app
  • Rooted emulator/physical device
  • ADB

Steps:

1. Install the vulnerable app on the emulator/physical device and ensure data is stored in AsyncStorage.

Mobile screen showing an AsyncStorage Demo app with a pop-up alert saying 'Data is saved' and an OK button, featuring a small Deadpool character above the alert and a displayed encrypted key below.

2. Run the following command to run the ADB daemon as root:

adb root

3. Access the shell of the device via adb shell

4. Navigate to the databases folder of the application

cd /data/data/<com.your.package>/databases</com.your.package>

5. You will find 3 files in this folder. You can check the contents of each file with

Screenshot of a terminal window showing three string entries: RKStorage, RKStorage-wal, and RKStorage-shm. Below, a command listing files with details includes RKStorage, RKStorage-shm, and RKStorage-wal with permissions, ownership, sizes, timestamps from August 13, 2022, and times 12:37.

6. You will be able to see data stored in AsyncStorage in cleartext.

Terminal screen showing command 'strings RKStorage-wal' running in a vbox86p environment, displaying SQLite format 3 database content with tables named android_metadata and catalystLocalStorage. Visible data includes itemList keys, specifically 's3cr3t_data1234' and 'enc_key_AQIPWQR4543B34DDFE'.

Sensitive information in XML files

In Android applications, XML files play important roles in defining layouts of components, storing recurring strings, providing IDs to the assets, etc. Developers sometimes store sensitive information in plaintext in these XML files. We can review these XML files to find the application’s hardcoded secrets.

Strings.xml

While looking for sensitive information in XML files, the “Strings.xml” should be the first place to look. For convenience, developers might include frequently needed sensitive information such as credentials, static tokens, passwords, secrets, and hidden URLs in the Strings.xml file, which can later be referenced within the application. This file can be located in the”/res/values/” folder of the decompiled application.

Screenshot of Android XML strings file showing code for Google Play services enable button, installation messages, Firebase URLs and keys, Google API keys, and UI text strings, with some lines highlighted in yellow and one in red.

AndroidManifest.xml

The “AndroidManifest.xml” file contains information on the application package, including application components such as activities, broadcast receivers, services, content providers, etc. This file may contain sensitive hardcoded strings such as keys, secrets, tokens, etc.

Screenshot of an Android manifest XML file showing service, provider, activity, meta-data elements, and an API key with the android:name 'com.test.API_KEY' and an associated android:value.

Uncovering unencrypted HTTP data in the cache

Why is there HTTP data in the cache folder?

Android applications can keep all kinds of stuff in the package cache folder, which helps boost the app’s performance. However, sometimes, the application may save sensitive information in the cache folder due to misconfiguration, more specifically, in the “HTTP” folder.

In the React Native applications, “http-cache” contains the GET-based HTTP request+response data. This may expose sensitive data if it is being transferred over an unencrypted or insecure channel.

Limitations of data in the http-cache folder:

1. Only “GET” based HTTP request+response data is stored in the cache folder of the application. Post-request data is not cached in the cache folder.

2. Only unencrypted (non-https) requests are cached in plaintext. If the URL has an SSL certificate implemented, then the data will be cached in an encrypted format.

Exploit scenario of http-cache folder:

1. Any GET HTTP request is saved in this folder. If the application sends sensitive data, such as OAuth tokens, credentials, etc., over the GET request type, then we can grab that data in plaintext.

2. Both request and response headers and their values are cached in plaintext. Thus, we can grab that data if the application sends any sensitive information in the request/response headers of a GET request.

How to test it?

1. Open the vulnerable application with the feature to transfer data in HTTP requests and issue some HTTP requests.

2. Open a command prompt and start the ADB server as root with the following command: adb root

3. Now access the shell of the device with adb shell and navigate to the following directory

/data/data/<com.package.name>/cache/http-cache</com.package.name>

4. Open all files in this folder with cat *, and you can now scrap through cached data

Terminal window showing adb shell command to access and display content of secret.json file from http-cache directory, revealing JSON data with user details including ids, emails, first and last names, and avatar URLs from reqres.in, along with server response headers and cache statuses.

Maintaining the security of application code is essential for protecting sensitive information. React Native apps, with their bundled JavaScript code, present unique challenges, but these can be mitigated through careful development practices. By proactively securing sensitive data, developers can significantly reduce the exposure risk and ensure their applications’ integrity.

The upcoming blog of the React Native Pentesting for Android Security Masterclass has some really exciting stuff in store. In it, we will learn how to edit and patch React Native applications.

References: