Skip to main content

oracle-manipulation

Hunt single-block oracle manipulation — spot-price AMM oracles, manipulable TWAP, dependent calculations, missing staleness checks.

インストールへ移動

ソース情報

リポジトリ
BitterSecurity/Decepticon
ソースの最終更新活動
2026年6月2日 17:25
検出された SKILL.md の言語
英語
スター
5,565
フォーク
1,053

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
oracle-manipulation
description
Hunt single-block oracle manipulation — spot-price AMM oracles, manipulable TWAP, dependent calculations, missing staleness checks.
metadata
{"subdomain":"smart-contracts","when_to_use":"oracle manipulation twap price bypass","mitre_attack":["T1565.001","T1190"]}
# Oracle Manipulation Playbook DeFi protocols that read a price from an on-chain source are vulnerable when the source can be moved within a single transaction or block. Classic vectors: 1. **Spot-price AMM oracle** — `reserve1 / reserve0` of a Uniswap V2 pool. Anyone with enough capital (or a flash loan) can push the price for one block. 2. **Manipulable TWAP** — short window TWAP, or TWAP over a low-liquidity pool. 3. **Single Chainlink feed without staleness check** — feed returns 0 / stale → uses 0 in math. 4. **Custom oracle reading from manipulable storage** — e.g., a "fair-price" oracle that reads `totalSupply()` of an LP token alongside reserves. 5. **L2 sequencer offline** — L2 oracles need a "is sequencer up?" check or attacker can exploit when sequencer downs and feeds freeze. ## Audit steps ### 1. Locate price-reading code ```bash # Common patterns grep -rn 'getReserves\|getAmountsOut\|getPriceFromSqrtPriceX96\|latestAnswer\|latestRoundData' src/ # Custom oracle reads grep -rn 'IPriceOracle\|getPrice\|consult' src/ ``` ### 2. Trace each price use For each call: - Is the price read from a Uniswap V2 / Sushi / Camelot pool's reserves? → spot price = manipulable - Is the price read from a Uniswap V3 pool's `slot0.sqrtPriceX96`? → manipulable - Is it a Uniswap V3 TWAP via `OracleLibrary.consult`? → check the secondsAgo window (>= 1800s = 30 min is the safe minimum) - Is it a Chainlink `latestRoundData()` call? → check: is `updatedAt` validated? Is `answeredInRound >= roundId`? Is `answer > 0`? Are L2 sequencer feeds checked? ### 3. Validate staleness handling ```solidity // MISSING — vulnerable (, int256 price, , , ) = priceFeed.latestRoundData(); // GOOD — explicit staleness + sequencer (uint80 roundId, int256 price, , uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData(); require(price > 0, "ORACLE_NEGATIVE"); require(updatedAt > block.timestamp - MAX_DELAY, "ORACLE_STALE"); require(answeredInRound >= roundId, "ORACLE_OLD_ROUND"); // On L2: also check sequencer uptime feed (L2 SequencerUptimeFeed) ``` ### 4. Trace the price's use The bug isn't oracle-reading — it's oracle-trusting. Find where the price drives a state change: - Liquidation thresholds - Collateral valuation - Borrow limits - Swap output amounts (slippage check) - LP token pricing for vaults ## PoC via Foundry ### Flash-loan price push (Uniswap V2 reserves) ```solidity // Pseudo: contract Test_oracle is Test { function test_manipulate() public { // 1. Flash-loan WETH from Aave / Balancer / Uniswap V3 // 2. Swap WETH → token in target pool, draining one side // 3. Reserves now skewed → spot price way off // 4. Call vulnerable protocol's price-dependent function // (e.g., borrow USDC against overvalued collateral) // 5. Reverse the swap, repay flash loan // 6. Profit = whatever was extracted in step 4 assertGt(USDC.balanceOf(attacker), 0, "should profit"); } } ``` Decepticon helper: ``` foundry_oracle_test(target="LendingPool", price_feed="UniV2Pair", token0="WETH", token1="TARGETTOKEN", target_path="src/LendingPool.sol") ``` ### Chainlink stale check missing ```solidity function test_stale_price() public { // mock the feed to return updatedAt that's 24h old vm.mockCall( address(priceFeed), abi.encodeWithSignature("latestRoundData()"), abi.encode(uint80(1), int256(STALE_PRICE), uint256(0), block.timestamp - 86400, uint80(1)) ); // call function — should revert if staleness checked, should proceed if not target.priceDependentFunction(); // If we reach here w/o revert → bug } ``` ## Severity calibration | Manipulation surface | Typical impact | Severity | |---|---|---| | Spot-price oracle drives liquidation | Drain LPs via fake liquidations | Critical | | Spot-price drives borrow limit | Borrow more than collateral worth | Critical | | TWAP < 30 min | Still manipulable on low-liquidity pairs | High | | TWAP 30 min+ on high-liquidity ETH-USDC | Hard to exploit profitably | Medium-Low | | Missing staleness check, but feed updates often | Edge-case impact during outages | Medium | | L2 sequencer not checked | Exploitable during sequencer downtime | High (timing-dependent) | ## CVSS - Spot-oracle + flash-loan available + drains protocol: `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H` = 10.0 (Crit) - 30-min TWAP on illiquid pair: 7-8 (High) - Missing staleness + Chainlink usually fresh: 5-6 (Medium) ## Defender remediation ```solidity // Use Chainlink properly, not spot-price function getPrice() internal view returns (uint256) { (uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = priceFeed.latestRoundData(); require(answer > 0, "neg price"); require(updatedAt > block.timestamp - 1 hours, "stale"); require(answeredInRound >= roundId, "old round"); // L2 only: (, int256 sequencerStatus, , uint256 sequencerStart, ) = sequencerFeed.latestRoundData(); require(sequencerStatus == 0, "sequencer down"); require(block.timestamp - sequencerStart > 1 hours, "grace period"); return uint256(answer); } // Where Chainlink unavailable: use 30+ min Uniswap V3 TWAP w/ deep-liquidity pool function getTwap(uint32 secondsAgo) internal view returns (uint160 sqrtPriceX96) { (int24 arithmeticMeanTick, ) = OracleLibrary.consult(pool, secondsAgo); return TickMath.getSqrtRatioAtTick(arithmeticMeanTick); } ``` ## Known exemplars - bZx Feb 2020: $645k via Synthetix sUSD spot-price oracle - Harvest Finance Oct 2020: $24M via Curve pool spot price - Cream Oct 2021: $130M via Yearn share-price manipulation - Mango Markets Oct 2022: $114M via MNGO spot price on AMM - Avraham Eisenberg's pattern, exactly: deposit small collateral, manipulate oracle, borrow against inflated value - Inverse Finance Apr 2022: $15M via INV/ETH spot price - Beanstalk Apr 2022: $182M via governance + flash-loan oracle (different but related)
GitHubで見る