₿ Blockchain & DeFi Cyberattacks Review

Index of cyberattacks (from recent to oldest):
- 💣 OLA $3.6 Million hack (2022)
- 💣 INV $15.6 Million price manipulation through keep3r oracle (2022)
- 💣 BAYC Discord compromise and phishing (2022)
- 💣 Ronin $625 Million hack (2022)
- 💣 Wormhole Qbridge $324 Million hack (2022)
- 💣 Poly Network Access Control $610 Million hack (2021)
- 💣 PancakeSwap Lottery Frontrunning Vulnerability (2021)
- 💣 Ethereum splitDAO reentrancy attack (2016)
- 💣 King of the Ether Throne (KotET) DOS on gas fees (2016)
- 💣 GovernMental Jackpot Timestamp based hack (2016)
For scams and known exploits in Ethereum smart contracts check the next link out:
Etherscan exploit label: https://etherscan.io/accounts/label/exploit
💣 OLA $4.67M Million reentrancy hack (2022)
On 31st March, Lending network Voltage Finance, powered by Ola Finance, was attacked taking advantage of the re-entrancy bug, the attacker was then able to remove the collateral without first paying back the loan.
References:
- https://ola-finance.medium.com/ola-and-voltage-lending-exploit-on-fuse-post-mortem-214c13d88443
- https://www.coindesk.com/tech/2022/04/01/ola-finance-says-attackers-stole-47m-in-re-entrancy-exploit/
💣 INV $15.6 Million price manipulation through keep3r oracle (2022)
Pouring $3 Million USD into INV to soar the prices in SushiSwap, they got tons of loans using INV as collateral in ANC Market Maker which uses keep3r as price oracle.
Not really they took advantage of a smart contract vulnerability, but they just stressed the market to force a situation, just as happened in the 2010 market flash crash.
💣 BAYC Discord compromise and phishing (2022)
Bored Apes Yacht club and other NFTs projects got their Discord servers compromised on April 1st 2022 due to a vulnerable version of Ticket Tool bot tool.
Using an exploit in Ticket Tool bot, the attacker gained admin access and was able to post a message as the official admin and sending a fraudulent cheap mint phishing site:

As usually happens the domain used in the phishing attack was registered just the same day of the attack:

