Umbrella Network
Published in

Umbrella Network

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
  • BlockId: 409819
  • 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.

Then, it was deployed, and then another commit was deployed.

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?

Yes.

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

  • Fortress Unitroller (behind Proxy) was called
  • Fortress GovernorAlpha 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.

proposal details:

[ 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

proposal actions:

[ getActions method Response ]
targets address[] : 0x67340Bd16ee5649A37015138B3393Eb5ad17c195
values uint256[] : 0
signatures string[] : _setCollateralFactor(address,uint256)
calldatas bytes[] : 0x000000000000000000000000854c266b06445794fa543b1d8f6137c35924c9eb00000000000000000000000000000000000000000000000009b6e64a8ec60000

input for _setCollateralFactor: FTS tokens address and 700000000000000000(7e17).

The attacker started preparing proposals on 28 April.

He made several proposals but only one was used in this tx.

The attacker voted for the proposal on May 6 and then he added it to the queue.

From the queue, the attacker did the following:

  • Fortress Timelock was called (this is part of the whole governor execution)
  • submit() was called on Chain contract and invalid block 409819 was created
  • 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
  • input: 0x095ea7b3000000000000000000000000d55555376f9a43229dc92abc856aa93fee617a9affffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
  • this is approve(BorrowerOperations, unlimited)
  • Another call to MahaChildToken
  • input: 0x70a08231000000000000000000000000cd337b920678cf35143322ab31ab8977c3463a45
  • this is balanceOf(ExploiterContract)
  • BorrowerOperations (0xd55555376f9a43229dc92abc856aa93fee617a9a) was called for getPriceFeed and returned the PriceFeed contract
  • BorrowerOperations called PriceFeed fetchPrice and returned 2e+34 (2_000_000_000_000_000 in 18 decimals)
  • UMBOracle was 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.14 in 18 decimals.
  • key: 0x0000000000000000000000000000000000000000000000004d4148412d555344
  • FCD value set by the attacker: 40_000_000_000_000_000e18, this invalid price was from block 17634663 to block 17634701
  • 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 getETH to get info about the amount of deposited ETH (output was 233207 ETH)
  • DefaultPool called for getETH output was 14182 ETH
  • ActivePool.getLUSDDebt() called, output was 210129
  • DefaultPool.getLUSDDebt() called, output was 18589
  • TroveManager called via proxy
  • .getTroveStatus(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 5_000_000e18
  • Governance contract was called, method sendToFund()
  • proxy calls + fallbacks TBD
  • attacker contract (0xcd337b920678cf35143322ab31ab8977c3463a45) deposit to ArthUSDWrapper (it is a rebase token)
  • amount 1_000_000_000.000000000000000000
  • Exchanged on a pool that was created 70 days ago
  • Another token exchange occurred on a pool made 30 days ago

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Umbrella Marketing Team

Umbrella Marketing Team

Sends Out official updates and news from Umbrella Network