On June 28th at 6:03PM UTC (2:03PM EDT), the CertiK Skynet found that block 10355807 on the Balancer DeFi contract was abnormal. Our team of security researchers acted quickly to investigate; however, the attack had already happened.
At 6:03PM UTC (2:03PM EDT) on June 28th, the attacker used WETH borrowed from dYdX, a flash loan service provider to buy STA tokens in large quantities, causing the exchange price of STA and other tokens to rise sharply.
Then, the attacker used the minimum amount of STA (a value of 1e-18, or in other words, 1 x 10^(-18)) to continuously repurchase WETH. Because of the nature of STA tokens, 1% of the transaction is burned; but in this case, it meant that the entire 1e-18 amount of STA was burned immediately and none was transferred into the Balancer. So at the end of each repurchase, the total amount of STA in Balancer remained at 1e-18 rather than increasing by the purchase amount of 1e-18 STA.
The attacker continued to exploit the vulnerability, used the high-priced STA to short tokens (WETH, WBTC, LINK, and SNX), and used WETH to repay the flash loan.
A large amount of STA, WETH, WBTC, LINK, and SNX remained, and the attacker transferred the illegal income to their own account through Uniswap. As a result, the attacker made off with $130k USD.
After extracting the STA balance, the attacker quickly called the swapExactAmountInfunction to purchase STA and used swapExactAmountOut on the 24th transaction to accurately buy STAs at the minimum value (1e-18). This allowed the attacker to maximize the efficiency of subsequent attacks.
The first 6 transactions from the attacker were repeated buy and sell orders between WETH and STA, which led to no gains and just 4 WETH lost. The purpose of these first 6 transactions is still unknown, but it’s possible that the attacker took advantage of these 6 transactions to hide traces of its own flash loan.
Phase 0: The attacker borrows WETH from a dYdX flash loan.
Phase 1: With the loaned WETH, the attacker manipulates and inflates the STA price by mass-purchasing STA via the Balancer contract.
Phase 2: With the newly-acquired STA, the attacker buys back WETH multiple times; in each transaction, the minimum amount of STA (value 1e-18) is used for post-purchase, and as with the nature of STA, that minimum amount of STA gets burned during the transaction rather than transferred into the Balancer pool. The attacker takes advantage of that functionality and uses the internal function gulp() of the Balancer to lock the number of STAs, thus controlling the price of STA to WETH. They repeat this buy-back operation several times until the WETH in the Balancer is drained.
Phase 3: The attacker changes the targeted tokens and repeats phase 2 with STA until all the targeted tokens are drained. Phase 3 is repeated three times, and a total of 4 tokens (WETH, WBTC, LINK and SNX) suffer losses.
Phase 4: The attacker repays dYdX flash loan and leaves the market.
Let’s review each phase in more detail.
As you can see, the first 24 transactions traded WETH with STAs that were borrowed from flash loans. The number of STAs in the Balancer was reduced as much as possible, which sharply increased the price of STAs compared to other tokens.
At the beginning of Phase 2, the total number of STAs was locked to the previous 1e-18 by the gulp function. When trading STA to WETH through the swapExactAmountIn function for the first time, the attacker deliberately set the number of STA transactions to 1e-18.
Since the number of STAs in the transaction model was burned (recall this is how DeFi is setup), the price of STA was extremely high compared to other tokens. After completing the first transaction, the number of STAs in the Balancer should have been 2e-18, but they were, in reality, burned.
However, before the second time and every subsequent round of trading STA to WETH through swapExactAmountIn, the attacker used the gulp() function to lock the current number of STAs with the internally recorded 1e-18 for the number of STAs in the Balancer. Therefore, when buying WETH, STA can still maintain an extremely high price.
But because the amount of WETH decreased after purchasing WETH, the illegal income of each attack gradually decreased. After 18 attacks, the WETH in the Balancer was completely stolen. Please note that the number of attacks that led to the exhaustion of WETH was a deliberate program design.
When the WETH in the Balancer was completely stolen, the attacker used the same vulnerability to repeat the attack on other Balancer tokens (WBTC, LINK, and SNX), thus stealing all those tokens as well.
The attacker address: 0x81D73c55458f024CDC82BbF27468A2dEAA631407
The attacker's final collection address: 0xbf675c80540111a310b06e1482f9127ef4e7469a
Total Profit: 565.5326240837032 ETH (about $127,250* USD)
*Note: Price as of 6:11PM UTC (2:00PM EDT) time in 20200630
The gulp() function of the Balancer contract allows the overwriting of an internal record value of a certain token to the current real number of the token, but in this case it’s been wrongly set as an external function with no limits on who can execute it. Instead, thegulp()function should be internal, or there should exist some verification or protection restrictions for specific users or smart contract owners.
Note: Shortly after this first attack was discovered, there were copycat attacks. Read more about the second wave of attacks on the Balancer contract
Balancer Github code:
Attack transaction history
Official attack report: