For task 7 a way to retrieve the Ether victims have already paid and send it back to them is required. The vulnerability that makes this possible is in the Escrow contract; it will pass control to the Ransom contract allowing what is call re-entrancy. Re-Entrancy is when a contract is able to call back into another contract to effect the state, before the transaction has been completed. The contract is written to protect itself from someone using re-entrancy to send themselves all the Ether directly, since it subtracts the amount before transferring the Ether.
Re-entrancy can be used to call other functions and effect the state of the contract in other ways. To see how the contract is vulnerable, the payRansom flow needs to be understood. I will go into brief description, but more detail can be found here.
To decrypt the encryption key an off chain oracle is required, since secrets can’t be kept on the block chain. When the decryptKey function is called, the Escrow contract will emit a DecryptEvent for the oracle to process. The oracle will attempt to decrypt the key and use it to decrypt the file submitted by the victim. Once complete the oracle will call the decryptCallback function, which does not check to see if the amount the victim paid is equal or greater then the ransom amount. The function instead assumes that the amount the victim paid is equal or greater, since the values were checked in the decryptKey function.
Exploit Contract by Re-registering
As mentioned in task 6, the Escrow doesn’t check if a Ransom contract has already been registered. At first it may seem that this is a race condition, since the ransom amount needs to be set to 0 before the decryptCallback function is called. This is actually not the case, since events are emitted when the block has been mined, and not when emitted in the code. This means that after the event has been emitted, but before control is returned to the Escrow contract we can use re-entrancy to effect any part of the contracts state we want.
We can use this vulnerability and re-entrancy to steal all the Escrow contract’s Ether. Below is the flow of the attack.
Below is a simple time line of the transaction, which makes it more clear that the event is emitted after the block is mined.
Causing Decryption to Fail to Receive Ether
There were 3 victims, who each paid 100 Ether, so there is 300 Ether we need to retrieve. There are two methods we can use to get this amount. The first is to re-register the Ransom contract with ransomAmount set to 300, and cause the decryption in the oracle to fail, by giving a bad key or file. This will result in authResult being false and the Escrow contract transferring the ransom amount back to the payer, us.
To check the Escrow contract’s balance the below code can be used.
Below is a video of the attack.
Generating multiple decryptEvents will not have the desired effect, since the decryptCallback function will check if there is a encrypted file saved in encFileMap[id] first. If there is a file there it is removed, otherwise the transaction will fail.
Using Underflow to Call requestRefund
The second method is to notice that escrowMap[id] is set to the amount paid, 0 in this case, and ransomAmount is greater than 0. This will cause an underflow, which is when a unsigned number is subtracted from a smaller unsigned number. This will result in escrowMap[id] being a little less than 2^256, and we can call the requestRefund function to retrieve all the ether in the Escrow contract.
Exploit Contract by Calling requestRefund
If the Escrow contract prevented a Ransom Contract from re-registering, by checking for that victim ID, there is another attack using requestRefund. This requires a little more set up, since calling this function is restricted to the victim address.
The victim address can be set to the Ransom contracts address when the Ransom contract first registers with the Escrow.
Now, instead of calling the register function, requestRefund is called. This will cause a underflow and requestRefund can be used to retrieve all the Ether.