Solidity Security By Example #12: Amplification Attack (Double Spending #1)
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.
An amplification attack can happen when a smart contract is misdesigned, resulting in the contract being exploited. This article will explain how a smart contract with a design flaw can be attacked and how to deal with the issue. Enjoy reading. 😊
You can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/12_amplification_attack__ double_spending_01.
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 Dependencies
- The Vulnerability
- The Attack
- The Solutions
- Summary
The Dependencies
The code below contains dependencies required by the InsecureMoonDAOVote
and the FixedMoonDAOVote
contracts. The dependencies include ReentrancyGuard
abstract contract (lines 3–12), IMoonToken
interface (lines 14–23), and MoonToken
contract (lines 25–99).
The ReentrancyGuard
contains the noReentrant
modifier that is used to prevent reentrancy attacks. The noReentrant
(lines 6–11) is a simple lock that allows only a single entrance to the function applying it. If there is an attempt to do the reentrant, the transaction would be reverted by the require
statement in line 7.
The IMoonToken
interface defines function prototypes, enabling the InsecureMoonDAOVote
and the FixedMoonDAOVote
contracts to interact with the MoonToken
contract.
Lastly, the MoonToken
contract is a simple ERC-20 token. Users can buy MOON tokens with the corresponding number of Ethers via the buy
function (lines 39–46). Users can also sell their MOONs through the sell
function (lines 48–55), transfer their MOONs via the transfer
function (lines 57–63) and the transferFrom
function (lines 65–78), and approve the token transfer to a spender via the approve
function (lines 80–82).
Additionally, users can query the transfer approval allowance to a spender by calling the allowance
function (lines 84–90), get the total number of Ethers locked in the contract by way of the getEtherBalance
function (lines 92–94), and get their balances by consulting the getUserBalance
function (lines 96–98).
In addition to the MOON token, it is a non-divisible token with zero token decimals (line 37). 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 Ether (line 30). In other words, 1 MOON will always be worth 1 Ether.
The Vulnerability
The following code exhibits the InsecureMoonDAOVote
contract. This contract provides the voting functionality for all MOON token holders to cast a vote for CEO candidates freely — id: 0 for Bob
, id: 1 for John
, and id: 2 for Eve
(lines 28–41).
Within a voting period, a user can cast a vote for only one candidate of their choice by invoking the vote
function (lines 44–59), and check for their vote via the getUserVote
function (lines 65–67).
Further, users can query the candidate list by consulting the functions getTotalCandidates
(lines 61–63) and getCandidate
(lines 69–72).
Unquestionably, the InsecureMoonDAOVote
contract must be vulnerable. Can you catch up on the issue? 👀
As you can see, the InsecureMoonDAOVote
contract is straightforward. The more MOON tokens a user possesses, the more voting points they can give to their candidate (line 58). ✍️
Unfortunately, the InsecureMoonDAOVote
contract got a design flaw in the voting mechanism, allowing an attacker to perform a voting amplification attack. 😲
Figure 1 below depicts how an attacker can exploit the InsecureMoonDAOVote
contract’s voting mechanism to mastermind the winner.
The upper part of Figure 1 pictures how a regular voter possessing 100 MOONs casts a vote for the candidate Bob. The lower part portrays how an attacker performs an amplification attack on voting for their candidate Eve.
In more detail, the attacker uses their 100 MOONs to vote for Eve (Steps 1 and 2) like a regular voter.
Since the InsecureMoonDAOVote
contract only records the vote of each voter and will not allow the same voter to cast a double vote (line 46), the attacker can bypass this condition check by transferring their 100 MOONs to another Sybil account (Step 3). 😎
For this reason, the attacker can double-spend the vote on the spent tokens (Step 4), amplifying Eve’s voting points easily (Step 5). 😈
Let’s demystify the root cause in short. We found that the voting mechanism of the InsecureMoonDAOVote
contract lacks locking up the spent MOON tokens after the vote. Please refer to the Solutions
section below for the remediation solutions. 🤕
Are there the voting amplification vulnerabilities in real production?
For sure, we discovered the voting amplication vulnerability of the on-chain voting mechanism of the SUSHI token, which can also affect every forked project that adopts that voting functionality to be attacked. 🕵️
Moreover, we also discovered other voting vulnerabilities including voting displacement and redelegation failure. Head to our discovery report for details of our findings. 🕵️
The Attack
The following code presents the contracts AttackServant
(lines 9–26) and AttackBoss
(lines 28–59). Both contracts facilitate an attacker to take control of the voting winner easily.
To exploit the InsecureMoonDAOVote
contract’s voting mechanism, an attacker performs the following actions:
- Deploy the
attackBoss
contract (lines 32–35) by passing addresses of theMoonToken
contract and theInsecureMoonDAOVote
contract as deployment arguments. - Trigger the attack by executing the
attackBoss.attack()
function (lines 38–58) and inputting as function arguments the target candidate’s id (i.e.,_candidateID
) and the number of times they would like to amplify the vote on the target candidate (i.e.,_xTimes
).
The following describes how the attackBoss.attack()
function would perform the exploitation under the hood. 🥷
- The
attackBoss.attack()
function transfers all attacker’s MOON tokens to the contract itself (line 43). - The
attackBoss.attack()
function executes the loop for orchestrating multipleservant
contract instances (lines 45–54) as per the_xTimes
parameter (line 45).
2.1 — In each iteration, theattackBoss.attack()
function deploys a singleservant
contract instance from theAttackServant
contract (line 47).
2.2 — TheattackBoss.attack()
function transfers all MOON tokens to the previously deployedservant
instance (line 50).
2.3 — TheattackBoss.attack()
function invokes theservant.attack(_candidateID)
function (line 53) to cast a vote for the given_candidateID
.
2.4 — Theservant.attack()
function performs the vote for the target candidate (line 23) and then transfers all MOON tokens back to theattackBoss
mother instance (line 24).
2.5 — TheattackBoss.attack()
function proceeds with the new iteration until it fulfills the_xTimes
parameter (line 45). - Finally, the
attackBoss.attack()
function transfers all MOON tokens back to the attacker (line 57) and finishes the attack execution.
AttackBoss
mother contract instance orchestrates multiple AttackServant
instances to take over the voting winnerFor the sake of illustration, consider Figure 2 above as an example. The attacker initiates the attack by triggering the AttackBoss
mother contract instance and approving 100 MOONs as initial tokens for the attack (Step 1).
The AttackBoss
mother instance sequentially deploys three AttackServant
child instances to amplify the voting points for the candidate Eve (Steps 2–10). 🧙♂️
Eventually, the AttackBoss
instance transfers 100 MOONs back to the attacker (Step 11). As a result of the attack, the voting points for Eve have been gained by 300 points — i.e., 3X amplification (Step 12). 🧞♂️
Figure 3 above displays our attack simulation result. As you can see, the attacker could gain 200 voting points for Eve by using only 10 MOONs (i.e., 20X voting amplification) in only a single attack transaction. 🤯
The Solutions
There are two preventive solutions to remediate the voting amplification vulnerability. 👨🔧
- Locking up the spent MOON tokens after the vote
- Applying the delegation and checkpoint approach to keep track of voting points at each specific block number
In this article, we will demonstrate the first solution (Locking up the spent MOON tokens after the vote). 👍
For the second solution (Applying the delegation and checkpoint approach to keep track of voting points), it requires both the
MoonToken
and theInsecureMoonDAOVote
contracts to be completely redesigned.Besides, the new design also requires users a completely different approach to interacting with the contracts. For this reason, the second solution will not be presented in this article. 🤡
In case you might be interested, nonetheless, please refer to the ERC20Votes implementation by OpenZeppelin.
This section will describe the first solution (Locking up the spent MOON tokens after the vote). Let’s take a look at the FixedMoonDAOVote
contract above.
The idea is to lock away all MOONs already used for voting for a certain period. Later on, voters can withdraw their MOONs after the voting period ends. This way, we can guarantee that no one can double-spend their MOONs. 👌
To achieve this, we introduced a new UserVote
struct property named moonWithdrawn
(line 14) for tracking the MOON token’s withdrawal status.
The moonWithdrawn
variable will be false once a voter executes the vote
function (line 61). The variable will be true when a voter withdraws their MOONs (line 74).
To lock up the MOON tokens, the vote
function will transfer all MOONs from a voter account to the FixedMoonDAOVote
contract itself (line 55). 🤔
For voters to withdraw their MOONs, we implemented the withdrawMoonTokens
function (lines 68–78). This function suddenly allows voters to retrieve their MOONs after the voting period. 🤝
Summary
In this article, you have learned how a design flaw can lead to an amplification attack in the smart contract. You have understood how an attacker exploits the vulnerable contract and how to tackle the issue.
We hope this article could gain your security knowledge. See you again in our next article.
Again, you can find all related source code at 👉 https://github.com/serial-coder/solidity-security-by-example/tree/main/12_amplification_attack__ double_spending_01.
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.