跨函数重入:当函数互相背叛

本文介绍了跨函数重入漏洞,它指的是攻击者利用一个函数打开后门,然后在第一个函数完成之前,通过另一个函数进入。

智能合约应该是符合逻辑的。一个函数执行它的工作,另一个处理其他事情,它们和平共处。但是,如果两个函数秘密共享同一个金库,并且攻击者利用了它们的交互呢?

我的朋友,这就是 跨函数重入(Cross-Function Reentrancy)

嗨,我是 0x00Auditor,你在 Solidity 黑暗小巷中的导游。在今天的剧集中,我们将研究当两个看起来无辜的函数以错误的方式组合时,如何变成武器。

这不是理论。真实的 DeFi 漏洞利用已经使用这种确切的技巧来耗尽数百万美元。所以让我们一步一步地分解它。

什么是跨函数重入?

在你可能见过的经典重入中,一个函数重新进入自身(例如 withdraw() 在完成之前调用自身)。

但是跨函数重入更加隐蔽。攻击者不是让单个函数循环回到自身,而是使用 函数 A 打开大门,然后在第一个函数完成之前溜进 函数 B

这就像魔术师用一只手分散你的注意力,而另一只手却在掏你的口袋。

想象一下这种情况:

  • deposit() 存储余额。
  • withdraw() 偿还它们。
  • 两者都依赖于相同的共享状态(例如 balances[msg.sender])。

如果一个函数更新状态太晚,攻击者可以_通过另一个函数_重新进入,该函数会触及相同的状态——导致双重支付或更糟。

为什么跨函数重入有效?

这是造成混乱的秘诀:

  1. 跨函数的共享状态

    两个函数都依赖于相同的变量(例如余额或 totalSupply)。

  2. 延迟更新

    在外部调用之前未更新状态——留下一个“漏洞窗口”。

  3. 外部调用 = 攻击者控制

    就像单函数重入一样,使用 .call() 或任何外部交互都会移交控制权。

  4. 函数不希望被混合

    开发人员通常会想,“这个函数不能调用那个函数!” 但攻击者不按规则行事。他们将它们_链接_起来。

让我们来看一些代码(坏的那种)

这是一个简单(且危险)的合约:

看起来还可以?不。withdraw()emergencyWithdraw() 都会触及相同的余额状态——并且两者都会在更新之前发送 ETH。

攻击者合约——双重麻烦

以下是攻击者如何滥用它:

攻击如何展开

  1. 像普通用户一样 存入资金
  2. 调用 withdraw() 以开始支付。
  3. 合约发送 ETH → 触发攻击者的 receive()
  4. receive() 内部,攻击者快速调用 emergencyWithdraw()
  5. 因为状态尚未更新,攻击者再次耗尽他们的余额。
  6. 反复执行,直到金库空空如也。

基本上,一个函数设置舞台,另一个函数完成盗窃。

针对跨函数重入的防御

阻止这种情况不是靠记忆黑客行为,而是靠编码的纪律。

1. Checks-Effects-Interactions (CEI) 模式

在进行任何外部调用之前更新你的状态。

1. 安全的 withdraw()(已修复):

现在,即使攻击者重新进入,他们的余额也已经减少了。

2. 使用 OpenZeppelin 的 ReentrancyGuard

在敏感函数上添加 nonReentrant 以防止任何嵌套调用。

那是 Solidity 的互斥锁——在其中一个函数运行时,没有函数可以偷偷溜回来。

3. 跨函数分离逻辑

如果两个函数依赖于相同的状态,请问自己:是否可以滥用其中一个来欺骗另一个?

有时候最简单的修复方法是合并函数或重构逻辑,以便它们不共享可利用的状态。

具有跨函数重入的真实漏洞利用

这不是假设。跨函数重入的变体已经出现在:

  • Curve Finance (2022) — 攻击者通过跨不同函数的重入利用流动性池。
  • Paraluni Hack (2022) — 通过以不同方式滥用触及相同余额的函数,盗取了约 170 万美元。

教训:如果你有多个通往同一个金库的门,攻击者会尝试所有这些门。

主要收获 / TL;DR

因为你可能想要作弊表:

  • 🔁 当多个函数共享相同的易受攻击状态时,会发生跨函数重入。
  • 🕳️ 漏洞:在更新余额之前进行外部调用。
  • 🧱 防御措施:

1. 使用 Checks-Effects-Interactions

2. 添加 ReentrancyGuard

3. 不要让多个函数不安全地操作相同的状态。

  • ⚡ 真实的黑客行为表明这并非纸上谈兵——始终像攻击者一样审查,他们会链接你的函数。

即将推出——跨合约重入

如果跨函数重入是同一栋房子里的两扇门,那么 跨合约重入 则是两栋房子之间有一条秘密隧道。

为什么要在同一个合约中的 withdraw()claimRewards() 之间来回跳转……当你可以完全在不同的合约之间进行乒乓球式的调用时?这正是攻击者在跨合约重入中所做的——通过受信任的邻居、流动性池或协议模块编织调用,直到他们解开整个系统。

在下一篇文章中,我们将探讨:

  • 彼此“信任”的合约如何打开新的攻击面,
  • 为什么可组合性(DeFi 的超能力)会变成它的阿喀琉斯之踵,
  • 多个合约成为百万美元抢劫案的同谋的真实漏洞利用,
  • 当然,还有防御措施:模式、保障措施和审计技巧,以在乒乓球开始之前阻止它。

将其视为多人模式下的重入。🎮

敬请关注——因为如果你正在 DeFi 中构建,那么你的合约很可能不仅仅是在与自身对话。接下来是什么?

你现在已经遇到了单函数重入的更狡猾的表亲。

在下一章中,我们将再次升级:跨合约重入——当攻击者像弹球机一样在合约之间弹跳时。

在那之前,请保持你的锁紧,并比你的咖啡☕更早地更新你的状态。

保持安全,fren。👋

直到下次

这就是 跨函数重入 的总结。

我们已经看到两个看起来无辜的函数如何成为犯罪伙伴,攻击者如何实际链接它们,以及使你的金库紧锁的防御措施。

寓意是什么?Solidity 不会原谅假设。始终在打开门之前更新你的状态,有纪律地保护你的函数,并像有人要破坏你一样进行测试——因为实际上,他们就是这样做的。

感谢你的一路阅读,fren。我希望这种分解能让你更加偏执(以好的、审计员的方式 😉)。

如果你想联系、交流笔记,或者只是讨论智能合约错误:

  • 🔗 LinkedIn — 我在这里假装很严肃
  • 🐦 X (Twitter) — 我在这里向虚空大喊关于重入的内容
  • 💻 GitHub — 我的实验、实验室和通过破坏事物(安全地)进行学习的家

我总是乐于剖析漏洞利用、交流安全提示,或者谈论为什么 delegatecall 仍然让我夜不能寐。

保持好奇心,保持安全——我们将在下一集中再见,关于 跨合约重入

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

0 条评论

请先 登录 后评论
0x00auditor
0x00auditor
江湖只有他的大名,没有他的介绍。