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 the ShareStaking 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.

What is Tranchess and how does it work?
什么是 Tranchess,它是如何工作的?

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 vulnerability


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 {
        _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)
                amount - spareAmount,
        } 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) {

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 spareAmountvalue (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:

  1. The attacker monitors the underlying price and waits for the time when they can initiate a rebalance by calling settle() in the FundV3 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 团队之前执行再平衡。

  2. When the price reaches the rebalance threshold at 14:00 UTC (the settlement time), the attacker calls claimableRewards() in the ShareStaking 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 the ShareStaking contract, causing it to skip the subsequent _checkpoint() when we invoke later in the same transaction in deposit().
    当价格在 UTC 时间 14:00(结算时间)达到再平衡阈值时,攻击者会调用 claimableRewards() ShareStaking 合约中除攻击者地址以外的任何地址的参数(以防止交易稍后因减法溢出而恢复)。此调用的目的是 _checkpoint() 在 ShareStaking 合约中调用,当我们稍后在 中调用同一事务时,它会跳过后续 _checkpoint() deposit() 调用。

  3. The attacker proceeds to call settle() in the FundV3 contract, triggering a rebalance.
    攻击者继续调用 settle() FundV3 合约,触发重新平衡。

  4. The attacker calls deposit() function in the ShareStaking contract to deposit tranche Q, with the amount argument being precomputed and equal to the spareAmount value within the deposit() function.
    攻击者调用 deposit() ShareStaking 合约中的函数来存入 Q 部分, amount 参数是预先计算的,并且等于 deposit() 函数中的 spareAmount 值。

  5. Finally, the attacker withdraws and redeem the drained Q to obtain underlying tokens, successfully drains users’ funds.

Proof of concept 概念验证

Check out the POC here
在此处查看 POC

Impact 冲击

Whenever 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 算将永远被错误计算。

Mitigation 缓解

For a detailed explanation of the mitigation, please refer to the Tranchess team’s publication.
有关缓解措施的详细说明,请参阅 Tranchess 团队的出版物。

Acknowledgement 确认

I 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

版权声明:admin 发表于 2023年12月4日 上午11:49。
转载请注明:floranguyen0/tranchess-vulnerability-disclosure | CTF导航