客服QQ:872490018

慢雾:YFValue,一行代码如何锁定上亿资产

By : yudan@慢雾安全团队


前言

据链闻消息,DeFi 项目 YFValue (YFV)发布公告称,团队于昨日在 YFV 质押池中发现一个漏洞,恶意参与者借此漏洞对质押中的 YFV 计时器单独重置。目前已有一个恶意参与者正试图借此勒索团队。慢雾安全团队对此进行了深入分析,以下是相关技术细节。


细节分析

慢雾:YFValue,一行代码如何锁定上亿资产-碳链

以上是 YFValue 的官方说明 (来源:https://medium.com/@yfv.finance/yfv-update-staking-pool-exploit-713cb353ff7d),从声明中我们可以得知是 YFV 抵押池出现了问题,恶意的用户可重置 YFV 抵押者的计时器,对 YFV 的抵押者造成不便,但这并不会导致资金损失。


通过登陆 YFValue 的官方网站 ,(https://yfv.finance/staking),可以发现在 YFValue 的体系中,用户可通过质押相关的代币获取对应的奖励,目前 YFValue 支持的质押代币池有以下几个:


慢雾:YFValue,一行代码如何锁定上亿资产-碳链

可以看到,目前由于漏洞的原因,YFV 的抵押池已经在 UI 界面关闭了抵押功能,但是合约上目前还没关闭代币抵押的功能,我们需要跟踪代码来分析具体的细节点。


根据官网提供的 Github 地址,我们溯源到了相关的代码仓库(https://github.com/yfv-finance/audit),关于 YFV 抵押的相关逻辑在YFV_Stake.sol 合约中,合约中关于抵押的函数有 2 个,分别是 stake 函数和 stakeOnBehalf 函数,以下是具体的代码:

 function stake(uint256 amount, address referrer) public updateReward(msg.sender) checkNextEpoch {        require(amount > 0, "Cannot stake 0");        require(referrer != msg.sender, "You cannot refer yourself.");        super.tokenStake(amount);        lastStakeTimes[msg.sender] = block.timestamp;        emit Staked(msg.sender, amount);        if (rewardReferral != address(0) && referrer != address(0)) {            IYFVReferral(rewardReferral).setReferrer(msg.sender, referrer);        }    }
   function stakeOnBehalf(address stakeFor, uint256 amount) public updateReward(stakeFor) checkNextEpoch {        require(amount > 0, "Cannot stake 0");        super.tokenStakeOnBehalf(stakeFor, amount);        lastStakeTimes[stakeFor] = block.timestamp;        emit Staked(stakeFor, amount);   }

通过代码不难发现,无论是 stake 函数还是 stakeOnBehalf 函数,逻辑基本是一样的,首先是校验了抵押金额不能为0,接着分别调用上层的 tokenStake 和 tokenStakeOnBehalf 函数。紧接着更新用户的抵押时间。只不过 stakeOnBehalf 函数可以用于为他人抵押。tokenStake 和 tokenStakeOnBehalf 的代码如下:

function tokenStake(uint256 amount) internal {        _totalSupply = _totalSupply.add(amount);        _balances[msg.sender] = _balances[msg.sender].add(amount);        yfv.safeTransferFrom(msg.sender, address(this), amount);    }
function tokenStakeOnBehalf(address stakeFor, uint256 amount) internal { _totalSupply = _totalSupply.add(amount); _balances[stakeFor] = _balances[stakeFor].add(amount); yfv.safeTransferFrom(msg.sender, address(this), amount); }

可以看到这里只是简单的把对应的 token 用 transferFrom 的方式转入到合约中,没有什么特别的逻辑点。到这里整个抵押流程就很清晰了,接下来是收益的过程。计算用户收益的是 stakeReward 函数,领取收益的为 withdraw 函数,代码分别如下:

function stakeReward() public updateReward(msg.sender) checkNextEpoch {        uint256 reward = getReward();        require(reward > 0, "Earned too little");        super.tokenStake(reward);        lastStakeTimes[msg.sender] = block.timestamp;        emit Staked(msg.sender, reward);    }        function withdraw(uint256 amount) public updateReward(msg.sender) checkNextEpoch {        require(amount > 0, "Cannot withdraw 0");        require(block.timestamp >= unfrozenStakeTime(msg.sender), "Coin is still frozen");        super.tokenWithdraw(amount);        emit Withdrawn(msg.sender, amount);    }

通过分析计算收益和领取收益的代码,发现逻辑也很简单,stake 函数首先是通过 updateReward 修饰器更新了用户的奖励,然后使用 getReward 函数计算了用户的奖励,并把抵押时间设置成当前区块时间。最后,用户在提取奖励的时候,withdraw 函数会首先计算当前的区块时间,再与 unfrozenStakeTime 函数中计算出的时间进行对比,只有当前区块时间大于 unfrozenStakeTime 计算出的时间,才允许提现。unfrozenStakeTime 的代码如下:

 function unfrozenStakeTime(address account) public view returns (uint256) {        return lastStakeTimes[account] + FROZEN_STAKING_TIME;    }

从代码中得知,unfrozenStakeTime 是使用用户的上次抵押时间加上 FROZEN_STAKING_TIME 常量得出锁定时间,只要超过时间,就能通过 withdraw 函数提现收益。整个抵押和领取收益的简化流程如下:

慢雾:YFValue,一行代码如何锁定上亿资产-碳链

分析了一大堆,回到我们最初的问题,恶意的用户是怎么锁定其他用户的资产的呢?


回到用户抵押的逻辑,可以发现抵押逻辑中的 stakeOnBehalf 函数本意是帮助进行抵押,但是这里有个问题,如果这个用户先前已经有抵押了呢?那通过对已经抵押的用户再次进行抵押,比方说抵押 1 个 YFV,是不是就能以极低的成本重置已抵押的用户的计时器,导致用户在 withdraw 时无法成功调用。更进一步,假设 YFV 抵押用户已经成功调用了 stakeReward 函数,在快要达到 unfrozenStakeTime 所规定的时间时,恶意的用户可以通过 stakeOnBehalf 函数给这个用户抵押少量资产,即可再次对抵押奖励进行锁定,理论上这样往复循环,即可使用户无法取出自己的资产,但这个问题并不会导致资金损失。攻击流程如下:

慢雾:YFValue,一行代码如何锁定上亿资产-碳链

前车之鉴

这是本月出现的第二个没有经过审计的 DeFi 项目所暴露出的风险,根据 YFValue 的官方声明(https://medium.com/@yfv.finance/yfv-bringing-true-value-to-yield-farming-bddc4edf889a),项目代码是由富有经验的开发者进行开发的,同时借鉴了其他成功的项目的代码,但是仍无可避免的出现了风险。术业有专攻,安全审计一方面需要项目方的正向思维,另一方面,还是需要专业的安全团队的逆向思维,从专业的黑客角度进行模拟对抗,发现问题。


修复方案

通过分析代码和漏洞细节,针对本次漏洞,修复方案也很简单,只要在抵押的时候检查用户的抵押状态是否为已经抵押,如果已经抵押,则不允许再次抵押。或者对每次的抵押进行单独的处理,不能对先前的抵押状态产生影响。

 

往期回顾

PlatON 入驻慢雾区,发布「安全漏洞与威胁情报赏金计划」

HBTC 入驻慢雾区,发布「安全漏洞与威胁情报赏金计划」

BigONE 入驻慢雾区,发布「安全漏洞与威胁情报赏金计划」

DeFi YAM,一行代码如何蒸发数亿美元?

BKEX 入驻慢雾区,发布「安全漏洞与威胁情报赏金计划」


慢雾:YFValue,一行代码如何锁定上亿资产-碳链

慢雾导航

慢雾科技官网

https://www.slowmist.com/

慢雾区官网

https://slowmist.io/

慢雾 GitHub

https://github.com/slowmist

Telegram

https://t.me/slowmistteam

Twitter

https://twitter.com/@slowmist_team

Medium

https://medium.com/@slowmist

币乎

https://bihu.com/people/586104

知识星球

https://t.zsxq.com/Q3zNvvF

火星号

http://t.cn/AiRkv4Gz

链闻号

https://www.chainnews.com/u/958260692213.htm

*文章为作者独立观点,不代表碳链立场
本文由 慢雾科技 授权 碳链 发表,并经碳链编辑。转载此文章须经作者同意,并请附上出处(碳链)及本页链接。原文链接http://www.itanlian.com/chainnews/4032.html
发表评论
坐等沙发
相关文章
如何解读中国加快推进区块链基础设施建设的意义?
如何解读中国加快推进区块链基础设施建…
YFI创始人:做DeFi让我心生畏惧,请谨慎使用
YFI创始人:做DeFi让我心生畏惧,请谨慎…
慢雾为香港浸会大学金融硕士课程赞助“慢雾网络安全奖”​
慢雾为香港浸会大学金融硕士课程赞助“慢…
一个知名区块链游戏工作室是如何倒闭的?
一个知名区块链游戏工作室是如何倒闭的?
提问有奖·波卡是否有机会超越以太坊?
提问有奖·波卡是否有机会超越以太坊?
DeFi热潮下的安全隐患:流动性危机恐将造成连锁反应 | 非正式会谈
DeFi热潮下的安全隐患:流动性危机恐将…
慢雾科技
SlowMist-Team 作者
我还没有学会写个人说明!
  • 文章

    115

  • 评论

    0

广告赞助