💣 Ronin $625 Million Hack (2022)
Ronin is the Ethereum-based sidechain that powers Axie Infinity NFT game (Smart contract address: 0x1A2a1c938CE3eC39b6D47113c7955bAa9DD454F2).
The exploit and draining of funds started on March 23 but wasn’t discovered until March 29 when a user was not able to withdraw 5k ETH from the bridge. Five validator private keys were hacked; 4 Sky Mavis validators and 1 Axie DAO.
Most probably by some sort of social engineering engineering attack, they compromised 5 out of 9 Ronin validators being able to transfer $625 million of funds out from the bridge to this wallet 0x098B716B8Aaf21512996dC57EB0615e2383E2f96.
It seems the precedents of the attack were placed in November 2021, when Sky Mavis loosen his security to absorb a huge spike on active users, using a gas-free RPC validator that was used as part of the attack, as the Axie DAO validator IP was still on the allowlist. Once the $RON token was deployed the gas-free RPC validators were no longer necessary, but Sky Mavis forgot to remove them.
The two fraudulent transactions:
- Transfers 173,600 Ether ($526,525,328) https://etherscan.io/tx/0xc28fad5e8d5e0ce6a2eaf67b6687be5d58113e16be590824d6cfa1a94467d0b7
- Transfers 25,500,000 USDC ($25,471,559.04): https://etherscan.io/tx/0xed2c72ef1a552ddaec6dd1f5cddf0b59a8f37f82bdda5257d9c7c37db7bb9b08
The funds are being washed through service Tornado.cash as shown in this tx: 0x284c64dc6788dc7bcdaaee2eee20c430e0860904fb3b1ab11147767f896084e1 where 2,018.23529 Ether ($6,577,872.82) are being sent to Tornado.cash in batches of 100 ETH (April 7th 2022).
References:
💣 Poly Network Access Control $610 Million hack (2021)
Poly Network Bridge offers interoperability between multiple blockchains: https://bridge.poly.network/
Extract from https://slowmist.medium.com/the-root-cause-of-poly-network-being-hacked-ec2ee1b0c68f
The details of the attack
1. The core of this attack is that the verifyHeaderAndExecuteTx
function of the EthCrossChainManager
contract can execute specific cross-chain transactions through the _executeCrossChainTx
function.
2. Since the owner of the EthCrossChainData
contract is the EthCrossChainManager
contract, the EthCrossChainManager
contract can modify the keeper of the contract by calling the putCurEpochConPubKeyBytes
function of the EthCrossChainData
contract.
3. The verifyHeaderAndExecuteTx
function of the EthCrossChainManager
contract can perform user-specified cross-chain transactions by calling the _executeCrossChainTx
function internally. So the attacker only needs to pass in the carefully constructed data through the verifyHeaderAndExecuteTx
function for the _executeCrossChainTx
function to execute the call to the EthCrossChainData
contract PutCurEpochConPubKeyBytes
function to change the keeper role to the address specified attackers.
4. After replacing the address of the keeper role, the attacker can construct a transaction at will and withdraw any amount of funds from the contract.
To call the vulnerable function is a little bit tricky as the attacker had to found out, the valid sighash value to trigger the method:
<function name>(<function input types>)
>http://ethers.utils.id('putCurEpochConPubKeyBytes(bytes)').slice(0, 10)
'0x41973cd9'
> http://ethers.utils.id('f1121318093(bytes,bytes,uint64)').slice(0, 10)
'0x41973cd9'
References:
https://github.com/polynetwork/eth-contracts/issues/17
https://twitter.com/kelvinfichter/status/1425217046636371969
💣 Wormhole Qbridge $324 Million hack (2022)
QubitFin’s bridge contract, QBridge, had two main problems:
- safeTransferFrom without proper address check
- 0x00 explicitly whitelisted
The hacker called deposit()
in the QBridge ETH contract without really making any deposit and emitted the Deposit event The exploit was caused by tokenAddress.safeTransferFrom
in QBridgeHandler.sol which didn’t revert the transaction when tokenAddress
is the 0x00. [Adapted from: https://twitter.com/CertiKCommunity/status/1486892063006334982]
References:
Contract: https://etherscan.io/address/0x99309d2e7265528dc7c3067004cc4a90d37b7cc3
Smart contract code: https://etherscan.io/address/0x99309d2e7265528dc7c3067004cc4a90d37b7cc3#code
Tracking of the stolen funds in SkyTrace: https://www.certik.com/skytrace/bsc:0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7
Address with stolen funds: https://etherscan.io/address/0xd01ae1a708614948b2b5e0b7ab5be6afa01325c7
Patch before the attack:
Vulnerable smart contract version (tag v2.7.2):
[email protected]:wormhole$ git checkout tags/v2.7.2 Previous HEAD position was 9ed71c00 node: terra reobservation HEAD is now at f829195e node/pkg/solana: kill recovery feature [email protected]:~/SCDB/wormhole$ git branch * (HEAD detached at v2.7.2) dev.v2
https://twitter.com/samczsun/status/1489044958417936386
💣 Ethereum splitDAO reentrancy attack (2016)
The DAO was launched on 30 April 2016 and attacked on June 17th. The exploit resulted in almost 33% of funds were siphoned to another contract and later some funds were withdrawn using either Grin or Wasabi Wallet.
The impact was so huge in the Ethereum blockchain that after some debate, the community decided to do a hard fork to reverse the attack transactions and return the funds to the investors. The old untouched blockchain still remains under the token Ethereum Classic (ETC).
The origin of the issue was in the function splitDAO
of the DAO smart contract. It was able to send funds before updating the values, what is called as reentrancy attack.
function splitDAO( uint _proposalID, address _newCurator ) noEther onlyTokenholders returns (bool _success) { Proposal p = proposals[_proposalID]; // Sanity check if (now < p.votingDeadline // has the voting deadline arrived? //The request for a split expires XX days after the voting deadline || now > p.votingDeadline + splitExecutionPeriod // Does the new Curator address match? || p.recipient != _newCurator // Is it a new curator proposal? || !p.newCurator // Have you voted for this split? || !p.votedYes[msg.sender] // Did you already vote on another proposal? || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { throw; } // If the new DAO doesn't exist yet, create the new DAO and store the // current split data if (address(p.splitData[0].newDAO) == 0) { p.splitData[0].newDAO = createNewDAO(_newCurator); // Call depth limit reached, etc. if (address(p.splitData[0].newDAO) == 0) throw; // should never happen if (this.balance < sumOfProposalDeposits) throw; p.splitData[0].splitBalance = actualBalance(); p.splitData[0].rewardToken = rewardToken[address(this)]; p.splitData[0].totalSupply = totalSupply; p.proposalPassed = true; } // Move ether and assign new Tokens uint fundsToBeMoved = (balances[msg.sender] * p.splitData[0].splitBalance) / p.splitData[0].totalSupply; if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) throw; // Assign reward rights to new DAO uint rewardTokenToBeMoved = (balances[msg.sender] * p.splitData[0].rewardToken) / p.splitData[0].totalSupply; uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / rewardToken[address(this)]; rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; if (rewardToken[address(this)] < rewardTokenToBeMoved) throw; rewardToken[address(this)] -= rewardTokenToBeMoved; DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; if (DAOpaidOut[address(this)] < paidOutToBeMoved) throw; DAOpaidOut[address(this)] -= paidOutToBeMoved; // Burn DAO Tokens Transfer(msg.sender, 0, balances[msg.sender]); withdrawRewardFor(msg.sender); // be nice, and get his rewards totalSupply -= balances[msg.sender]; balances[msg.sender] = 0; paidOut[msg.sender] = 0; return true; }
References:
- https://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/
- https://github.com/blockchainsllc/DAO/blob/v1.0/DAO.sol#L599
- https://en.wikipedia.org/wiki/The_DAO_(organization)
💣 King of the Ether Throne (KotET) DOS on gas fees (2016)
King of the Ether Throne was a popular game in 2016 where you could send Ether to the smart contract address to become the new King. The prerequisite was to send more Ether that the previous King sent in order to dethrone him. Also the new King paid a compensation to the previous King. If nobody claimed the throne in 14 days, the game was reset.
The main problem was that the payment to the dethroned King was sent to an Ethereum mist contract-based wallet instead of a contract account which cost more than the maximum 2600 gas fee, actually the gas fee was 10,000 Gwei. So the payment was unsuccessful and the Ether returned to the contract address, getting stuck until the gas limit was expanded and address 0xb2afec1da55c15ad57b3310f9008c47f4e028de3 was able to claim 101.30877 Ether on February 19th 2016 09:51:14 PM +UTC.
References:
- https://www.kingoftheether.com/postmortem.html
- https://github.com/kieranelby/KingOfTheEtherThrone/blob/v0.4.0/contracts/KingOfTheEtherThrone.sol
- https://hackernoon.com/smart-contract-attacks-part-2-ponzi-games-gone-wrong-d5a8b1a98dd8
- https://solidity-by-example.org/hacks/denial-of-service/
- Original contract: https://etherscan.io/address/0xb336a86e2feb1e87a328fcb7dd4d04de3df254d0
💣 GovernMental Jackpot Timestamp based hack (2016)
GovernMental was a ponzi scheme very popular in 2016. It rewarded the last player to join for at least one minute it accumulated 1,100 ETH.
It suffered of two security problems: the first was a DOS due of how the creditors list was structured gas price was increasing for each new player until the gas price surpassed the limit of a block, blocking 1,100 ETH (tx id 0x0d80d67202bd9cb6773df8dd2020e7190a1b0793e8ec4fc105257e8128f0506b) until the gas price limit was modified (June 17th 2016).
The second problem was that a miner could modify the timestamp to fake that he joined somewhere in the future and become the winner.
function lendGovernmentMoney(address buddy) returns (bool) { uint amount = msg.value; // Check if the system already broke down. If for 12h no new creditor gives new credit to the system it will brake down. // 12h are on average = 60*60*12/12.5 = 3456 if (lastTimeOfNewCredit + TWELVE_HOURS < block.timestamp) { // Return money to sender msg.sender.send(amount); // Sends all contract money to the last creditor creditorAddresses[creditorAddresses.length - 1].send(profitFromCrash); corruptElite.send(this.balance); // Reset contract state lastCreditorPayedOut = 0; lastTimeOfNewCredit = block.timestamp; profitFromCrash = 0; creditorAddresses = new address[](0); creditorAmounts = new uint[](0); round += 1; return false; }
References:
https://github.com/GovernMental/GovernMental/blob/gh-pages/contract/Government.sol#L25
https://etherscan.io/address/0xf45717552f12ef7cb65e95476f217ea008167ae3
💣 PancakeSwap Lottery Frontrunning Vulnerability (2021)
Protections against frontrunning existed for the buy
method, but didn’t exist for the multibuy
method.
Patch:
function multiBuy(uint256 _price, uint8[4][] memory _numbers) external { require (!drawed(), ‘drawed, can not buy now’); require(!drawingPhase, ‘drawing, can not buy now’); # <<<<------ FIX require (_price >= minPrice, ‘price must above minPrice’); uint256 totalPrice = 0;`
References:
https://medium.com/immunefi/pancakeswap-lottery-vulnerability-postmortem-and-bug-4febdb1d2400