Recently, looking at published smart contracts on the Ethereum network, I came across one interesting contract with a vulnerability inside. At first glance, the developer made a mistake in the code and you can get the money of the contract, but if you carefully analyze the logic of the contract, everything looks completely different.
Here is the
address of the contract for those who want to see how it works in the blockchain. And here is its source code:
pragma solidity ^0.4.19; contract NEW_YEARS_GIFT { string message; bool passHasBeenSet = false; address sender; bytes32 public hashPass; function () public payable {} function GetHash(bytes pass) public constant returns(bytes32) { return sha3(pass); } function SetPass(bytes32 hash) public payable { if ((!passHasBeenSet && (msg.value > 1 ether)) || hashPass == 0x0) { hashPass = hash; sender = msg.sender; } } function SetMessage(string _message) public { if (msg.sender == sender) { message = _message; } } function GetGift(bytes pass) external payable returns(string) { if (hashPass == sha3(pass)) { msg.sender.transfer(this.balance); return message; } } function Revoce() public payable { if (msg.sender == sender) { sender.transfer(this.balance); message = ""; } } function PassHasBeenSet(bytes32 hash) public { if (msg.sender == sender && hash == hashPass) { passHasBeenSet = true; } } }
The author of the contract as if hints that he wanted to make a greeting card with money, but he is a useless programmer. The algorithm for this contract is as follows:
- You put money on a contract using the SetPass method, while setting the SHA-3 hash of your password, which is available to the recipient (as romantic).
- You send a message to a recipient using the SetMessage method
- You can also refuse a gift using the Revoce method .
- And the recipient gets the money and the message method GetGift
Isn't it a beauty? Plus, this picture is complemented by three transactions:
')

The first two of these are:
- Publication of the contract
- Calling the SetPass function with a certain hash and replenishing the balance of the contract for 1 Ether.
Note that only one function was called.
The third transaction is a “failed” attempt to hack a contract with a call to the
GetGift method and a random dataset.
And now the actual trap:
Let's take a closer look at the check in the
SetPass method:
function SetPass(bytes32 hash) public payable { if ((!passHasBeenSet && (msg.value > 1 ether)) || hashPass == 0x0) { hashPass = hash; sender = msg.sender; } }
As you can see, it is based on the variable
passHasBeenSet , which must be set using the same method
PassHasBeenSet . This method was not called, therefore the variable is still false. Moreover, this method can be called only by the one who first called the
SetPass method.
That is, theoretically, anyone who calls the
SetPass method, with a balance refill of more than 1 Ether, will become a sender. Moreover, so that no one else would become one, you just need to immediately call the
PassHasBeenSet method or just one of the
Revoce / GetGift methods for withdrawing money.
And everything seems to be logical - you just call these two methods and 1 Ether is yours. But, as we know, in Ethereum there is a front-running attack. Its meaning is as follows: the attacker watches the pool of pending transactions and waits for your transaction. As soon as the transaction associated with the contract appears in the transaction pool, the attacker performs the transaction with a higher gas price. The attacker's attack came the last in the current round, but thanks to the highest price of gas, it will actually be executed before your transaction.
What do you think, what method will perform the attacker? Of course
PassHasBeenSet .
This will give him the opportunity to avoid a change of sender, and in addition, all your money sent out by the
SetPass method
will safely settle in the contract. Well, then he just bring them out.
Be careful when working with blockchains!