你的dApp是否足够安全,可以在链上运行?

本文探讨了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 的更多信息。

如何避免 Calldata 下溢和溢出损坏?

当由于超出数据类型限制的计算而发生算术错误时,会发生此类漏洞。攻击者可以通过迫使整数超出其最大或最小范围来利用这一点,从而导致下溢或溢出。

例如,如果你使用的是 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 亿美元。

以下是攻击的运作方式:

  1. 初始设置:

    Eisenberg 在 Mango Markets 的两个不同账户中存入了 1000 万美元的 USDC。

  2. 价格操纵:

    使用一个账户,他出售了大量的 MNGO(Mango 的代币),同时用他的第二个账户回购了它。这创造了一种人为的需求,导致 MNGO 的价格迅速飙升。

  3. 价格膨胀:

    由于该协议使用这种被操纵的价格作为参考,他的第二个账户突然显示出超过 4 亿美元的虚假价值。

  4. 抵押借贷:

    他使用这个被人为抬高的账户作为抵押品来借用其他有价值的代币,从而耗尽了 Mango Markets 的几乎所有流动资产。

  5. 崩溃:

    一旦操纵结束,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、数据馈送和价格馈送。

  1. 使用 Chainlink CCIP(跨链互操作性协议) 可以实现安全的跨链通信和数据传输。它采用分层安全架构设计,即使在存在受损链或节点的情况下也能防止恶意活动,你可以在 chain.link 上阅读有关 CCIP 的更多信息;
  2. 使用 Chainlink 数据馈送 允许你的智能合约以安全和去中心化的方式访问可靠的真实世界数据。这些馈送聚合来自多个来源的数据,以最大限度地减少操纵或单点故障,你可以在 chain.link 上阅读更多相关信息;
  3. 使用 Chainlink 价格馈送 通过去中心化的独立节点网络,聚合来自多个优质数据提供商的信息,从而提供高质量、防篡改的价格数据,你可以在 chain.link 上阅读更多相关信息。

了解风险管理网络和预言机安全

Chainlink 还包括一个额外的保护层:风险管理网络 (RMN),它引入了五个级别的预言机安全。该系统确保去中心化环境中数据的可靠性和执行安全性。

风险管理网络 作为一个并行的链下系统运行,该系统验证通过 CCIP(Chainlink 的跨链协议)传递的消息。它通过重建和比较跨链消息中的 Merkle 根来执行独立的验证。如果验证成功,则 Merkle 根将被认定为有效,并且可以执行该消息。如果检测到异常(例如双重执行或终结违规),系统将标记该协议,自动暂停该特定链的 CCIP,直到手动审查和解决该问题。

Chainlink 中的这五个级别的预言机安全包括:

  1. 数据源冗余:聚合来自多个 API 的数据,以减少对单个来源的依赖。
  2. 节点去中心化:使用独立节点的网络来传递数据,确保没有单点故障。
  3. Don 聚合:通过去中心化预言机网络 (DON) 在链下聚合数据,然后再发送到链上。
  4. 链上监控与警报:内置工具实时监控活动并检测异常情况。
  5. 风险管理网络:为 CCIP 添加一个额外的验证层,以防止跨链漏洞、异常情况或数据不一致。

通过利用 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
            // 触发逻辑来处理用户清算
        }
    }
}

结论

成为一名智能合约开发者是一个很好的职业机会,但 请记住,能力越大,责任越大

来源: https://www.reddit.com

在这个角色中,最重要的责任之一是对安全开发实践的承诺。始终优先考虑安全性,遵循最佳编码标准,并意识到即使是经过多次审计的代码也可能存在漏洞。

在本文中,我们探讨了重入、下溢/溢出错误和价格预言机操纵等漏洞,以及如何通过使用诸如 CEI(检查、效果和交互)等安全编码实践和诸如 Chainlink 价格馈送等工具来减轻智能合约开发中的这些错误。

如果你在阅读完本文后认为安全性是智能合约开发者最重要的技能,那么我很高兴,因为这种思维模式可以构建更安全的 Web3 生态系统。

本文是 以太坊开发者包 的最终作品的一部分,该项目由 ETH Kipu 提供,并由 ERC5577 Innovation Labs77 Educational Labs 提供支持。该课程由 Barba 教授 授课,并得到了 Romina SejasRobson SilvaThiago Rocha 的宝贵支持。

此外,我通过 Chainlink Labs 课程 扩展了我的知识,这有助于理解安全预言机实施和去中心化数据集成。

我很高兴与我的同事 Vinicius CiscotoGustavo GialluisiLuciano GrossiEduardo ViegasLeonardo GodoyFrancis WagnerMoises AraujoPedro NascimentoAntonio QuentalRaffaela LoffredoThiago 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://www.quicknode.com/guides/ethereum-development/smart-contracts/a-broad-overview-of-reentrancy-attacks-in-solidity-contracts

https://blog.decurity.io/yul-calldata-corruption-1inch-postmortem-a7ea7a53bfd9

https://rekt.news/1inch-rekt

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://docs.chain.link/ccip

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

https://rekt.news/leaderboard

  • 原文链接: coinsbench.com/is-your-d...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
CoinsBench
CoinsBench
https://coinsbench.com/