Copay wallet emptying vulnerability
Copay is an easy-to-use multi-signature bitcoin wallet developed by BitPay to significantly decrease the risk of Bitcoin theft. When users join a Copay wallet, more than one person must sign every transaction. Users communicate peer-to-peer and the signing keys are stored locally. If the computer of one of the members of the wallet is compromised, the bitcoins can not be transferred without the authorization of another copayer. Copay use cases also include groups where the members don’t trust each other.
Coinspect performed a brief review of the Copay source code and discovered a design flaw that can be abused to bypass the multi-party authorization mechanism. Any user capable of submitting transaction proposals to the group of copayers can exploit the vulnerability and steal all the wallet’s bitcoins.
The attack starts with an innocent looking transaction proposal; it is initiated by one of the copayers, the amount is small and the destination Bitcoin address already known and trusted by the group. Everything looks normal; but the proposal is specially crafted to force the signers to involuntarily produce what is analogous to a blank check. Once the transaction has the required signatures, the attacker modifies the destination address and amount before broadcasting it to the Bitcoin network.
A malicious member of the wallet, or an adversary with access to the computer or phone of one, can install malicious code to automatically initiate the attack the next time the user starts a legitimate transaction. In a 2-of-3 signers configuration, the wallet will be emptied as soon as the second copayer accepts the transfer proposal. The multi-signature wallet should make it impossible to transfer all the funds to the thief address after compromising a single device. With this Copay bug, the only extra step for the thief is making a convincing transfer proposal or waiting for the victim to do it.
Watch demo
Technical Details
Bitcoin transactions are cryptographically signed messages that reassign ownership of bitcoins to new users. The transaction inputs are references to the funds from other previous transactions. The outputs determine the new owner of the transferred bitcoins. The outputs will be referenced as inputs in future transactions.
Each input in a transaction must have a signature that unlocks the funds from the prior transaction.
Transactions include a signature for each input and in a multi-signature transaction, each input has multiple signatures. The hash type of a signature determines what fields of the transaction are actually signed by it. Copay is vulnerable because each signer uses the hash type value chosen by the proposal initiator. The default hash type value is SIGHASH_ALL but the code can be modified to set the TxProposal.SIGHASH field to SIGHASH_NONE or SIGHASH_SINGLE. When SIGHASH_NONE is used only the inputs are signed, and consequently, the destination of the funds can be changed by anyone.
The wallet emptying attack can be tested by making the following modifications to the attacker’s Copay code:
1. Change the _TransactionBuilder.prototype.selectUnspent function of the bitcore library to use all the wallet’s inputs in a single transfer. Make fullfill true and this.selectedUtxos (selected inputs) equal to this.utxos (all the inputs).
2. Set opts.SIGHASH value to SIGHASH_NONE (2) in the function Wallet.prototype.createTx
3. Change the transfer proposal serialization code function TxProposal.prototype.toObj to invalidate one of the signatures of the outgoing proposal. This change is necessary to avoid other copayers broadcasting the transaction to the network after completely signing it. The transaction will be rejected by the network because one of the signatures is wrong. The attacker receives the transaction through the peer-to-peer Copay protocol in step 4 and modifies it. The first copayer that tries to broadcast the transaction will see an error notification and a balance of zero few seconds later.
4. Most of the exploit code is in _Wallet.prototype.handleTxProposal where the TxProposal objects from the other copayers are processed. Change the code of this function to detect when the malicious proposal is signed by other copayers, fix the signature broken in step 3 and change the output to transfer all the wallet funds to any address. Broadcast the transaction and the money will be gone.
Coinspect reported the vulnerability to BitPay on July 20th. The Copay team confimed that the bug affected all implementations of Copay Beta version 0.3.2 and started developing a patch. The latest version of Copay (0.4.1), which is now available at copay.io and the Google Play Store, is fixed and all users should update.