While developing the Sia Ledger app, I discovered a vulnerability that affected nearly every app for Ledger and Trezor hardware wallets. This vulnerability does not allow an attacker to directly steal your coins, but if you use your wallet with a compromised computer, an attacker can trick you into “locking” your own coins and subsequently demand a ransom in exchange for “unlocking” them.
I disclosed this vulnerability to both Ledger and SatoshiLabs, the companies behind the two most popular hardware wallets. Both companies responded promptly and fixed the vulnerability in their subsequent firmware update. If you own a Ledger device, you need firmware v1.5.5 or above; if you own a Trezor Model T, you need firmware v2.0.9 or above (the Trezor One was never vulnerable).
This post provides a technical description of the vulnerability and my experience disclosing it to Ledger and SatoshiLabs. If you use a hardware wallet, I encourage you to study this vulnerability. Simply owning a hardware wallet does not make you safe; you must understand the threat model as well, and follow proper procedures when using your device.
Hardware wallets are small devices that connect to a traditional computer to perform two cryptocurrency-related tasks: address generation and transaction signing. The appeal of hardware wallets is that these actions can be performed without the wallet’s private keys ever leaving the device. Even if the device is connected to a compromised computer (a computer running arbitrary malware), the keys cannot be stolen.
Of course, this key-segregation property alone does not make hardware wallets fully secure. In most attacks involving private keys, the attacker does not need to actually possess the key; they just need to use the key to sign something on their behalf. For example, malware could instruct a hardware wallet to sign a transaction that sends all of its coins to an attacker-controlled address. This type of attack is typically mitigated by requiring the user to explicitly approve or deny each action. That way, if a compromised computer asks the device to sign a malicious transaction, the user can detect this and deny the request.
But there’s an important implication here: the user needs to know exactly what they’re approving. And they can’t trust the computer to tell them — the computer could be lying. So a hardware wallet needs to have a built-in screen that displays requested actions and their associated data. This leads to a simple rule of thumb for developing a hardware wallet app: start by assuming that you must display everything — every single byte sent to and from the device — and work backwards from there. Compromised computers can lie about what they send and what they receive, so it’s the responsibility of the device (and the user) to detect those lies.
Unfortunately, I discovered that most hardware wallet apps fail to follow this rule of thumb. Specifically, during address generation, they fail to display a crucial piece of information that I refer to as the key index. Most users are not even aware that these key indices exist, much less how important they are, so I will dedicate a section to describing them here.
Most hardware wallets are “HD wallets” that use the BIP32 specification to derive an unbounded number of keys from a master seed and a key derivation path. For our purposes, we can think of the path as an index, allowing us to generate key 0, key 1, key 2, and so on. When you generate your first address, your computer tells the device, “Please derive key 0 and give me the resulting address.” Later, when you want to sign a transaction that spends the coins in that address, your computer will tell the device, “Please derive key 0 and use it to sign this transaction.” Therein lies the rub: the key that you sign with must be the same key that the address was derived from. Consequently, if you do not know the key index for an address, you do not control that address, even if you know the master seed!
If you’re familiar with HD wallets, you might protest that this can’t be correct. After all, the whole point of using a master seed is that you can recover a wallet from just the seed and nothing else, right? There’s no prompt during the recovery process that asks for your key indices: you just enter your seed, and it magically finds all your coins.
What’s really happening under the hood during the recovery process is that the wallet is guessing the key indices — and it can guess very accurately, for two reasons. First, when you originally generated your addresses, the wallet generated them from sequential key indices: when you clicked “New Address” three times, you got addresses 0, 1, and 2. Second, the wallet assumes that you used those addresses in sequential order, i.e. address 0 will appear in the blockchain before address 1. So during the recovery process, the wallet begins by using your seed to generate address 0, and looks for that address in the blockchain. If it finds address 0, it starts looking for address 1 (as well as address 0, since it could appear multiple times). If it finds address 1, it starts looking for addresses 0, 1, and 2. At the end of this process, the wallet will have found all of your address that appear in the blockchain, and will know which key indices were used to generate them.
But there’s a catch: this only works if the “sequential use” assumption holds true. If you generate addresses 0 and 1, but then send coins to address 1 without ever using address 0, this process won’t be able to find address 1. Fortunately, this problem can be alleviated by using a gap limit: instead of looking for just the next sequential address, the wallet looks for the next n sequential addresses. That way, if the addresses appear slightly out-of-order, the wallet will still be able to recognize them. Still, this isn’t perfect: if there’s a gap in address use that exceeds n, it won’t be found. For example, if the only address that you use on the blockchain is address 2,192,562,109, there’s no hope of recovering it through the standard recovery process. But since wallets generate addresses in sequential order and use a reasonable gap limit (20 is the recommended value), this is a highly unlikely scenario, and so in practice the recovery tools work quite well — so well that most people think that their seed is the only piece of information necessary to recover their coins.
As you may have guessed, the attack I discovered involves violating the assumptions made by the recovery process. If we can trick the user into generating an address with a very large key index, then any coins sent that address are as good as gone. The user’s only recourse is to check every possible key index and hope they get a match. But the search space is so large that any attacker worth their salt can make this infeasible.
When you generate an address using your hardware wallet, you can be certain that the address was derived from your master seed. But if the device does not display the key index, you cannot be certain that you will actually be able to spend any coins sent to that address.
So the full attack works like this. The user connects their hardware wallet to a compromised computer and opens a desktop app to communicate with the device. Unbeknownst to them, this app has been replaced by a malicious version that looks identical, but behaves differently. When the user clicks the “New Address” button, the malicious app tells the device, “Please give me the address derived from key 2,192,562,109.” When the user receives coins at this address, they’ll get a nasty surprise: the transaction will be confirmed on the blockchain, but the coins won’t show up in their wallet.
This is an interesting attack, because the coins aren’t being sent directly to the attacker: the address is still “owned” by the user, in the sense that it was derived from their seed. It’s as though the coins are locked in a safe, and the user has the safe, but the attacker has the key. Neither can open the safe without the other’s cooperation. But of course, this situation is clearly to the advantage of the attacker, since the coins weren’t theirs to begin with! The attacker has all the leverage, so they can demand that the user pay a ransom (perhaps 50% of the coins sent to the address) in exchange for the key index. This is highly reminiscent of the CryptoLocker ransomware scam, in which the virus encrypts files on the victim’s computer and demands a ransom in exchange for the decryption key.
After discovering this attack, I contacted Ledger’s security team in late July 2018 to confirm that the Ledger Blue and Ledger Nano S were vulnerable. They replied that they were already aware of the issue, but that they were “still looking for a proper way to counter [the attack] that doesn’t clutter the UX too much.” I found this surprising: shouldn’t a hardware wallet always prioritize security over user-friendliness? Ledger further justified the decision, however, saying that “providing too much information usually leads to random approval clicks,” i.e. security could actually decrease due to users feeling overwhelmed. In keeping with this view, Ledger chose not to use my recommended fix (displaying the key index alongside every address); instead the device displays a warning if the key index falls outside a “normal” range. This limits the attacker to a space of about 5 million keys, which is small enough to brute-force. The fix was committed on November 30th and released on January 16th as part of their v1.5.5 firmware update.
After discussion with Ledger, it occurred to me that Trezor devices might be vulnerable as well. I contacted Trezor’s security team and they confirmed that the Model T was vulnerable. (The Trezor One already displays the key index of each address, so it has never been vulnerable.) Trezor developer Pavol Rusnak immediately opened a GitHub issue regarding the vulnerability, and a fix was included in their next firmware version (v2.0.9), released on November 5th. Their approach, in line with my recommendation, was to simply copy the Trezor One behavior and display the key index for every address generated.
Neither company offered me compensation via their respective bounty programs (granted, I didn’t ask), nor did they mention the vulnerability in their release notes.
Unfortunately, due to the nature of the vulnerability, each wallet app must be patched individually. Although Ledger has fixed their first-party wallet apps, third-party apps may still be vulnerable. (Trezor does not support third-party apps.) I strongly urge the developers of third-party Ledger apps to add safeguards to address this vulnerability. Specifically, apps should immediately release an update that caps key indices at 10,000. This doesn’t prevent the attack, but it drastically shrinks the search space, making it possible to recover any funds locked by an attacker. This is a simple change that can be written and released within a day. In a subsequent release, apps should follow Sia’s example of displaying the key index on the device screen. And for future development, remember the rule of thumb: start by displaying every byte, and work backwards.