Solidity Security By Example #12: Amplification Attack (Double Spending #1)
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.
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.
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 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).
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.
IMoonToken interface defines function prototypes, enabling the
InsecureMoonDAOVote and the
FixedMoonDAOVote contracts to interact with 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 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).
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). ✍️
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.
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 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:
attackBosscontract (lines 32–35) by passing addresses of the
MoonTokencontract and the
InsecureMoonDAOVotecontract 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.,
The following describes how the
attackBoss.attack() function would perform the exploitation under the hood. 🥷
attackBoss.attack()function transfers all attacker’s MOON tokens to the contract itself (line 43).
attackBoss.attack()function executes the loop for orchestrating multiple
servantcontract instances (lines 45–54) as per the
_xTimesparameter (line 45).
2.1 — In each iteration, the
attackBoss.attack()function deploys a single
servantcontract instance from the
AttackServantcontract (line 47).
2.2 — The
attackBoss.attack()function transfers all MOON tokens to the previously deployed
servantinstance (line 50).
2.3 — The
attackBoss.attack()function invokes the
servant.attack(_candidateID)function (line 53) to cast a vote for the given
2.4 — The
servant.attack()function performs the vote for the target candidate (line 23) and then transfers all MOON tokens back to the
attackBossmother instance (line 24).
2.5 — The
attackBoss.attack()function proceeds with the new iteration until it fulfills the
_xTimesparameter (line 45).
attackBoss.attack()function transfers all MOON tokens back to the attacker (line 57) and finishes the attack execution.
For 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).
AttackBoss mother instance sequentially deploys three
AttackServant child instances to amplify the voting points for the candidate Eve (Steps 2–10). 🧙♂️
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. 🤯
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
InsecureMoonDAOVotecontracts 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.
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. 🤝
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.
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 email@example.com.
Originally published in Valix Consulting’s Medium.