A comprehensive list of historic defi attacks, the attack vectors, and how to prevent them.
This site is dedicated to information about flash loan attacks (which are usually just price oracle attacks) and how to stop them. For a study done by the Imperial College London, check out this deep dive on the historic bZx flash loan (oracle manipulation) attack for more information. The first step you can take, is to use decentralized oracles when working with data, a number of the projects that had issues moved themselves to Chainlink Price Feeds and are now secure. This is an open source project.
If you’d like to help fund Gitcoin grants for research, please consider donating.
Protocol | Reference | Date | Value (USD) | Transaction | Cause | Fix |
---|---|---|---|---|---|---|
bZx (1) | QuantStamp | Feb-15-2020 01:38:57 AM +UTC | $350k | Etherscan | Pump & Arbitrage | Unclear |
bZx (2) | CoinTelegraph | Feb-18-2020 03:13:58 AM +UTC | $600k | Etherscan | Oracle Attack | Chainlink Integration |
Origin Protocol | CoinDesk | Nov-17-2020 12:47:19 AM +UTC | $7M | Etherscan | Re-entrancy Attack | Unclear |
Harvest.Finance | CoinDesk | Oct-26-2020 02:58:16 AM +UTC | $24M | Etherscan | Oracle Attack | Unclear |
Value Defi | CoinDesk | Nov-14-2020 03:36:30 PM +UTC | $6M | Etherscan | Oracle Attack | Chainlink Integration |
Akropolis | CoinDesk | Nov-12-2020 12:04:41 PM +UTC | $2M | Etherscan | Re-Entrancy | Re-Entry Check added |
Cheese Bank | CoinTeleGraph | Nov-14-2020 03:36:30 PM +UTC | $6M | Etherscan | Oracle Attack | Unclear |
Compound | Decrypt | Nov-26-2020 08:55:16 AM +UTC | $89M | Etherscan (one of many) | Oracle Attack | Unclear |
MakerDAO | Post Mortem | Nov-25-2020 10:46:00 PM +UTC | -- | -- | Oracle Attack | Unclear |
Warp Finance | Summary | Dec-17-2020 11:24:41 PM +UTC | $7.76M | ethtx.info | Oracle Attack | -- |
A flash loan is a loan that is only valid within one blockchain transaction. Flash loans fail, if the borrower does not repay its debt before the end of the transaction borrowing the loan. That is, because a blockchain transaction can be reverted during its execution, if the condition of a repayment is not satisfied.
We can then see some easy attack vectors using this tool.
This seems to be the #1 cause of attacks at the moment, by far. What is important to note, is that decentralized exchanges are not decentralized oracles. Using Uniswap, Sushiswap, or Curve to get pricing information to execute trades is pulling data from potocols whose price depends soley on liquidity. Looking at the infamous ground zero bZx attack that sparked this wave of attacks, we can see exactly what happens. These flash loans are used to crash and manipulate the price of these decentralized exchanges, which most projects deemed safe to use. The issue here relies in the fact that these protocols prices depend entirely on liquidity.
See the above section for what something like this would look like.
The easiest way to solve this is to use decentralized oracles. Chainlink Price Feeds are the leading decentralized oracle provider, and you can see that the vast majority of the protocols end up adding Chainlink to fix these attacks. If the data (price or otherwise) you're looking for isn't there yet, you can always request new decentralized networks or build your own.
Let's take a look at some malicious pseudo-code, pretend these are each ERC20s.
1uint256 priceOfMyGovernanceTokenInETH = dexTokenETHPairPrice;2myGovernanceToken.transfer(msg.sender, priceOfMyGovernanceTokenInETH)
This right here should be the easiest red flag on the planet. If you ever do a transfer based on a centralized price oracle, you're asking to get owned. One way or another.
Check the Chainlink documentation for decentralized price feeds
1import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";2// constructor and getLatestPrice function truncated3uint256 priceOfMyGovernanceTokenInETH = getLatestChainlinkPrice();4myGovernanceToken.transfer(msg.sender, priceOfMyGovernanceTokenInETH)
A reentrancy attack can occur when you create a function that makes an external call to another untrusted contract before it resolves any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the effects were resolved.
Leave external transactions to the last parameter. These are the harder ones to prevent, but below is a simple example of what should be done.
Whenever possible, use the built-in transfer()
function. It only sends 2300
gas with the external call, making reentrancy almost impossible. Since that will give you just enough gas to write to a log.
You could alternativly add a mutex, or a variable that puts a lock on calling the function or working with the variables until the work with them is done. You don't need to do all of these tips, but you do need to do at least one of these tips.
This code:
1function withdraw() external {2 uint256 amount = balances[msg.sender];3 require(token.transfer(msg.sender, amount)());4 balances[msg.sender] = 0;5}
Should be changed so that the external token transfer call happens after the balance is updated to 0.
1function withdraw() external {2 uint256 amount = balances[msg.sender];3 balances[msg.sender] = 0;4 require(token.transfer(msg.sender, amount)());5}
Additionally, you could do something like:
1bool public mutex = false;23function withdraw() external {4 require(!mutex);5 mutex = true;6 uint256 amount = balances[msg.sender];7 balances[msg.sender] = 0;8 require(token.transfer(msg.sender, amount)());9 mutex = false;10}
The DAO attack is an example of the reentrancy attack as well, and is also considered the mother of not just defi, but decentralized attacks in general on the ETH chain.
Since everything on-chain is public information, an attacker can watch transactions on-chain and look for those that would be detrimentalto the atacker, and make a transaciton with a higher gas price to occur before that transaction goes through. For example, they notice a whale is about to dump a token that the attacker holds, so the attacker pays extra gas to dump theirs first. This is known as "front running" in traditional finance, you could also think of it as a race condition because there can be scenarios where it's more complicated than this exmaple, but still boiled down to this. Reentrancy technical falls under this category.
The best way to prevent against these is with a commit-reveal scheme. This is when a project sends a transaction that goes through and is accepted, but is hashed or encrypted. Only after the transaction has concluded that they send a "reveal" phrase that decodes the transaction. This method prevents both miners and users from frontrunning transactions as they cannot determine the contents of the transaction. Transactional value however, cannot be commit-revealed, making this far less effective in the defi world. This is another very difficult type of attack to prevent.
Bancor had run into this issue but fixed it before it was exploited.
#TODO looking for projects that use commit-reveal.
Pump and arbitrage attacks are difficult to find, some even saying they are less "attacks" and more "the system working as intended". Liquidity is an important part of any and all processes, so when a whale spikes or crashes a price, does that really reflect the true value of that crash/spike? It's hard to say.
Prevention at the moment hangs around preventing anyone from being able to cause these spikes. Sometimes, coordinated attacks from social groups can be enough to pump and dump a price of an asset.
It's important to note that their are a LOT more vulnerabilities than what we are covering here. These are just the major issues we've seen in defi. This blog does a great job of outlining many of these attacks, and showing how to prevent them. Various Known Attacks
Anther great resource that goes over additional attacks. Additional security considerations.
It's unclear if auditors should be catching these, or if developers and orgainizations are taking shortcuts, or if people are just "apeing" into projects before thinking. We are all leanring
This will be a work in progress for us to get better at standards here. At the moment, I'm not sure how to address some of the liquidity issues. If you have a protocol that depends heavily on liquidity of another platform, you could very well be vulnerable.