2024 -11-20 sui 存钱罐的权限控制和pacakge升级带来的风险

1.概述

  • 假设写一个存钱罐合约,可以从合约中存入货币,提取货币,
    • 存钱不控制权限,每个人都能存
    • 只有拥有AdminCap对象的人才能取钱。
      • 取钱函数有一个AdminCap参数,sui 判断谁拥有Admincap 对象才能调用 withdraw
    • 合约构造者A可以将AdminCap对象 转让给B
      • 表面上只有B 能获得权限取钱
  • 风险
    • 合约构造者还拥有升级合约的权限,可能通过升级合约来获取新的取钱权限

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
module upgrade_demo::upgrade_demo ;
use sui::object::{Self, UID};
use sui::transfer;
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin};
use sui::sui::SUI;
use sui::tx_context::{Self, TxContext};

// 错误码
const ENotAdmin: u64 = 0;
const EInsufficientBalance: u64 = 1;

// 管理员权限凭证
public struct AdminCap has key, store {
id: UID
}

// 金库结构
public struct Treasury has key {
id: UID,
balance: Balance<SUI>
}

// 初始化函数 - 创建管理员凭证和金库
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx)
};

let treasury = Treasury {
id: object::new(ctx),
balance: balance::zero(),
};

// 将AdminCap转移给部署合约的地址
transfer::transfer(admin_cap, tx_context::sender(ctx));
// 共享金库对象
transfer::share_object(treasury);
}

// 存入SUI代币
public fun deposit(treasury: &mut Treasury, payment: Coin<SUI>) {
sui::balance::join(&mut treasury.balance, payment.into_balance());

}

// 管理员提取SUI代币
public fun withdraw(
treasury: &mut Treasury,
amount: u64,
_admin_cap: &AdminCap,
ctx: &mut TxContext
): Coin<SUI> {
// 检查余额是否充足
assert!(balance::value(&treasury.balance) >= amount, EInsufficientBalance);

// 从金库中提取代币
coin::from_balance(balance::split(&mut treasury.balance, amount), ctx)
}

// 查询金库余额
public fun balanceOf(treasury: &Treasury): u64 {
balance::value(&treasury.balance)
}

3. 发布合约

  • 获得对象
1
sui move   --skip-fetch-latest-git-deps  publish
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
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323791 │
│ │ Digest: BLp2DjwNMD1fZ4Y4HxNMRz1AQq28afgMQ57pNFt4BZoa │
│ └──

│ ┌── │
│ │ ObjectID: 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 245323791 │
│ │ Di


