In April 2021, I uncovered a critical security vulnerability in browser extension-based cryptocurrency wallets, while part of Halborn. This vulnerability, which I have dubbed “DEMONIC” as a play on the word “mnemonic,” allows for the extraction of the mnemonic phrase in clear text from memory, posing a grave threat to the security of countless users’ digital assets. This vulnerability has been assigned CVE-2022-32969.
TL;DR Link to heading
The vulnerability, “DEMONIC,” lies in the fact that the mnemonic phrase remains unencrypted in memory, even when the associated wallet is locked. An attacker who gains access to the victim’s machine with the same privilege level as the wallet’s user can extract the mnemonic phrase from memory without the need for privilege escalation. This issue is compounded by the behavior of Chromium-based browsers and JavaScript’s garbage collection mechanism, which fails to securely erase sensitive data from memory.
References: Link to heading
- Phantom - Keeping Phantom safe from the “Demonic” critical vulnerability
- Decrypt - Ethereum Wallet MetaMask, Solana’s Phantom Patch ‘Demonic’ Security Bug
- CryptoPotato - ‘Demonic’ Vulnerability Affecting Crypto Wallets Patched by Metamask, Brave, Phantom
- Crypto News - Vulnerability Affecting Crypto Wallets
Impact 1: Temporary Exposure Link to heading
In the first scenario, the mnemonic phrase is unencrypted in memory while the browser process remains active. This means that if the wallet has been unlocked at least once during the session, the mnemonic phrase can be extracted even if the wallet is subsequently locked. The vulnerability persists until the browser process is terminated.
Exploitation Requirements:
- Initial Access: The attacker must have access to the victim’s machine with the same privilege level as the wallet’s user. No privilege escalation is required.
- Wallet Use: The wallet must have been unlocked at least once during the browser session.
Risk Level:
- Likelihood: Likely
- Impact: Catastrophic – Massive financial loss due to potential theft of crypto assets.
Impact 2: Permanent Exposure Link to heading
In the second scenario, the mnemonic phrase remains unencrypted in memory and on disk even after the browser is restarted, the machine is rebooted, or the wallet extension is removed. This occurs because Chromium-based browsers save the contents of non-password input fields to disk as part of the “Restore Session” feature, and the use of the history.push()
function in certain libraries exacerbates this issue.
Exploitation Requirements:
- Initial Access: The attacker must have access to the victim’s machine with the same privilege level as the wallet’s user. No privilege escalation is required.
- User Interaction: The user must have viewed their mnemonic phrase using the “Show Secret Recovery Phrase” feature.
Risk Level:
- Likelihood: Very Likely
- Impact: Catastrophic – Persistent exposure could lead to severe financial losses, as the mnemonic phrase can be retrieved even after significant security measures are taken.
Root Cause Link to heading
The root cause of this vulnerability stems from two primary factors:
JavaScript Garbage Collection: JavaScript’s memory management does not allow developers to control when or how memory is cleaned up. Sensitive data, like the mnemonic phrase, may remain in memory even after it is no longer needed, leading to potential exposure.
Chromium Browser Behavior: Chromium-based browsers store non-password input fields in plaintext as part of the “Restore Session” feature. When developers use functions like
history.push()
from libraries like “react-router-dom”, these plain-text input fields, including mnemonic phrases, can be stored on disk, making them vulnerable to extraction even after the browser session ends.
Attack Scenarios Link to heading
Imagine a user who is onboarding onto a browser extension-based cryptocurrency wallet. They are presented with two options: generate a new mnemonic phrase or import an existing one. Both scenarios expose the user to significant risk due to this vulnerability. These scenarios highlight how this vulnerability can be exploited at different stages of a wallet’s usage, exposing users to severe financial risks.
Scenario 1: Generating a New Mnemonic Phrase Link to heading
When the user chooses to generate a new mnemonic phrase, the wallet application creates a new seed phrase, which is stored temporarily in memory. Due to the way JavaScript’s memory management operates, this mnemonic phrase remains in memory unencrypted, even after the wallet’s setup process is complete. The user may later log into the wallet, where they unlock it using their password. During this process, the wallet decrypts the mnemonic phrase to grant access to the user’s assets. This decrypted mnemonic phrase is then left in memory, again unencrypted, creating a persistent risk. If an attacker gains access to the user’s machine, they can extract this sensitive data from memory, leading to potential theft of the user’s assets.
Scenario 2: Importing an Existing Mnemonic Phrase Link to heading
In the second scenario, the user chooses to import an existing mnemonic phrase. They paste their mnemonic into a plaintext input field provided by the wallet during the import process. If the user selects the “Show Secret Recovery Phrase” checkbox to view the seed phrase on-screen, this further exposes the phrase. The real danger occurs when the wallet application uses a function like history.push()
from libraries such as “react-router-dom.” This function pushes the document object, which includes all non-password input fields, onto the browser’s history stack. As a result, the mnemonic phrase is stored on disk in plain text as part of the browser’s “Restore Session” feature. Even after the user completes the import process, closes the browser, or removes the wallet extension, the mnemonic phrase remains vulnerable, stored in plain text on the disk.
Scenario 3: 1-Click RCE Exploit Link to heading
While the initial exploitation of this vulnerability requires an attacker to have a initial access on the user’s machine, the threat level escalates significantly when combined with a Remote Code Execution (RCE) vulnerability in the browser. In a 1-click exploit scenario, an attacker could craft a malicious webpage or email that exploits a browser vulnerability to execute arbitrary code in memory. Once the attacker’s code is running within the browser process, they can utilize the vulnerability described in this report to access and leak the mnemonic phrase directly from memory.
Mitigation Link to heading
To mitigate this vulnerability, the following steps should be taken:
Avoid Sensitive Data in Plaintext Input Fields:
- Ensure that sensitive data is not stored in non-password input fields, especially in situations where the data might persist beyond the current session.
- Replace the use of
history.push()
withhistory.replace()
in browser extensions to prevent sensitive data from being stored on disk.- MetaMask has recently submitted a pull request (#12693) in their repository to enhance navigation handling within their extension. The PR aims to replace the use of
history.push()
withhistory.replace()
in certain scenarios.
- MetaMask has recently submitted a pull request (#12693) in their repository to enhance navigation handling within their extension. The PR aims to replace the use of
Utilize the Web Crypto API:
- When handling sensitive data, it’s recommended to use the built-in browser Web Crypto API. This API provides robust cryptographic operations that are essential for secure key management, encryption, decryption, and other cryptographic needs within your application.
Avoid Serializing Sensitive Values as Strings:
- Although serializing data as strings is convenient and familiar to many JavaScript developers, it poses significant security risks, especially when dealing with credentials or other sensitive information. Instead of serializing these values to strings, developers should opt for more secure data types, such as
TypedArray
. - Once the data is no longer needed, make sure to zero out these buffers to prevent potential leakage. This can be achieved using methods like
TypedArray.prototype.fill(0)
to overwrite the data with zeros, effectively wiping it from memory. - When encoding data, use encoding schemes that are less easily discoverable, such as base64 or base58, to add an additional layer of security.
- Although serializing data as strings is convenient and familiar to many JavaScript developers, it poses significant security risks, especially when dealing with credentials or other sensitive information. Instead of serializing these values to strings, developers should opt for more secure data types, such as
Conclusion Link to heading
While the steps outlined above are crucial in enhancing the security of your application, it’s important to acknowledge that achieving a 100% foolproof solution is nearly impossible. Despite best efforts, there will always be potential vulnerabilities that could be exploited, especially in complex systems where sensitive data is handled frequently.
Ultimately, one of the most effective strategies is obfuscation. By obfuscating sensitive data, you can ensure that even if the data is exposed, it cannot be easily identified or located inside the memory This approach makes it much harder for malicious actors to extract and misuse the information.
Disclaimer: Link to heading
All content provided here is for educational and research purposes only. Any actions or activities related to the material are solely your responsibility. Misuse of this information could result in criminal charges. The author will not be held liable if any individuals face legal consequences for using this material to break the law.