Originally published at panda-security.xyz
At 21:50:20 UTC on 3 November 2026, a single transaction on BNB Smart Chain drained 259.09 BNB from the reserve behind CoreVault, a BNB vault system tied to the OSK token. The attacker kept 160.26 BNB, which CertiK valued at about $112,200. There was no stolen key and no reentrancy. The attacker borrowed about 207,000 WBNB, dumped most of it into PancakeSwap's WBNB/USDT pool, and then asked CoreVault to redeem a position while the pool showed BNB at roughly one percent of its real price. CertiK reported that the redemption came out about 33 times larger than it should have.
The interesting part is not that a spot price can be moved with a flash loan. Everyone knows that. The interesting part is that the protocol reportedly did have a multi-source oracle, and the attack went through the path the oracle used when its safer inputs were not ready yet. This post walks through what the chain shows, where the reported root cause sits, and what to test in any vault that pays out of a reserve.
TL;DR
- Target: CoreVault and the InsurancePool that backs its redemptions (OSK ecosystem), BNB Smart Chain.
- Transaction:
0x072a10a579e2a8afa26a681205a2aaaa38f74bcb9d1064b12bd9e9ebe2c598b3, block 131,507,810. - Attacker EOA:
0xd3565b191b28b49cc517189b984dacdb1ff05304. - Loss: the reserve contract's BNB balance fell by 259.09 BNB; the attacker's balance rose by 160.26 BNB. The gap is swap fees paid to PancakeSwap.
- Method: flash loan of 207,358.41 WBNB from Lista DAO's Moolah, dump 195,000 WBNB into the PancakeSwap v2 WBNB/USDT pair, redeem at the distorted price, swap back, repay.
- Reported root cause: the redemption price came from an oracle helper that, according to one public breakdown, falls back to the PancakeSwap spot price when its TWAP and average-price windows are not fully populated.
What the Chain Shows
We pulled the receipt and the surrounding state from public BSC nodes. The transaction is a contract creation: the attacker deployed a helper at 0xc28cfeee...f387e2 and ran the whole attack from its constructor, so there was no separate setup transaction to front-run or flag.
The token transfers in the receipt follow a clean sequence:
- Borrow.
0x8f73b65b...e5d8csends 207,358.410468 WBNB to the attack contract0x92f521a7...f542. Onchain Lens identified this lender as Lista DAO's Moolah, and its estimate of about $143.6M for the loan matches the amount at roughly $700 per BNB. - Dump. The attack contract sends 195,000 WBNB into the PancakeSwap v2 pair
0x16b9a828...1eb0daeand receives 13,791,474.30 USDT. - Redeem. A long run of OSK transfers and contract events follows between
0xfc1b8abb...159ef5,0x302f5084...5d674and several other addresses. These are the vault's own bookkeeping as the redemption is processed. - Unwind. The attack contract sends the same 13,791,474.30 USDT back to the pair and receives 194,901.165115 WBNB.
- Repay. Exactly 207,358.410468 WBNB goes back to the lender. We saw no fee transfer to the lender in the receipt.
The native BNB side does not show up as token transfers, so we compared balances one block before and after using an archive node:
| Address | Before | After | Change |
|---|---|---|---|
0xf40e0d52...53df1 (reserve paying out) | 259.1618 BNB | 0.0691 BNB | -259.0928 BNB |
0xd3565b19...05304 (attacker EOA) | 1.9114 BNB | 162.1692 BNB | +160.2578 BNB |
The numbers reconcile. The round trip through PancakeSwap cost the attacker 195,000 minus 194,901.165, about 98.83 WBNB in fees and slippage. Take that from the 259.09 BNB that left the reserve and you get 160.26 BNB, which is what landed in the attacker's wallet. Onchain Lens reported a gross withdrawal of 260.21 BNB through CoreVault and a net depletion matching the roughly 259 BNB we see, and Telem's write-up reports the same pair of figures.
How Far the Price Moved
Right before the attack block, the PancakeSwap pair held about 15,345,479 USDT and 21,917 WBNB, a spot price of about $700.15 per BNB. After the 195,000 WBNB dump and the USDT that came out, the reserves were about 1.55M USDT and 216,917 WBNB, which puts the spot price near $7.16. That is roughly a 98x drop, held for the length of one transaction.
CertiK reported the redemption was inflated about 33x, not 98x. We do not have the verified CoreVault source to explain the exact ratio, so treat this as our reading rather than a confirmed fact: a figure between 1x and 98x is what you would expect if the manipulated spot price was blended with other price inputs instead of used alone. Blackpaper's summary says the redemption logic averaged a same-transaction spot price into a short sampling window. Either way, a partial dilution of a 98x move still left a 33x error, which was more than enough to empty the reserve.
The Reported Root Cause
The most detailed public description comes from Telem, and since CoreVault has not published a post-mortem, the function names below are as reported there and not something we have verified against source code.
According to that report, CoreVault's redeemPrincipal pays BNB for a USD-denominated principal. To convert, it uses a value called wbnbPerUsd, how many WBNB one dollar buys. That value comes from an OracleLib helper that takes a median of three sources. When the TWAP or average-price windows are not fully populated, the median falls back to the PancakeSwap spot price. The payout then flows through Treasury.payoutBnb into InsurancePool.withdrawReserve.
Dumping WBNB makes WBNB cheap, so wbnbPerUsd goes up, so a fixed dollar principal is converted into far more BNB. The attacker did not need to fool the oracle forever. It needed one transaction in which the only source that counted was the one it could move.
A simplified sketch of that failure pattern, written for illustration only and not CoreVault's actual code:
// Simplified illustration of the pattern. Not CoreVault's source. function wbnbPerUsd() public view returns (uint256) { uint256 spot = pancakeSpotWbnbPerUsd(); // movable inside one tx (uint256 twap, bool twapReady) = twapWbnbPerUsd(); (uint256 avg, bool avgReady) = avgWbnbPerUsd(); // The dangerous branch: when safer feeds are not ready, // the "median" quietly becomes the spot price. if (!twapReady || !avgReady) { return spot; } return median3(spot, twap, avg); } function redeemPrincipal(uint256 usdPrincipal) external { uint256 bnbAmount = usdPrincipal * wbnbPerUsd() / 1e18; treasury.payoutBnb(msg.sender, bnbAmount); // pulls from the insurance reserve } A safer shape refuses to price at all when the robust inputs are missing:
// Simplified illustration of a fail-closed version. function wbnbPerUsd() public view returns (uint256 price) { (uint256 twap, bool twapReady) = twapWbnbPerUsd(); require(twapReady, "oracle: twap not ready"); uint256 spot = pancakeSpotWbnbPerUsd(); // Spot may be used as a sanity check, never as the price. require(_withinBps(spot, twap, MAX_DEVIATION_BPS), "oracle: spot deviates"); return twap; } Why a Three-Source Median Did Not Help
A median of three sources is a reasonable design. It only works if all three sources are live and independent. The reported fallback breaks both conditions at once: when the windows are empty there is effectively one source, and it is the one an attacker can move with borrowed money.
Fallback paths are easy to miss in review because they look defensive. Someone wrote "if the TWAP is not ready, do not revert, use what we have" so the product would keep working right after deployment or after a quiet period. That is a liveness decision. In a function that sends value out, it turns into a security decision, and the safe default for a payout is to stop, not to guess.
The second design problem is where the money came from. An insurance reserve is supposed to absorb losses. Here it was wired directly into the redemption path, so any pricing error in redemptions was paid from the backstop. That turned a pricing bug into a reserve drain, and there was no per-transaction or per-period limit in the way.
Lessons for Builders and Auditors
Make every oracle fallback fail closed in value-moving paths. If a TWAP, median or external feed is not ready, revert redemptions, withdrawals and liquidations. A paused vault is an annoyance; a vault that prices from spot is a donation.
Test the cold start. Many TWAP implementations need a warm-up period after deployment, after a pool migration, or after a long gap in observations. Write tests that call every payout function in that state and assert that they revert.
Use spot only as a sanity check. Comparing spot to a TWAP and rejecting large deviations is useful. Letting spot set the price, even inside a median, is not.
Bound reserve outflows. Rate limits, per-transaction caps and caps relative to the reserve's size would not fix a bad price, but they turn a full drain into a partial one and buy time for monitoring to react.
Model the flash loan budget. Here roughly $143.6M of WBNB was borrowed against a pool holding about $30.7M of total liquidity. Your threat model should assume an attacker can borrow several times the depth of any pool your contracts read from, for free, within one transaction. Our earlier guide to flash loan attacks covers the general pattern.
Treat "derived from balances" and "derived from spot" as the same smell. The Moonwell MAMO exploit inflated a value through direct token transfers; CoreVault inflated one through a swap. Both read a number an attacker could set. Our smart contract security checklist is a good place to start when reviewing a vault.
What Is Still Unknown
- CoreVault has not published a post-mortem that we could find, and the contracts' source was not available to us for verification. The function-level root cause above is third-party analysis.
- Loss figures differ by framing. CertiK puts the attacker's take at 160.26 BNB, about $112,200. Onchain Lens and Telem put the reserve's net loss at about 259 BNB, roughly $175K to $181K depending on the BNB price used. Our balance check supports both: 259.09 BNB out of the reserve, 160.26 BNB to the attacker.
- We found no public report of recovered funds or of a patch to the oracle fallback.
Key Addresses and Figures
| Item | Value |
|---|---|
| Chain | BNB Smart Chain |
| Exploit transaction | 0x072a10a579e2a8afa26a681205a2aaaa38f74bcb9d1064b12bd9e9ebe2c598b3 |
| Block / time | 131,507,810 / 3 Nov 2026, 21:50:20 UTC |
| Attacker EOA | 0xd3565b191b28b49cc517189b984dacdb1ff05304 |
| Attack contract (created in tx) | 0xc28cfeee046e1421e9dbfb2cf689074bf9f387e2 |
| Flash loan | 207,358.410468 WBNB |
| WBNB dumped / recovered | 195,000 / 194,901.165115 WBNB |
| PancakeSwap spot before / during | about $700.15 / about $7.16 per BNB |
| Reserve balance change | -259.0928 BNB |
| Attacker profit | +160.2578 BNB (about $112,200 per CertiK) |
Sources
- CertiK alert via TechFlow: 33x redemption inflation, 160.26 BNB, about $112,200
- Onchain Lens via KuCoin News: Moolah flash loan, 260.21 BNB withdrawal, attack sequence
- Telem: CoreVault insurance pool drained: reported function names and oracle fallback behavior
- Blackpaper BP-1387: transaction hash, attacker address, spot-price averaging summary
- The Crypto Guardian incident record: SlowMist classification and loss estimate
- BNB Smart Chain receipt, block and archive balance data read directly from public RPC nodes
Closing Thoughts
CoreVault did not lose its reserve because it trusted a single DEX price on purpose. It lost it because a fallback written for convenience became the only price that mattered for one transaction. If your protocol pays out from a reserve or converts between assets with an on-chain price, talk to our team or check our audit pricing to have the cold-start and fallback paths reviewed before an attacker finds them.


