一篇文章看懂cosmos重委托漏洞
2021-05-29

摘要

Cosmos SDK 在 2019年 5 月底发现存在严重安全漏洞,该团队随后发布了补丁,并与全球验证人共同进行了升级,从收到漏洞报告到修复主网共用了72小时。

攻击者可利用该漏洞绕过委托金赎回时的锁定时间。

在介绍漏洞细节之前,先介绍一下cosmos的投票机制。

staking:投票机制

Cosmos中持币人可以将自己的货币委托给validator,当validator出块时,持币人获得收益。
当持币人遇到了佣金更低、网络状态更好的validator,不想继续委托原来的validator时。
可以unbond之前的validator。这需要等待21天。21天结束之后,委托的货币会被返还,然后持币人就可以委托其他validator了。

redelegation:一键更换validator

redelegation功能 使持币人不用等待21天的unbonding周期,可以立即更换至其他validators。
唯一的限制是21天时间内,最多更换7次validators。
目前 Cosmostation 钱包已经支持redelegation功能。
redelegation截图

漏洞介绍

Cosmos重委托漏洞,使得持币人在赎回委托金时,不必再等待21天的时间。
Cosmos 团队的 Jack Zampolin 分享了一个程序,可以发现那些利用漏洞提前撤回委托的恶意交易。
Jack Zampolin

该漏洞导致7250个Atom代币被提前赎回,当时每个Atom价值6美元。
截止2019年5月31日,使用上述恶意方法解除委托的 Atoms 大约为43,500美元。

补丁代码分析

Cosmos SDK 0.34.6 版本发布了重委托漏洞的补丁,并在区块高度 482100 时(大约2019年5月31日早上 10 点)生效。

cosmos重委托漏洞 补丁commit 链接如下:
https://github.com/cosmos/cosmos-sdk/commit/80234baf91a15dd9a7df8dca38677b66b8d148c1

代码修改的重点在于
github.com/cosmos/cosmos-sdk/x/staking/keeper/delegation.go,L585-L638:
Keeper.Undelegate函数
代码修改
可见Undelegate函数中592行做了一个硬分叉,分叉高度是UndelegatePatchHeight等于482100

在分叉高度前的代码中,有一处关于completeNow的判断

if completeNow {
	balance := sdk.NewCoin(k.BondDenom(ctx), returnAmount)
	if !balance.IsZero() {
		if _, err := k.bankKeeper.UndelegateCoins(ctx, delAddr, sdk.Coins{balance}); err != nil {
			return completionTime, err
		}
	}
	return completionTime, nil
}

如果completeNow等于 True,就直接退还委托的货币,而无需等待21天。
在Keeper.getBeginInfo函数中,我们发现
要使completeNow == True,需要validator.status == sdk.Unbonded
completeNow=True

不同的completeTime / completeNow

当持币人想要撤销对validator的委托时,需要等待completeTime后,委托金才会返还给持币人。
对于completeTime,不同情况有不同的值。

  • 如果validator此时是Bonded状态,赎回委托金需要经历21天的时间。
  • 如果validator处于UnBonding状态,持币人从该validator解绑定需要的锁定期等于该validator的锁定期(小于或等于21天)。
  • 如果validator处于UnBonded状态,则不需要锁定期,持币人抵押的股权可立即变回代币。这也就是上面代码中提到的validator.status == sdk.UnbondedcompleteNow == True

这样设计的本意的是,当validator节点因作恶或意外被Jail后,持币人可以尽快取回抵押的股权,避免造成损失。

漏洞利用方法

攻击者想要撤销对validatorA的委托,拿回委托金。但是又不想等待21天的unbonding。
于是攻击者使用cosmostation钱包提供的redelegation方法,将委托对象立刻转换为一个状态为Unbonded的validator。
接着再进行undelegate操作,Keeper.Undelegate函数收到completeNow == True
便调用k.bankKeeper.UndelegateCoins立刻归还了委托金。
攻击者便可以绕过锁定期限制,瞬间赎回委托金。

漏洞危害

漏洞不会造成用户的财产损失,但是可以让攻击者绕过21天的委托金锁定。
这会让cosmos设计的经济模型变得不稳定,破坏交易的公平。

总结

cosmos重委托漏洞的本质,是新功能redelegation和旧功能unbonded立刻赎回机制,两者的逻辑出现冲突。
在开发过程中,开发人员需要对新功能可能涉及到的旧代码有充分的了解。

参考资料

Cosmos staking module
Node A-Team 分析文章 Cosmos Hub Security Vulnerability regarding Redelegation and Unbonding
Cosmos攻略:Staking 新手实用工具指南
cosmos源码分析之四Staking模块
jessysaurusrex 记录的几个cosmos 1day
Cosmos的解绑定漏洞分析