Solidity Security By Example #08: Unexpected Ether With Forcibly Sending Ether
Originally published in Valix Consulting’s Medium.
Smart contract security is one of the biggest impediments to the mass adoption of the blockchain. For this reason, we are proud to present this series of articles regarding Solidity smart contract security to educate and improve the knowledge in this domain to the public.
Forcibly sending ether is an attacker’s technique to manipulate a target contract balance. This article will describe how a smart contract relying on improper balance checking can be attacked and how to avoid the issue. Enjoy reading. 😊
You can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/08_unexpected_ether_with_forcibly_sending_ether.
Disclaimer:
The smart contracts in this article are used to demonstrate vulnerability issues only. Some contracts are vulnerable, some are simplified for minimal, some contain malicious code. Hence, do not use the source code in this article in your production.
Nonetheless, feel free to contact Valix Consulting for your smart contract consulting and auditing services. 🕵
Table of Contents
The Vulnerability
The following code exhibits the InsecureMoonToken
contract. The MOON is a non-divisible token with zero token decimals (line 12). Users can buy, sell, or transfer 1, 2, 3, or 46 MOONs but not 33.5 MOONs.
Besides the non-divisible characteristic, the MOON token is also a stablecoin pegged with the price of the ETH token (line 6). In other words, 1 MOON will always be worth 1 ETH.
Assuredly, the InsecureMoonToken
contract is vulnerable. Can you catch up on the issue? 👀
|
|
In the InsecureMoonToken
contract, users can buy MOON tokens with the corresponding number of Ethers via the buy
function (lines 16–24). Users can also sell their MOONs through the sell
function (lines 26–36), transfer their MOONs via the transfer
function (lines 38–44), get their balances by consulting the getUserBalance
function (lines 50–52), and get the total number of Ethers locked in the contract by way of the getEtherBalance
function (lines 46–48).
As you can see, the InsecureMoonToken
contract is straightforward. However, the contract got an improper balance assertion issue in line 35 in the sell
function.
Specifically, the sell
function hires the assert(getEtherBalance() == totalSupply * TOKEN_PRICE);
statement to strictly assert that the Ether balance of the InsecureMoonToken
contract (i.e., the getEtherBalance()
part) must always be equal to the total supply of the MOON token (i.e., the totalSupply * TOKEN_PRICE
part). This assertion ensures that the number of locked Ethers balances the MOON total supply.
Nevertheless, relying on the contract’s Ether balance as the sell
function did is prone to attack. Consider if an attacker can send some small Ethers to lock into the InsecureMoonToken
contract. What would happen? 🤔
Gotcha! 😱 The assertion statement would always be evaluated as false because the contract’s Ether balance would no longer match the MOON token’s total supply. This results in reverting all sell
transactions.
Since the InsecureMoonToken
contract does not implement the receive
or fallback
function, the contract regularly cannot receive any Ethers. But how can the attacker send Ethers into the contract? Figure 1 below illustrates the solution the attacker adopts to achieve the exploitation.
In Solidity, a special function named selfdestruct
is used for removing the bytecode from the contract address executing it. Besides the contract bytecode removal, one side effect is that the Ethers stored in the removing contract would be forcibly sent to any specified address.
The selfdestruct
function can forcibly send Ethers to even the contract that does not implement the receive
or fallback
function like the InsecureMoonToken
contract. 🤢
This way, if the attacker deploys and executes the Attack
contract containing the selfdestruct
function, they can forcibly send Ethers to the InsecureMoonToken
contract by specifying the InsecureMoonToken
contract address as the argument of the selfdestruct
function (i.e., selfdestruct(InsecureMoonToken)
). 😬
The Attack
The following code presents the Attack
contract that can be used to exploit the InsecureMoonToken
contract.
|
|
To attack the InsecureMoonToken
, an attacker performs the attack steps as follows.
-
Deploy the
Attack
contract as well as specifying theInsecureMoonToken
contract address as the contract deployment argument (line 6) -
Invoke the
Attack.attack()
function along with supplying some Ethers for attacking
After step 2, the supplied Ethers would be forcibly sent into the InsecureMoonToken
contract by way of the selfdestruct
function (line 14). Then, any sell
transactions would be reverted, leading to a denial-of-service attack to the InsecureMoonToken
contract. ☠️
Figure 2 displays the result of the attack. As you can see, two users bought 55 MOONs with 55 Ethers. But, after the attacker forcibly sent 1 Wei to the InsecureMoonToken
contract, the users were no longer selling their MOONs.
Surprise!! you can buy it but may not sell it. 😭
The Solution
The FixedMoonToken
contract below is the remediated version of the InsecureMoonToken
contract. 👨🔧
|
|
The smart contract should avoid being dependent on the contract’s Ether balance (i.e., address(this).balance
) as it can be artificially manipulated. If necessary, however, the contract should be prepared for such cases of contract balance manipulation.
To remediate the improper balance assertion issue, the FixedMoonToken
contract’s assertion statement was improved by using the >=
instead of the ==
symbol as follows: assert(getEtherBalance() >= totalSupply * TOKEN_PRICE);
(line 37).
As a result, even if the contract balance is manipulated, the FixedMoonToken
contract’s sell
function could still work fine.
Summary
In this article, you have learned that incorrectly depending on improper contract balance assertion can lead to a denial-of-service attack in the smart contract. You have understood how an attacker exploits the vulnerable contract and how to avoid the issue. We hope you find this article useful. Until next time.
Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/08_unexpected_ether_with_forcibly_sending_ether.
Author Details
Phuwanai Thummavet (serial-coder), Lead Blockchain Security Auditor and Consultant | Blockchain Architect and Developer.
See the author’s profile.
About Valix Consulting
Valix Consulting is a blockchain and smart contract security firm offering a wide range of cybersecurity consulting services. Our specialists, combined with technical expertise, industry knowledge, and support staff, strive to deliver consistently superior quality services.
For any business inquiries, please contact us via Twitter, Facebook, or info@valix.io.
Originally published in Valix Consulting’s Medium.