Vulnerability disclosure
漏洞披露
I was searching on Immunefi for projects that piqued my interest, scanning contract after contract, the Tranchess protocol caught my eye. It has every component developed from the ground up, with quite a unique implementation on the risk/return matrix compared to other yield-farming protocols. This uniqueness of the codebase steered me to anticipate the presence of a protocol-specific type of bug that oftentimes leads to surprising damage.
我在 Immunefi 上搜索激起我兴趣的项目,扫描一份又一份的合同,Tranchess 协议引起了我的注意。它的每个组件都是从头开始开发的,与其他流动性挖矿协议相比,它在风险/回报矩阵上具有相当独特的实现。代码库的这种独特性使我预见到存在特定于协议的 bug,这种 bug 通常会导致令人惊讶的损害。
总结
Summary- On Oct. 30, 2023, at 09:50:00 UTC, an attack vector in the
ShareStaking
contract was disclosed. It was mitigated by the Tranchess team with a temporary fix within 18 hours of its receipt, the permanent solution was deployed 6 days later. User assets remain secure and there were no losses.
2023 年 10 月 30 日 09:50:00 UTC,ShareStaking
合约中的攻击媒介被披露。Tranchess 团队在收到后 18 小时内通过临时修复缓解了这种情况,6 天后部署了永久解决方案。用户资产保持安全,没有损失。 - As part of the security process, the Tranchess team has acknowledged and rewarded me with the maximum bounty of $200,000 from the Tranchess treasury.
作为安全流程的一部分,Tranchess 团队已经承认并奖励了我 Tranchess 金库的最高赏金 200,000 美元。 - The vulnerability originated from the omission of the
_checkpoint()
function, leading to a potential mismatch of the tokens’ balance theShareStaking
contract holds.
该漏洞源于该_checkpoint()
功能的遗漏,导致ShareStaking
合约持有的代币余额可能不匹配。 - An important note from the vulnerability for developers and security researchers is to pay close attention to any gas optimization techniques. A seemingly innocuous method to cut execution costs can at times cause dangerous behaviours.
对于开发人员和安全研究人员来说,该漏洞的一个重要注意事项是密切关注任何气体优化技术。一种看似无害的削减执行成本的方法有时会导致危险行为。
什么是 Tranchess,它是如何工作的?
What is Tranchess and how does it work?Tranchess is a yield-enhancing asset tracker protocol with varied risk-return solutions. It provides a different risk/return matrix out of a single main fund that tracks a specific underlying asset (e.g. BTC, ETH, BNB) or a basket of crypto assets.
Tranchess 是一种提高收益的资产跟踪协议,具有多种风险回报解决方案。它提供了跟踪特定标的资产(例如 BTC、ETH、BNB)或一篮子加密资产的单一主要基金的不同风险/回报矩阵。
The main fund is an asset tracking index fund. Queen’s Net Asset Value (NAV) tracks the underlying asset’s price on a fully correlated basis with deduction of protocol fees. Token Queen can be further split into/merge from two sub-tranches, token Bishop and token Rook. Token Rook leverages exposure to the main fund without forced liquidation risk. Token Bishop provides BUSD yield at a variable interest rate.
主要基金是资产跟踪指数基金。Queen’s Net Asset Value (NAV) 在扣除协议费用的基础上跟踪标的资产的价格。代币女王可以进一步拆分/合并为两个子部分,代币 Bishop 和代币 Rook。Token Rook 利用主要基金的敞口,没有强制平仓风险。Token Bishop 以可变利率提供 BUSD 收益。
漏洞的详细信息
Details of the vulnerabilityShareStaking.deposit()
The deposit()
function enables users to stake their Queen/Bishop/Rook tokens. The crucial variable, spareAmount
within this function is the amount of tokens the ShareStaking
contract has received for a given deposit, which is determined by calculating the disparity between token total supply of the ShareStaking
contract and the actual token balance the contract holds.
该 deposit()
功能使用户能够质押他们的 Queen/Bishop/Rook 代币。在这个函数中,关键变量是合约在给定存款中收到的代币数量, spareAmount
这是通过计算合约的代币总供应量与 ShareStaking
ShareStaking
合约持有的实际代币余额之间的差异来确定的。
function deposit(uint256 tranche, uint256 amount, address recipient, uint256 version) external {
_checkpoint(version);
_userCheckpoint(recipient, version);
_balances[recipient][tranche] = _balances[recipient][tranche].add(amount);
uint256 oldTotalSupply = _totalSupplies[tranche];
_totalSupplies[tranche] = oldTotalSupply.add(amount);
_updateWorkingBalance(recipient, version);
uint256 spareAmount = fund.trancheBalanceOf(tranche, address(this)).sub(oldTotalSupply);
if (spareAmount < amount) {
// Retain the rest of share token (version is checked by the fund)
fund.trancheTransferFrom(
tranche,
msg.sender,
address(this),
amount - spareAmount,
version
);
} else {
require(version == _fundRebalanceSize(), "Invalid version");
}
emit Deposited(tranche, recipient, amount);
}
Rebalance mechanism and ShareStaking._checkpoint()
再平衡机制和 ShareStaking._checkpoint()
A rebalance can be initiated in the FundV3
contract when the Fair Value ratio (ROOK/BISHOP) is below 0.5 or over 2. A rebalance will reset this ratio back to 1. Following a rebalance event, the balance of tokens in the ShareStaking
contract will change due to the adjustment of the fair value of token BISHOP and ROOK. This change involves an increase in the Q balance (information about the additional Q amount can be found here).
当公允价值比率 (ROOK/BISHOP) 低于 0.5 或高于 2 时,可以在 FundV3
合约中启动再平衡。重新平衡会将此比率重置回 1。在再平衡事件之后, ShareStaking
合约中的代币余额将因代币BISHOP和ROOK的公允价值调整而发生变化。此更改涉及 Q 余额的增加(有关额外 Q 金额的信息可在此处找到)。
The vulnerability stems from _checkpoint()
function, which is responsible for making a global reward checkpoint and updating the token total supplies based on the latest rebalance version.
该漏洞源于 _checkpoint()
函数,它负责建立全局奖励检查点,并根据最新的再平衡版本更新代币总供应量。
The _checkpoint()
will be skipped if we have called _checkpoint()
in the same block previously.
如果我们之前调用 _checkpoint()
了同一个块, _checkpoint()
则将被跳过。
function _checkpoint(uint256 rebalanceSize) private {
uint256 timestamp = _checkpointTimestamp;
if (timestamp >= block.timestamp) {
return;
}
...
}
Exploiting the vulnerability
利用此漏洞
In the transaction that triggers a rebalance, if the attacker calls _checkpoint
earlier and causes the subsequent checkpoint()
within deposit()
to be skipped, the spareAmount
value for the Queen tranche would become the amount of Queen tokens that is drainable from the ShareStaking
contract. This happens because the Queen total supply has not been synchronized with the ShareStaking
contract’s Queen balance of the most recent rebalance version stored in the FundV3
contract.
在触发再平衡的交易中,如果攻击者提前调用 _checkpoint
并导致跳过后续 checkpoint()
的 Within deposit()
,则 Queen 部分 spareAmount
的价值将成为可从 ShareStaking
合约中耗尽的 Queen 代币数量。发生这种情况的原因是 Queen 总供应量未与合约中存储的最新再平衡版本的 ShareStaking
FundV3
合约的 Queen 余额同步。
If the attacker has the fund, they can obtain Bishop and Rook tokens to deposit them into the ShareStaking
contract before the rebalance in order to increase the spareAmount
value (since the more Bishop and Rook tokens the ShareStaking
contract holds, the more Queen tokens it will receive after a rebalance).
如果攻击者拥有资金,他们可以获得 Bishop 和 Rook 代币,在重新平衡之前将它们存入合约中 ShareStaking
,以增加 spareAmount
价值(因为 ShareStaking
合约持有的 Bishop 和 Rook 代币越多,重新平衡后收到的 Queen 代币就越多)。
Otherwise, the summarized attack steps are as follows:
否则,汇总的攻击步骤如下:
-
The attacker monitors the underlying price and waits for the time when they can initiate a rebalance by calling
settle()
in theFundV3
contract, potentially employing frontrunning and private transaction services (accessible at https://bloxroute.com/ for BSC chain) to execute the rebalance before the Tranchess team does.
攻击者监控标的价格,并等待他们可以通过调用settle()
FundV3
合约来启动再平衡的时间,可能采用抢先和私人交易服务(可在 https://bloxroute.com/ 处访问 BSC 链)在 Tranchess 团队之前执行再平衡。 -
When the price reaches the rebalance threshold at 14:00 UTC (the settlement time), the attacker calls
claimableRewards()
in theShareStaking
contract with the argument of any address other than the attacker’s address (to prevent the transaction from reverting later due to subtraction overflow). The purpose of this call is to invoke_checkpoint()
in theShareStaking
contract, causing it to skip the subsequent_checkpoint()
when we invoke later in the same transaction indeposit()
.
当价格在 UTC 时间 14:00(结算时间)达到再平衡阈值时,攻击者会调用claimableRewards()
ShareStaking
合约中除攻击者地址以外的任何地址的参数(以防止交易稍后因减法溢出而恢复)。此调用的目的是_checkpoint()
在ShareStaking
合约中调用,当我们稍后在 中调用同一事务时,它会跳过后续_checkpoint()
deposit()
调用。 -
The attacker proceeds to call
settle()
in theFundV3
contract, triggering a rebalance.
攻击者继续调用settle()
FundV3
合约,触发重新平衡。 -
The attacker calls
deposit()
function in theShareStaking
contract to deposit tranche Q, with theamount
argument being precomputed and equal to thespareAmount
value within thedeposit()
function.
攻击者调用deposit()
ShareStaking
合约中的函数来存入 Q 部分,amount
参数是预先计算的,并且等于deposit()
函数中的spareAmount
值。 -
Finally, the attacker withdraws and redeem the drained Q to obtain underlying tokens, successfully drains users’ funds.
最后,攻击者提现并赎回流出的Q,获得底层代币,成功抽干用户资金。
概念验证
Proof of conceptCheck out the POC here
在此处查看 POC
冲击
ImpactWhenever the condition is right for the rebalance to happen, an attacker can directly steal funds from existing stakers. The impact of this attack depends on the size of the attacker’s fund:
只要条件适合再平衡发生,攻击者就可以直接从现有质押者那里窃取资金。此攻击的影响取决于攻击者资金的规模:
-
If the attacker possesses a fund, the maximum total loss is approximately 815.1 BTC and 1438.5 ETH on BSC chain, based on the on-chain funds at the time of reporting.
如果攻击者拥有资金,则根据报告时的链上资金,BSC 链上的最大总损失约为 815.1 BTC 和 1438.5 ETH。 -
If the attacker does not possess any fund, the total loss is approximately 46.8 BTC and 156.2 ETH on BSC chain, based on the on-chain funds at the time of reporting.
如果攻击者没有任何资金,则根据报告时的链上资金,BSC 链上的总损失约为 46.8 BTC 和 156.2 ETH。
Following the attack, the ShareStaking
contract within the Tranchess protocol will become insolvent, and the deposit()
function in the contract will always revert due to subtraction overflow.
攻击发生后,Tranchess 协议内的合约将资不抵债, ShareStaking
合约中的 deposit()
函数将始终因减法溢出而恢复。
Additionally, the accounting of _workingSupply
and _totalSupplies
for the three tranches in the ShareStaking
will be perpetually miscalculated.
此外,这 ShareStaking
三部分的会计核 _workingSupply
_totalSupplies
算将永远被错误计算。
缓解
MitigationFor a detailed explanation of the mitigation, please refer to the Tranchess team’s publication.
有关缓解措施的详细说明,请参阅 Tranchess 团队的出版物。
确认
AcknowledgementI would like to thank the Tranchess team for their decision to reward me with the maximum $200k bounty in recognition of the disclosure. Their quick responses throughout the process also show the team’s commitment to putting security first and maintaining a high level of integrity with whitehats.
我要感谢 Tranchess 团队决定奖励我最高 20 万美元的赏金,以表彰我的披露。他们在整个过程中的快速反应也表明了团队将安全放在首位并保持高度诚信的承诺。
原文始发于Github:GitHub – floranguyen0/tranchess-vulnerability-disclosure