On August 29, 2021, the xToken DeFi project experienced a second hack. The attacker managed to steal approximately $4.5 million in tokens using a flash loan by exploiting a vulnerability in the project’s smart contracts.
Inside the Attack
Flash loan attacks are designed to take advantage of artificially modified token prices. The attacker swaps a large amount of one token for a large amount of another on a cryptocurrency exchange. This causes the value of the tokens to change on that exchange because it has a different supply of each token.
In this case, the attacker borrowed 25k ETH from dydx and about 1m SNX from Aave V1 and V2. They then performed a few different swaps on different exchanges:
- 6.8k ETH to 519k SNX on Bancor
- 1.5m SNX to ~6.5m USDC on Kyber
- ~6.5m USDC to ~6.5m sUSD on Curve
At this point in the process, the attacker has artificially depressed the value of SNX on Kyber (by making a large deposit) and has about 6.5m sUSD. This is when they interact with the xSNXAdmin contract, which manages xSNX assets:
- Deposit ~2m sUSD to pay the contract’s sUSD debt and unlock SNX
- Call callFunction in the contract to burn sUSD debt and swap ~614k SNX for ~811k sUSD at a low price (due to depressed price on Kyber)
The attacker should not have been able to call the callFunction function, which allowed them to exploit the contract. This function should only have been callable from sysx’s SoleMargin flash loan contract.
However, the code used the statement require(sender=address(this)) rather than require(msg.sender==soloMarginAddress). The erroneous require statement made the function publicly callable and made the exploit possible.
By depressing the value of SNX, the attacker created an opportunity for arbitrage. At the end of the process, they reversed all steps to convert the stolen tokens to ETH, which was used to pay off their flash loan.
Lessons Learned From the Attack
The xToken hack boils down to an access control vulnerability. The require statement used in the contract’s callFunction function does not appropriately limit access to the function and makes it publicly callable.
The xToken contracts underwent security audits nearly a year ago, but the vulnerable code was added within the last three months. This underscores the importance of performing a security audit before pushing any new code to production.