While developing the Bloom dApp, we frequently stumble across noteworthy design considerations of the platforms we are building on. Recently, we noticed several privacy and security issues for in-browser wallets which can expose personal information and trick users into sending false transactions.
This weekend, I posted a tweet about this which gained some momentum on Twitter and Reddit. Dan Finlay, a Metamask core developer, agreed with my analysis and security precautions.
Bloom is committed to working alongside the various open-source projects we build upon, and boosting the overall knowledge and security of the Ethereum ecosystem is good for everyone. I’ve converted my thoughts around this topic into the post below.
In-browser Ethereum Wallets Reveal More Than You May Think
If you are using an in-browser Ethereum wallet (Metamask or Parity) then any website can detect if you are an Ethereum user.
I wouldn’t be surprised if some advertisers are already collecting this information. The information is rather easy to collect and has significant use in terms of targeting cryptocurrency holders.
How does this happen? Metamask and Parity inject a global web3 object into the page. For example, the screenshots below show Metamask and Parity both injecting web3 into Twitter:
Practically Speaking, What is the Risk?
Don’t worry, these websites can’t send transactions on your behalf of course (not without popping up a confirmation window) but do you really want any website owner or advertiser to know that you hold crypto?
Do you want them to know how much ETH you own?
This fact, in the wrong hands, could be a risk. It could increase the volume of ads you see or make your device a target to malicious actors.
Does MetaMask Expose your Wallet?
Even though Metamask is locked by default (meaning public addresses aren’t exposed) it is trivial to listen for a wallet unlock.
When you unlock your Metamask wallet, it unlocks it across all tabs. The gif below is an example app that alerts when it detects a wallet unlock.
Detecting an unlock from an unfocused tab is especially sketchy in my opinion. This means any app can detect when you are in the middle of using your Ethereum wallet.
If the user just unlocked their wallet for another tab then they are probably about to send a transaction. The attacker can detect the unlock, wait 30 seconds, then pop up their own transaction.
The gif below is an example attack where the user is in the middle of using an exchange:
In my opinion, a lot of other small attack opportunities exist as long as web3 is injected into every page. You can tell if someone is specifically a Metamask user by checking web3.currentProvider.isMetamask.
Why not pop up your own Metamask lookalike in the top right?
Regardless of whether the wallet is currently locked, you can use the current network id on web3 to see if the user was last using the testnet or mainnet. If they’re on anything besides mainnet, they are probably a developer and likely have higher than average crypto holdings.
To give Metamask credit: They do have several issues on Github open discussing issues around globally available web3 instance:
- Security audit and team discussion (issue #799)
- Configuration should be per-domain (issue #12)
- Inject Metamask on a per-page basis (issue #537)
- Be signed out per domain until signing in (issue #714)
So What is The Solution?
In my opinion, the best immediate stopgap these extensions should add is something similar to other browser permissions like how we grant access to location information.
I think the Metamask team is looking into doing this long term: https://github.com/MetaMask/metamask-extension/issues/813 …
Potential short term solution:
- Hardcode that only http://metamask.io/whitelist can call invoke some whitelist functionality in the Metamask extension
- dApps should open a popup to http://metamask.io/whitelist?url=example.com. This would be similar to some oAuth user flows.
- Extension asks if you want to give http://example.com access to your wallet.
- From then on, web3 is injected automatically on that domain
Restricting this functionality to their domain means that nothing has to be injected into every page. The popup means any page can really easily initiate the whitelist process without having to write browser vendor specific extension interactions.
Short term solution would then be to add whitelisting as an opt-in feature (so you don’t break existing integrations and make people regret writing their app to depend on Metamask).
Security conscious users can opt-in right away and app developers can plan for deprecation.
Can you protect yourself?
In the meantime, I’d recommend users disable Metamask by default in their browser and then enable it when they want to use it.
Unlike unlocking a wallet, I wasn’t able to detect (from an already open and unfocused tab) when a disabled extension was enabled.
Some UI improvements would also help the transaction spoofing example I gave. The browser should switch to the tab that created the transaction. The confirmation window should probably have a brightly colored banner that says “http://example.com created this transaction.”
Metamask and other browser wallets are in a tough position though with these transaction popups. If a legitimate dApp could put a custom message in the confirmation window then an attacker could mimic it.
Some Final Thoughts:
I also want to clarify that I’m not saying that people should never use these in-browser wallets. Security is especially tough and nuanced when you are a platform for handling money and user identity. Even harder when you are on the development side of early adoption.
In addition, I’d love to see is the ability to use an ENS address as the recipient field in a transaction. For @BloomToken, users are currently interacting with one of two contracts. If our end users saw our ENS address for every transaction then they’d be less likely to fall for a spoof.
In-browser wallets should be unlocked per domain. An advertiser shouldn’t be able to log my ETH address in an unfocused tab just because I want to check on my crypto kitties.
I’d also love to see at least an opt-in setting that locks my wallet again after sending a transaction. If a dApp wants to send multiple transactions without unlocking between each, they can use `web3.createBatch`. Metamask handles this well already.
An attack that sent ERC20 tokens would be effective. Unlike moving ETH (where Metamask would say how much ETH you are sending), a token transaction would just display the gas price. This might be more likely to trick people if they can’t tell what is being sent.