Technical Post Mortem — Umbrella Chain Exploit
On Sunday, May 8th, hackers managed to exploit a bug in Umbrella’s Chain oracle smart contract, manipulating the price feed data of two of our First Class Data pairs, MAHA-USD and FTS-USD. No Layer 2 Data was manipulated. The subsequently manipulated prices were used to further exploit the users of that data, MahaDAO and Fortress Loans, respectively. This post will look to explain the course of events that led to exploitative attacks by malicious third parties on our oracle.
First, the details on-chain:
- The transaction that exploited the oracle: Link
- The Malicious contract: Link
- BSC block number 17634663
Background: What Made The Attack Possible
There was a bug in the chain contract (and its related ancillaries), and it was not audited back in August of 2021 when it should have.
Most codebases keep evolving (almost daily) to add new features, improvements, and optimizations. Given our aggressive roadmap and limited resources at that time, there was a missed opportunity for peer review and code audit.
Pre-August 6th, our chain contract would check balances of validator wallets that submitted signatures (basically submitted blocks to get added to the blockchain network). A 2/3 of consensus was required before a block of transactions (root hash and FCDs) could be added to the blockchain network.
To ensure that community validators do not collude and abuse the consensus requirements, we implemented a PoA (Proof of Authority) consensus mechanism while working on rolling out our DPoS (Delegated Proof of Stake) consensus.
4 signatures would be needed before a block could be added on-chain, irrespective of power (volume of UMB tokens held by individual validators). The goal was simple — make sure that any entity could not buy a lot of tokens and force a malicious transaction to get added on-chain.
On August 6th the PR with the bug was created. The bug was that instead of counting valid signatures, the code would count the total number of signatures and give a green light if the total (of valid and invalid) signatures was 4 or more.
On August 10th, in this commit, the bug was introduced but not activated.
August 31st: Bug Gets Activated
On this day, we turned off the mandatory balance check because we thought that the PoA requirement of 4 signatures (this number could be increased for more security at will) would do the job adequately.
Deploying this code also activated the bug.
After that, three more contracts were deployed with the same bug after August 31st (the last one on Dec-13–2021).
How the Attacker Exploited the Bug
As per the padding set in our chain contract, an entity can only submit one transaction every 60 seconds. The attacker submitted a transaction, front-ran the real validator, and because the chain contract was now only counting total number of signatures (instead of valid signatures), the malicious transaction (with incorrect pricing data’s root hash and FCD) was committed on-chain.
Now, the incorrect FCD could be used until the next block was minted (with the correct data).
The attacker wrote a smart contract to do the above and could now abuse the partner platforms with the incorrect FCD.
Has The Bug Been Fixed?
The fix was to count signatures that have positive balances only (non-verified validators have a balance of 0)and redeploy the contract.
Once redeployed, the whole protocol was updated automatically with the new contract address.
Execution Flow of Attack on Fortress Finance
Unitroller(behind Proxy) was called
execute()the method with proposal id
11, the one that the attacker created. This creation was not part of this tx, but the execution of this proposal was.
[ proposals method Response ]
id uint256 : 11
proposer address : 0x0dB3B68c482b04c49cD64728AD5D6d9a7B8E43e6
eta uint256 : 1652041513
startBlock uint256 : 17490884
endBlock uint256 : 17577284
forVotes uint256 : 415967882226291982976102
againstVotes uint256 : 0
canceled bool : false
executed bool : true
[ getActions method Response ]
targets address : 0x67340Bd16ee5649A37015138B3393Eb5ad17c195
values uint256 : 0
signatures string : _setCollateralFactor(address,uint256)
calldatas bytes : 0x000000000000000000000000854c266b06445794fa543b1d8f6137c35924c9eb00000000000000000000000000000000000000000000000009b6e64a8ec60000
_setCollateralFactor: FTS tokens address and
The attacker started preparing proposals on 28 April.
He made several proposals but only one was used in this tx.
From the queue, the attacker did the following:
Timelockwas called (this is part of the whole governor execution)
submit()was called on
Chaincontract and invalid block
- then lending protocol was abused by depositing and borrowing assets based on invalid FTS price
Execution Flow of Attack on MAHADAO
- MahaDao (via proxy) call
- MahaChildToken called via proxy delegation
- this is
- Another call to MahaChildToken
- this is
- BorrowerOperations (
0xd55555376f9a43229dc92abc856aa93fee617a9a) was called for
getPriceFeedand returned the PriceFeed contract
- BorrowerOperations called PriceFeed
2_000_000_000_000_000in 18 decimals)
UMBOraclewas called inside, this is Maha contract deployed on Feb 13, with UMB oracle implementation that pulls price for key
MAHA/USD, at the time of writing, the reported price is
2.14in 18 decimals.
- FCD value set by the attacker:
40_000_000_000_000_000e18, this invalid price was from block
- the attacker did not send any data to the contract while creating tx, so this was probably included in the contract code, that executed tx
- ActivePool (Maha protocol) was called
getETHto get info about the amount of deposited ETH (output was
- DefaultPool called for
- ActivePool.getLUSDDebt() called, output was
- DefaultPool.getLUSDDebt() called, output was
- TroveManager called via proxy
0xcd337b920678cf35143322ab31ab8977c3463a45) called with attacker address, output was
0. (it means not exists)
- .decayBaseRateFromBorrowing() was called (Updates the baseRate state variable based on time elapsed since the last redemption or LUSD borrowing operation.)
- LiquityLUSDToken.mint() (ARTH Valuecoin (ARTH)) was called for Governance contract with value
- Governance contract was called, method
- proxy calls + fallbacks TBD
- attacker contract (
0xcd337b920678cf35143322ab31ab8977c3463a45) deposit to ArthUSDWrapper (it is a rebase token)
- Exchanged on a pool that was created 70 days ago
- Another token exchange occurred on a pool made 30 days ago