分析 | YFValue,一行代码如何锁定上亿资产
前言
据链闻消息,DeFi 项目 YFValue (YFV)发布公告称,团队于昨日在 YFV 质押池中发现一个漏洞,恶意参与者借此漏洞对质押中的 YFV 计时器单独重置。目前已有一个恶意参与者正试图借此勒索团队。慢雾安全团队对此进行了深入分析,以下是相关技术细节。
细节分析
以上是 YFValue 的官方说明 (来源: https://medium.com/@yfv.finance/yfv-update-staking-pool-exploit-713cb353ff7d ),从声明中我们可以得知是 YFV 抵押池出现了问题,恶意的用户可重置 YFV 抵押者的计时器,对 YFV 的抵押者造成不便,但这并不会导致资金损失。
通过登陆 YFValue 的官方网站 ,( https://yfv.finance/staking ),可以发现在 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 函数提现收益。整个抵押和领取收益的简化流程如下:
分析了一大堆,回到我们最初的问题,恶意的用户是怎么锁定其他用户的资产的呢?
回到用户抵押的逻辑,可以发现抵押逻辑中的 stakeOnBehalf 函数本意是帮助进行抵押,但是这里有个问题,如果这个用户先前已经有抵押了呢?那通过对已经抵押的用户再次进行抵押,比方说抵押 1 个 YFV,是不是就能以极低的成本重置已抵押的用户的计时器,导致用户在 withdraw 时无法成功调用。更进一步,假设 YFV 抵押用户已经成功调用了 stakeReward 函数,在快要达到 unfrozenStakeTime 所规定的时间时,恶意的用户可以通过 stakeOnBehalf 函数给这个用户抵押少量资产,即可再次对抵押奖励进行锁定,理论上这样往复循环,即可使用户无法取出自己的资产,但这个问题并不会导致资金损失。攻击流程如下:
前车之鉴
这是本月出现的第二个没有经过审计的 DeFi 项目所暴露出的风险,根据 YFValue 的官方声明( https://medium.com/@yfv.finance/yfv-bringing-true-value-to-yield-farming-bddc4edf889a ),项目代码是由富有经验的开发者进行开发的,同时借鉴了其他成功的项目的代码,但是仍无可避免的出现了风险。 术业有专攻,安全审计一方面需要项目方的正向思维,另一方面,还是需要专业的安全团队的逆向思维,从专业的黑客角度进行模拟对抗,发现问题。
修复方案
通过分析代码和漏洞细节,针对本次漏洞,修复方案也很简单,只要在抵押的时候检查用户的抵押状态是否为已经抵押,如果已经抵押,则不允许再次抵押。或者对每次的抵押进行单独的处理,不能对先前的抵押状态产生影响。
위믹스3.0 최초의 DAO ‘원더다오’, 노드 카운슬 파트너 합류
위믹스3.0 최초의 DAO ‘원더다오’, 노드 카운슬 파트너 합류 l 탈중앙화 자율조직 형태로 결성…구성원 모두가 위믹스3.0 운영에 참여 가능l 스마트 컨트랙트 기반 모듈화 ...
[주간톡톡] 양의 탈을 쓴 늑대는 양일까? 늑대일까?
주간톡톡은 한주간의 블록체인 소식을 재구성해 독자들과 재미있게 이야기해보는 코너입니다. 이번주는 페이스북의 스테이블 코인 프로젝트 '디엠(Diem)'에 대해 알아보겠습니다. 그럼 ...
IBM, 블록체인 와인 추적 서비스 '빈어슈어(Assure)' 공개
IBM이 재배지부터 매장까지 와인 공급망을 추적할 수 있는 블록체인 기반 플랫폼을 공개했다.10일(현지시간) 발표에 따르면 IBM은 와인 모니터링 업체 e프로브넌스(eProven...