Skip to main content

flash-loan

Flash-loan exploit patterns — callback reentrancy, oracle amplification, governance attacks, unauthenticated callback handlers.

Jump to install

Source facts

Repository
BitterSecurity/Decepticon
Last source activity
June 2, 2026 at 17:25
Detected SKILL.md language
English
Stars
5,611
Forks
1,061

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
flash-loan
description
Flash-loan exploit patterns — callback reentrancy, oracle amplification, governance attacks, unauthenticated callback handlers.
metadata
{"subdomain":"smart-contracts","when_to_use":"flash loan aave dydx balancer composition","mitre_attack":["T1190","T1565.001"]}
# Flash Loan Attack Playbook Flash loans give the attacker uncollateralized capital for a single transaction. They're not vulnerabilities themselves — they're a force multiplier for existing bugs. Sources: Aave, Balancer, Uniswap V2/V3, Maker, dYdX (deprecated). ## Common attack patterns ### 1. Oracle amplification (most common) Use loan to push a price, then trigger price-dependent action. **Cross-reference**: see `oracle-manipulation/SKILL.md`. ### 2. Unauthenticated callback handler ```solidity function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, // ← UNVALIDATED in many contracts bytes calldata params ) external returns (bool) { // Anyone can call this with fake `initiator` // and have the contract do whatever the params say } ``` **Bug**: `initiator` and `msg.sender == pool` checks are missing. **Attack**: call `executeOperation` directly with no actual loan, malicious `params` → contract does the operation anyway. ### 3. Governance attack via flash loan ```solidity // Vulnerable governance: function propose() external { require(getVotes(msg.sender) > THRESHOLD); // ... } function getVotes(address user) public view returns (uint256) { return token.balanceOf(user); // ← reads SPOT balance, not snapshot } ``` **Attack**: flash-loan governance tokens, propose malicious change (e.g., upgrade contract to drain), vote with the loaned tokens, execute, repay loan. **MakerDAO** had this pattern; mitigated by checkpoint-based voting. ### 4. Liquidity manipulation Pool that uses `totalSupply()` or `balanceOf(pool)` for share math: ```solidity function deposit(uint256 amt) external { uint256 shares = (amt * totalSupply()) / underlying.balanceOf(address(this)); _mint(msg.sender, shares); underlying.transferFrom(msg.sender, address(this), amt); } ``` Attacker: 1. Flash-loan and deposit (becomes 99% of pool) 2. Donate underlying to inflate `underlying.balanceOf` artificially 3. Next depositor's shares = `amt * totalSupply / inflatedBalance` ≈ 0 4. Withdraw → drain This is the **ERC4626 inflation attack** (also covered in `reentrancy/` overlap). ### 5. Liquidation arbitrage gone wrong A liquidator that uses flash loans to repay debt before claiming collateral — usually benign. The bug: liquidation health check happens *before* the seizure, but the seizure happens via callback into a manipulable function. Reentrancy + oracle dependent. ## Audit steps ### 1. Locate flash-loan integrations ```bash grep -rn 'executeOperation\|flashLoan\|onFlashLoan\|flashCallback\|aaveFlashLoan' src/ ``` ### 2. For each handler Check: - `msg.sender == known_pool` (whitelist callback origin) - `initiator == address(this)` (only respond to your own loans) - ReentrancyGuard on the public function that *takes* the loan - The fee is correctly accounted (`amounts[i] + premiums[i]` is repaid) ### 3. For each price-reading function See `oracle-manipulation/SKILL.md`. ### 4. For each share-mint / share-burn function Check `ERC4626` virtual-shares mitigation: ```solidity // OpenZeppelin ERC4626 v4.7+ function _convertToShares(uint256 assets, MathUpgradeable.Rounding rounding) internal view virtual override returns (uint256) { return _initialConvertToShares(assets, rounding); // adds 10**18 to total supply for share calc } ``` If using legacy ERC4626 or custom vault math, write a Foundry test that: 1. Attacker deposits 1 wei → gets 1 share 2. Attacker direct-transfers 1e18 underlying to vault 3. Victim deposits 1e18 → gets 0 shares (rounding) 4. Attacker withdraws 1 share → gets 100% of pool ``` foundry_inflation_test(vault_address="...", underlying="...", target_path="src/Vault.sol") ``` ## Severity calibration | Pattern | Severity if confirmed | |---|---| | Unauth callback handler → arbitrary execution | Critical 10.0 | | Flash-loan oracle manipulation drains protocol | Critical 9-10 | | Governance attack possible | Critical 9-10 (if exec results in fund loss) | | ERC4626 inflation attack on live vault | Critical 9 | | Liquidation reentrancy via callback | High 8 | | Theoretical (low liquidity, manipulation cost > attack profit) | Medium-Low | ## PoC template (Foundry) ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "@aave-v3/interfaces/IPool.sol"; contract Test_FlashAttack is Test { IPool constant AAVE = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); address constant TARGET = 0x...; address constant ATTACKER = address(0xBEEF); function setUp() public { vm.createSelectFork("https://eth-mainnet.../<block>"); } function test_drain() public { vm.startPrank(ATTACKER); uint256 beforeBal = WETH.balanceOf(ATTACKER); // Request 10000 WETH flash loan address[] memory assets = new address[](1); assets[0] = address(WETH); uint256[] memory amts = new uint256[](1); amts[0] = 10000 ether; uint256[] memory modes = new uint256[](1); // 0 = repay full AAVE.flashLoan(address(this), assets, amts, modes, address(this), "", 0); uint256 afterBal = WETH.balanceOf(ATTACKER); assertGt(afterBal, beforeBal + 100 ether, "should profit"); } function executeOperation( address[] calldata assets, uint256[] calldata amts, uint256[] calldata premiums, address initiator, bytes calldata ) external returns (bool) { // 1. Use loaned funds to manipulate TARGET // 2. Profit // 3. Repay IERC20(assets[0]).approve(address(AAVE), amts[0] + premiums[0]); return true; } } ``` ## OPSEC (operational, not blue team — this is on-chain) Flash-loan attacks leave a single transaction trail visible to: - Mempool watchers / Flashbots inspectors - MEV bots that may front-run if not using private mempool - Risk dashboards (DefiLlama, Forta, Cyfrin) For audit-PoC purposes (not live exploit) just use a forked anvil. ## Defender remediation 1. **Snapshot-based governance** — vote based on historical balance, not spot 2. **TWAP oracles only** — never spot price for trust-critical decisions 3. **Validate flash callback origin** — `require(msg.sender == knownPool && initiator == address(this))` 4. **ERC4626 OpenZeppelin v4.7+** — virtual shares mitigation 5. **Initial deposit by deployer** — seed the vault with 10**18 underlying before opening, makes inflation impractical ## Known exemplars - bZx 1 (Feb 2020): $350k flash-loan oracle attack - bZx 2 (Feb 2020): $645k Synthetix sUSD attack - Beanstalk (Apr 2022): $182M flash-loan governance attack - Euler (Mar 2023): $197M; donateToReserves bug + flash loan - Mango (Oct 2022): $114M oracle + flash loan - Cream (Oct 2021): $130M via yUSD oracle - KyberSwap (Nov 2023): $48.5M via concentrated-liquidity edge case + flash loan
View on GitHub