┌── │
│ │ ObjectID: 0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Shared( 245323791 ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::Treasury │
│ │ Version: 245323791 │
│ │ Digest: Enq9zenKrY4r3fXYEWr9XJCcA8FixSTZhKbVqXqDueNM
  • 对象说明
对象名 取值 说明
package address 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89 合约包的地址
upgradeCap 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 控制升级的权限
adminCap 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf 控制提取Treasury余额
owner 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c 目前合约发布者拥有adminCap,upgradeCap
treasury 0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c 存钱罐
  • 查看当前用户的coin

    {.line-numbers}
    1
    2
    3
    4
    5
    6
    7
    8
    sui client gas
    ╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
    │ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
    ├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
    │ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 470927364 │ 0.47 │
    │ 0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22 │ 497664604 │ 0.49 │
    │ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
    ╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

4. A用户存钱

  • 配置一些变量,方便后面的脚本使用
1
2
3
4
5
6
export PKG=0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
export ADMIN_CAP=0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf
export UPGRADE_CAP=0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0
export TREASURY=0x830243c107ff3ad19e4506d9a61d04af4a0c35771445e468c3a7befbc6d9293c
export ADDR_A=0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c
export ADDR_B=0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看当前用户有哪些coin
sui client switch --address $ADDR_A
sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 470927364 │ 0.47 │
│ 0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22 │ 497664604 │ 0.49 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

# 根据前面的输出,设置需要存入的coin
export COIN_A=0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22
# 存入货币
sui client ptb --move-call $PKG::upgrade_demo::deposit "@$TREASURY,@$COIN_A"

5. A 用户取钱

1
$ sui client ptb --move-call $PKG::upgrade_demo::deposit   "@$TREASURY @0xae3da3cb598610db16ea040ad6b09cfd324e146a2a7e5b18ccfa76b34b8d3b22"
  • 取回5000单位

    1
    2
    3

    sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP"
    sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_A
  • 查看当前A用户拥有的coin

1
2
3
4
5
6
7
8
sui client gas $ADDR_A
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 466808880 │ 0.46 │
│ 0xd91123cfc325ba1eff95e839837a1bf443046cc00a72da5192a005ffea1a7117 │ 50000 │ 0.00 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

6. A 用户转移adminCap 给B

1
2
3
4
5
6
7
8
9
10
11
12
sui client ptb --transfer-objects [@$ADMIN_CAP] @$ADDR_B

# 显示ADMIN_CAP 对象的owner是0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432

│ ┌── │
│ │ ObjectID: 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432 ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323796 │
│ │ Digest: 6QpfZqgrTFjTcaySHPrfS5cDE1K1L5xvuWDQRxh1vumS │
│ └──

7. A用户 再次取款,预期会失败

1
2
sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_A --gas-budget 30000000
RPC call failed: ErrorObject { code: ServerError(-32002), message: "Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n- Transaction was not signed by the correct sender: Object 0x10ca605ce0437c10274bcc260901ef5b34bab91c747f2dc498b722ae151a5baf is owned by account address 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432, but given owner/signer address is 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c\n- Could not find the referenced object 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89 at version None", data: None }

8. 切换B 用户,取款成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 切换到ADDR_B
$ sui client switch --address $ADDR_B
Active address switched to 0x7cbe5e6596e23266dd5763dd89b4ab1195516908ecde8febfe96685c7cbe6432
# 调用withdraw取款
$ sui client ptb --move-call $PKG::upgrade_demo::withdraw "@$TREASURY 50000 @$ADMIN_CAP" --assign coin1 --transfer-objects [coin1] @$ADDR_B

# 查看获得50000 mist的coin
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x015a3cb7a04cdcd7631e4056f56e8a55b77a1a7d05dc7da6f30e9ac705d19b7c │ 50000 │ 0.00 │
│ 0x418760c10d6fe9ecaa2594803848412c834f58983f1be03b26962a22dd84f9e5 │ 995949216 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯

9. A 用户发起攻击,升级合约包, 新建AdminCap对象adminCap2

9.1 修改合约代码,新构造AdminCap对象

1
2
3
public fun mintCap(ctx: &mut TxContext): AdminCap {
AdminCap{id: object::new(ctx)}
}

9.2 升级合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 切换为A
sui client switch --address $ADDR_A
Active address switched to 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c
# A 升级合约
sui client upgrade --upgrade-capability $UPGRADE_CAP --gas-budget 100000000 --skip-fetch-latest-git-deps
# 升级对象变更,package版本增长 ,版本id 变成了 0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869
老的版本id是0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
│ ┌── │
│ │ ObjectID: 0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 245323797 │
│ │ Digest: 64rX9ChLmX5mupim2UqRGUoKnHD3wTiiYg4uXxzjvppc │
│ └── │
│ Published Objects: │
│ ┌── │
│ │ PackageID: 0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869 │
│ │ Version: 2 │
│ │ Digest: 8aWACYMu9nLjAG3MHQGteXMsNiBUY5xCt6KhKmMEmrTX │
│ │ Modules: upgrade_demo │
│ └──
  • 新发布的的包 id发生改变

    0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915bb0

9.3 发起攻击

用户用新创建的adminCap2 来取钱

初始的package

image-20241120130414556

  • 1 pacakge object id
  • 2 版本
  • 3 代码中的包地址。

升级后的packageid发生变更

image-20241120125933911

  • 1 package object id
  • 2 版本
  • 3 代码中的package地址

但是从代码看,package地址 还是最初发布的地址,version=2, 代码module后面的地址不变。 但是objectid发生变更(图1)

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
$  export PKG2=0xc163e33f376cae7a90b0c56b49af9a0aa387c210792d6c76aba0a12a22de8869
$ sui client ptb --move-call $PKG2::upgrade_demo::mintCap --assign new_cap --transfer-objects ["new_cap" ] @$ADDR_A

Created Objects: │
│ ┌── │
│ │ ObjectID: 0xb71d4b11e35d769bcdefa42f9706af118b76b2ab2896fe2a294639e19f52d816 │
│ │ Sender: 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c │
│ │ Owner: Account Address ( 0x6560a053cd8d98925b33ab2b951d656736d0133734def0b5d679402fc555576c ) │
│ │ ObjectType: 0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89::upgrade_demo::AdminCap │
│ │ Version: 245323798 │
│ │ Digest: 8xio19V93oLgmF8jLxWKoCfgCpSitFJqYLLLTbN9Wc77 │
│ └──
$ export ADMIN_CAP2=0xb71d4b11e35d769bcdefa42f9706af118b76b2ab2896fe2a294639e19f52d816
$ sui client ptb --move-call $PKG2::upgrade_demo::withdraw "@$TREASURY 6000 @$ADMIN_CAP2" \
--assign coin2 --transfer-objects [coin2] @$ADDR_A
# 偷取成功,获得一个6000mist的coin
$ sui client gas $ADDR_A
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x09119914c43f80a486b30cc5d4f1382d0b57929eabd70f116ae4e918d9c89bf0 │ 450920664 │ 0.45 │
│ 0x33233ad9e71a8ef079b7c1c58ac9c99a263d04620f585d37e1ae94b4bd64d538 │ 6000 │ 0.00 │
│ 0x64502383e84a24dda2e531ec0cac4d8123c9c4d7820451a1b42a02eae1888bea │ 50000 │ 0.00 │
│ 0xd91123cfc325ba1eff95e839837a1bf443046cc00a72da5192a005ffea1a7117 │ 50000 │ 0.00 │
│ 0xeda78a0ba9b2ef153a209f51380d8e77af0f35c60eb8ea26d4239c639844680d │ 995399888 │ 0.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
1
2
3
4
export PKG=0x38417a57a4f061487895b2c97b3e188e847c25de37762c5e0146bd6a32cd5a89
export UPGRADE_CAP=0x67a5815e053ab8be8248c894ec288baf79bc6f323321812074cac70e63915嗯嗯

$ sui client upgrade --upgrade-capability $UPGRADE_CAP --package-id $PKG --gas-budget 100000000

对策:

方法1

  • 转移权限时,将UpgradeCap也转移

方法2

  • 在 Treasury 中添加adminCap的id。

  • 在取款的时候做校验

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

// 金库结构
public struct Treasury has key {
id: UID,
balance: Balance<SUI>,
+ admin_id : ID,
}

// 初始化函数 - 创建管理员凭证和金库
fun init(ctx: &mut TxContext) {
let admin_cap = AdminCap {
id: object::new(ctx)
};

let treasury = Treasury {
id: object::new(ctx),
balance: balance::zero(),
+ admin_id: *admin_cap.id.as_inner(),
}
...


// 管理员提取SUI代币
public fun withdraw(
treasury: &mut Treasury,
amount: u64,
admin_cap: &AdminCap,
ctx: &mut TxContext
): Coin<SUI> {
+ assert!(admin_cap.id.as_inner() == treasury.admin_id, ENotAdmin);
...

附录:

最近在参加HOH 共学活动,

💧 HOH水分子公众号

🌊 HOH水分子X账号

📹 课程B站账号

💻 Github仓库 https://github.com/move-cn/letsmove