1. Move语言常见漏洞
简单介绍一下move的常见漏洞:(接下来详细介绍的问题其实都属于这些基本漏洞中的一种或多种)
1.1 未验证输入
有些输入,在使用前未经过验证,比如允许你设置为2^64-1
,那么将导致匪夷所思的问题:
public entry fun check_access(score: u64) {
if (score+1 > 50) {
return false;
}else {
return true;
}
}
虽然例子有些离谱,但是确实是这样的。
1.2 逻辑错误
有的开发者在开发时,会遗漏临界条件,如:
public struct AccessControl has key {
id: UID,
is_allowed: bool,
threshold: u64
}
public entry fun check_access(access: &mut AccessControl, score: u64) {
if (score > 50) { // 应为 >= 50,但是设置为 >50
access.is_allowed = true;
}
}
当然更复杂的比如逻辑写反了等等,也是比较常见的。
public entry fun vote(
store: &mut VoteStore,
vote_coin: Coin<VOTE>,
proposal_name: String,
ctx: &mut TxContext
) {
assert!(vote_coin.value() > 0, E_INVALID_AMOUNT);
assert!(object_table::contains(&store.proposals, proposal_name), E_INVALID_PROPOSAL);
let proposal = object_table::borrow_mut(&mut store.proposals, proposal_name);
let sender = tx_context::sender(ctx);
let amount = coin::into_balance(vote_coin);
if (vec_map::contains(&store.voters, &sender)) {
let voter_amount = vec_map::get_mut(&mut store.voters, &sender);
*voter_amount = *voter_amount + amount.value();
} else {
vec_map::insert(&mut store.voters, sender, amount.value());
};
proposal.votes = proposal.votes + amount.value();
proposal.locked_tokens.join(amount);
}
public entry fun close_proposal(
store: &mut VoteStore,
proposal_name: String,
ctx: &mut TxContext
) {
assert!(object_table::contains(&store.proposals, proposal_name), E_INVALID_PROPOSAL);
let proposal = object_table::borrow_mut(&mut store.proposals, proposal_name);
let sender = tx_context::sender(ctx);
assert!(sender == proposal.owner, E_UNAUTHORIZED);
assert!(!proposal.closed, E_PROPOSAL_CLOSED);
proposal.closed = true;
let coin = coin::from_balance(proposal.locked_tokens.withdraw_all(), ctx);
public_transfer(coin, sender);
}
这个问题中,在close_proposal
后,仍然可以进行投票!!!
1.2.1 逻辑漏洞防御
检验状态一致性非常重要,上述例子中,投票前需要校验proposal
是否已经被关闭。
1.3 权限控制不足
函数未限制调用者身份,允许任何人执行敏感操作。
Move 的 public entry fun 默认对所有地址开放,需手动验证 tx_context::sender.
Sui 的共享对象尤其需注意权限。
比如:
public entry fun reset_counter(counter: &mut Counter) { // 未验证调用者
counter.count = 0;
}
导致任何人都可以重置计数器。
还有一个例子:
public entry fun close_proposal(
store: &mut VoteStore,
proposal_name: String,
ctx: &mut TxContext
) {
assert!(object_table::contains(&store.proposals, proposal_name), E_INVALID_PROPOSAL);
let proposal = object_table::borrow_mut(&mut store.proposals, proposal_name);
assert!(!proposal.closed, E_PROPOSAL_CLOSED);
proposal.closed = true;
let coin = from_balance(proposal.locked_tokens.withdraw_all(), ctx);
public_transfer(coin, tx_context::sender(ctx));
}
在这个代码中没有对proposal
进行管控,导致任何人都可以关闭proposal
。
1.3.1 权限控制漏洞防御
在一些敏感的功能中,需要添加权限访问机制,例如assert!(proposal.owner == tx_context::sender(ctx), E_UNAUTHORIZED);
来校验使用敏感功能的身份。或者直接一点,直接不公开敏感操作即可。
1.4 整数溢出/下溢
在 Sui 中,Move 的整数运算(如 u64 的加法、减法)默认启用溢出检查,溢出或下溢会导致交易失败.
module counter::counter{
use sui::event;
public struct Counter has key {
id: UID,
count: u64,
}
public struct CounterEmit has copy, drop{
count: u64,
}
fun init(ctx: &mut TxContext){
transfer::share_object(Counter { id:object::new(ctx), count: 0 });
}
public entry fun add(counter: &mut Counter,amount: u64){
counter.count = counter.count + amount;
event::emit(CounterEmit { count: counter.count })
}
public entry fun reduce(counter: &mut Counter,amount: u64){
counter.count = counter.count - amount;
event::emit(CounterEmit { count: counter.count })
}
}
1.5 资源管理不当
资源未正确转移或销毁,导致编译错误。这个属于编译错误,对安全性的影响倒不大。(对开发影响很大,非常影响体验)
当然还有其他资源管理不当的例子:
public entry fun vote(
store: &mut VoteStore,
vote_coin: &Coin<VOTE>,
proposal_name: String,
ctx: &mut TxContext
) {
assert!(vote_coin.value() > 0, E_INVALID_AMOUNT);
assert!(object_table::contains(&store.proposals, proposal_name), E_INVALID_PROPOSAL);
let sender = tx_context::sender(ctx);
if (!vec_map::contains(&store.voters, &sender)) {
vec_map::insert(&mut store.voters, sender, true);
};
let proposal = object_table::borrow_mut(&mut store.proposals, proposal_name);
proposal.votes = proposal.votes + vote_coin.value();
}
这段代码中,对于已经投过票的coin
对象,没有进行销毁,导致用户可以靠一个coin
对象进行对此投票。
1.5.1 资源管理漏洞防御方法
1、已经投过的coin
对象,要及时销毁
2、验证状态,验证coin
对象是否是有效的
3、最小化共享对象,share_object中的敏感数据要尽可能少(不然会被操纵)
2. 泛型类型安全
在Sui Move
中,泛型类型是由调用者在运行时提供的用户输入
。如果合约未验证泛型类型<T>
是否符合预期,攻击者可以传入任意类型,导致难以预想的问题。可以判断,其实泛型类型漏洞属于未验证输入的一种
一个典型的具有泛型参数的函数的例子如下:
public entry fun register_voter<T>(vote_coin: coin::Coin<T>, ctx: &mut TxContext) {
let amount = vote_coin.value();
assert!(amount == 100,1);
let sender = tx_context::sender(ctx);
let token = VoteToken<T> {
id: object::new(ctx),
amount: 100,
};
public_transfer(token, sender);
public_transfer(vote_coin, @0x0);
}
其中T是我们可控的参数,如果不进行限制,将导致:
伪造凭证:攻击者创建非法类型的对象(如 VoteToken<FakeToken>)绕过权限检查。
逻辑破坏:非预期类型导致合约状态异常,影响核心功能(如投票结果错误)。
资源滥用:攻击者利用伪造类型创建无效资源,干扰合约运行或耗尽 Gas.
2.1 泛型漏洞的抵御方法
最直接的抵御方法,是在使用前,进行校验:
use std::type_name;//std::type_name能够检测传入的泛型的完整类型名称(模块名::类型名称)
public entry fun register_voter<T>(ctx: &mut TxContext) {
assert!(type_name::get<T>() == type_name::get<votechain::OfficialToken>(), 3);
let sender = tx_context::sender(ctx);
let token = VoteToken<T> {
id: object::new(ctx),
amount: 100,
};
public_transfer(token, sender);
}
另外就是在开发时要注意不要有事没事就用泛型,使用泛型的话要注意显示的检查(白名单)就好了。
3. 跨合约交互安全
公开到链上的合约,能够被其他合约调用,详见:https://learnblockchain.cn/article/7623