FEATURED · 精选文章

Solidity 智能合约安全实战:基于 agents24 仓库 solidity-security 技能的重入攻击、溢出、权限控制与审计就绪全解

发布时间 / 2026/9/9 19:55:14
来源 / 创域科博编辑部
栏目 / 资讯中心
Solidity 智能合约安全实战:基于 agents24 仓库 solidity-security 技能的重入攻击、溢出、权限控制与审计就绪全解 Solidity 智能合约安全实战基于 agents24 仓库 solidity-security 技能的重入攻击、溢出、权限控制与审计就绪全解【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents本篇技术指南以本仓库中 blockchain-web3 插件 下 solidity-security 技能 的详细模式参考文档 references/details.md 为骨架展开。当你需要编写或审计智能合约、实现安全的 DeFi/NFT/区块链应用、或为合约做专业安全审计前的准备时本指南给出可直接复制运行的 Solidity 示例涵盖重入、整数溢出、权限绕过、抢跑Front-Running四类高危漏洞的攻防对照以及 Checks-Effects-Interactions、Pull-Over-Push、熔断器等安全设计模式与 Gas 优化实践。读完你将掌握一套从写出漏洞到消除漏洞、跑通测试、审计就绪的完整闭环方法。这份安全资料的仓库定位与使用方式在 agents24 这个多 Harness 的 Agent 插件市场中智能合约与 Web3 领域由 blockchain-web3 插件 覆盖其目录结构如下agents/blockchain-developer.md负责编写生产级 Web3 应用与智能合约的领域专家 Agent明确将智能合约安全审计重入、溢出、访问控制漏洞列为能力之一skills/solidity-security/SKILL.md导航层导航层给出技能的触发条件、简要目录与 Hardhat 安全测试示例skills/solidity-security/references/details.md细节层SKILL.md 明确写道Detailed pattern documentation lives inreferences/details.md即当导航层信息不足以作答时Agent 应继续读取该文件获取完整的漏洞模式与可运行代码。这正是本仓库在 docs/agent-skills.md 中贯彻的渐进式披露progressive disclosure知识组织方式先加载轻量的 SKILL.md 概览再按需深入 references 细节从而控制上下文占用并保证专业深度。仓库 docs/plugins.md 的目录表将本插件登记为Smart contracts and DeFi protocols可通过/plugin install blockchain-web3安装docs/agents.md 亦登记了blockchain-developerAgent 用于 Web3 合约场景。换言之本文出现的每段攻击代码 / 修复代码对照都是该技能在真正投入合约编写与审计时的事实性知识载体。高危漏洞逐一拆解攻击面、漏洞代码与修复模式1. 重入攻击Reentrancy重入攻击的原理是在合约完成状态更新之前就发起外部调用攻击者借由 fallback/receive 函数在被调用方地址的钩子里递归回调原合约反复提取本应只发放一次的资金。典型的受害者即银行式提款合约。存在漏洞的写法// VULNERABLE TO REENTRANCY contract VulnerableBank { mapping(address uint256) public balances; function withdraw() public { uint256 amount balances[msg.sender]; // DANGER: External call before state update (bool success, ) msg.sender.call{value: amount}(); require(success); balances[msg.sender] 0; // Too late! } }问题一目了然msg.sender.call{value: amount}()触发对方合约的 receive 逻辑而此刻balances[msg.sender]仍然是旧值攻击者在回调里再次调用withdraw()就能在余额被清零前反复提款直到 gas 耗尽。修复方式一手工落实 Checks-Effects-Interactions先校验、先改状态、最后交互contract SecureBank { mapping(address uint256) public balances; function withdraw() public { uint256 amount balances[msg.sender]; require(amount 0, Insufficient balance); // EFFECTS: Update state BEFORE external call balances[msg.sender] 0; // INTERACTIONS: External call last (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }关键在于把balances[msg.sender] 0这一状态写入移到外部调用之前。即便攻击者再次进入amount已是 0require(amount 0)会直接令重入尝试回滚。修复方式二直接复用 OpenZeppelin 的ReentrancyGuardimport openzeppelin/contracts/security/ReentrancyGuard.sol; contract SecureBank is ReentrancyGuard { mapping(address uint256) public balances; function withdraw() public nonReentrant { uint256 amount balances[msg.sender]; require(amount 0, Insufficient balance); balances[msg.sender] 0; (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }nonReentrant修饰符通过一个存储槽锁标记在函数执行期间阻止同一合约内任何带该修饰符的函数的嵌套进入并会以ReentrancyGuard: reentrant call的原因回滚——这个字符串与下文 Hardhat 测试中的断言完全对应。注意代码中的openzeppelin/contracts/security/...路径对应 OpenZeppelin Contracts v4 版目录布局实际版本迁移时需同步 import 路径。2. 整数溢出 / 下溢Integer Overflow/Underflow算术结果超出类型取值范围时发生回绕uint256减去超出自身值得到巨大数累加则可能溢出归零。历史上曾造成代币凭空增发或余额为负等于天文数字的资金损失。存在漏洞的写法Solidity 0.8.0 时代编译出的字节码不做检查// VULNERABLE contract VulnerableToken { mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { // No overflow check - can wrap around balances[msg.sender] - amount; // Can underflow! balances[to] amount; // Can overflow! } }修复方式一升级到 Solidity 0.8.0编译器内建溢出/下溢检查// Solidity 0.8 has built-in overflow/underflow checks contract SecureToken { mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { // Automatically reverts on overflow/underflow balances[msg.sender] - amount; balances[to] amount; } }从 0.8.0 起、-、*等运算默认执行 checked 算术一旦回绕整笔交易自动 revert这是当前最省心的防线。只有在显式确认安全的临界区例如先require保证不越界才应使用unchecked {}换取 gas且必须辅以注释说明为什么安全。修复方式二仍停留在 Solidity 0.8.0 时改用 SafeMath 库import openzeppelin/contracts/utils/math/SafeMath.sol; contract SecureToken { using SafeMath for uint256; mapping(address uint256) public balances; function transfer(address to, uint256 amount) public { balances[msg.sender] balances[msg.sender].sub(amount); balances[to] balances[to].add(amount); } }SafeMath以库函数形式对每一处加减运算插入边界检查sub/add越界时直接回滚语义上等价于 0.8 的内建行为。3. 访问控制缺失Access Control权限类漏洞指关键函数未限制调用者任何人都能执行提款、铸币、改参数等敏感操作。存在漏洞的写法// VULNERABLE: Anyone can call critical functions contract VulnerableContract { address public owner; function withdraw(uint256 amount) public { // No access control! payable(msg.sender).transfer(amount); } }修复方式一继承 OpenZeppelinOwnable仅合约所有者可调用import openzeppelin/contracts/access/Ownable.sol; contract SecureContract is Ownable { function withdraw(uint256 amount) public onlyOwner { payable(owner()).transfer(amount); } }修复方式二面向多管理员场景实现自定义角色映射与修饰符// Or implement custom role-based access contract RoleBasedContract { mapping(address bool) public admins; modifier onlyAdmin() { require(admins[msg.sender], Not an admin); _; } function criticalFunction() public onlyAdmin { // Protected function } }权限校验必须一律以msg.sender为准严禁用tx.origin做身份认证——通过中间合约调用的用户其tx.origin恒为原始 EOA攻击者可以让受害者顺手调用任何以tx.origin鉴权的危险函数详见下文检查清单。4. 抢跑 / 三明治攻击Front-Running矿工与 MEV 机器人能实时读取公开内存池mempool中的待确认交易并在其之前插入自己的交易从而在 DEX 兑换等场景中赚取价差或抢占机会。仅靠minOutput滑点保护并不能防住全部抢跑变体。存在风险的简化示例// VULNERABLE TO FRONT-RUNNING contract VulnerableDEX { function swap(uint256 amount, uint256 minOutput) public { // Attacker sees this in mempool and front-runs uint256 output calculateOutput(amount); require(output minOutput, Slippage too high); // Perform swap } }缓解手段Commit-Reveal先承诺、后揭示两阶段方案contract SecureDEX { mapping(bytes32 bool) public usedCommitments; // Step 1: Commit to trade function commitTrade(bytes32 commitment) public { usedCommitments[commitment] true; } // Step 2: Reveal trade (next block) function revealTrade( uint256 amount, uint256 minOutput, bytes32 secret ) public { bytes32 commitment keccak256(abi.encodePacked( msg.sender, amount, minOutput, secret )); require(usedCommitments[commitment], Invalid commitment); // Perform swap } }用户先在区块 N 提交keccak256(调用者, 参数, 随机秘密)的哈希攻击者无法提前得知真实参数到区块 N1 再携带秘密揭示并执行。提交与揭示分处不同区块对手便失去了看着你的参数抢跑的信息优势。实际 DeFi 中 commit-reveal 往往有额外的顺序或时效约束需要与协议机制配合设计且应结合滑点下限等多层防护。安全最佳实践把防御写进代码结构在具体漏洞之上本技能沉淀了四条可复用的编程纪律。它们共同指向一个目标让危险的外部调用永远出现在函数末尾让状态变更永远先于副作用。Checks-Effects-InteractionsCEI模式这是 Solidity 安全编码最核心的纪律将函数体显式划分为三段并严守顺序contract SecurePattern { mapping(address uint256) public balances; function withdraw(uint256 amount) public { // 1. CHECKS: Validate conditions require(amount balances[msg.sender], Insufficient balance); require(amount 0, Amount must be positive); // 2. EFFECTS: Update state balances[msg.sender] - amount; // 3. INTERACTIONS: External calls last (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }CHECKS所有前置条件用require集中断言尽量前置以便尽早回滚、少耗 gasEFFECTS尽早修改自身状态这是阻止重入的根因性手段INTERACTIONS把call/transfer等所有可能触发对方代码的外部交互放到最后并对返回值做检查。Pull Over Push拉取优于推送向多个接收方批量推送资金存在一个致命弱点只要其中任意一方失败整批交易都回滚形成单点阻塞。改为让用户自行拉取提款才是稳健做法// Prefer this (pull) contract SecurePayment { mapping(address uint256) public pendingWithdrawals; function recordPayment(address recipient, uint256 amount) internal { pendingWithdrawals[recipient] amount; } function withdraw() public { uint256 amount pendingWithdrawals[msg.sender]; require(amount 0, Nothing to withdraw); pendingWithdrawals[msg.sender] 0; payable(msg.sender).transfer(amount); } } // Over this (push) contract RiskyPayment { function distributePayments(address[] memory recipients, uint256[] memory amounts) public { for (uint i 0; i recipients.length; i) { // If any transfer fails, entire batch fails payable(recipients[i]).transfer(amounts[i]); } } }对比可见SecurePayment先把金额记账为待提取余额用户自行调用withdraw()时先清零再转账再次遵循 CEI而RiskyPayment的推送循环中任何一个接收合约拒绝收款都会拖垮整批分配。实践中拉取往往还配合时间锁或过期回收机制。输入校验Input Validation外部输入是攻击的入口require应当像守门员一样在函数最前面拦截非法值。本资料给出的转账函数示例覆盖了最常见的四类非法输入contract SecureContract { function transfer(address to, uint256 amount) public { // Validate inputs require(to ! address(0), Invalid recipient); require(to ! address(this), Cannot send to contract); require(amount 0, Amount must be positive); require(amount balances[msg.sender], Insufficient balance); // Proceed with transfer balances[msg.sender] - amount; balances[to] amount; } }逐条解读禁止向零地址转账避免代币永久锁死禁止转给自己合约地址金额必须为正余额必须充足在 0.8 编译器下由下溢检查兜底但显式require能给出更清晰的回滚原因便于调用方与测试断言。实际项目还应结合白名单/黑名单、地址格式Address.isContract、以及 EIP-165 接口探测等策略做纵深防御。紧急停止Circuit Breaker / 熔断器当合约上线后发现新漏洞或遭遇攻击时需要一键暂停关键功能的急停开关。OpenZeppelinPausable与Ownable组合是最常用方案import openzeppelin/contracts/security/Pausable.sol; contract EmergencyStop is Pausable, Ownable { function criticalFunction() public whenNotPaused { // Function logic } function emergencyStop() public onlyOwner { _pause(); } function resume() public onlyOwner { _unpause(); } }whenNotPaused修饰符让敏感函数在暂停期间一律回滚emergencyStop()/resume()仅onlyOwner可触发分别调用_pause()与_unpause()。设计时需考虑谁有权暂停、暂停多久、如何恢复、暂停本身是否会造成资金冻结如同时配合提款白名单让用户仍能撤出资金。Gas 优化在不牺牲安全的前提下省钱链上每一次存储写入SSTORE都昂贵本技能给出四条相辅相成的优化纪律。需要强调的是优化必须在不削弱上文任何安全防线的前提下进行——例如绝不能为了省 gas 取消输入校验或改为先交互后改状态。优先使用uint256避免无意义的小类型EVM 存储槽固定为 256 位函数参数声明uint8并不会少付 gas反而可能在编解码与类型转换时引入额外开销// More gas efficient contract GasEfficient { uint256 public value; // Optimal function set(uint256 _value) public { value _value; } } // Less efficient contract GasInefficient { uint8 public value; // Still uses 256-bit slot function set(uint8 _value) public { value _value; // Extra gas for type conversion } }注意优先 uint256与下文的存储打包并不矛盾前者针对函数参数与栈上逻辑后者针对同一存储槽内紧邻变量的布局。打包存储变量Storage PackingSolidity 会把连续声明、且加起来不超过 32 字节的若干存储变量合并进同一个存储槽写满一个槽只收一次 SSTORE 费用// Gas efficient (3 variables in 1 slot) contract PackedStorage { uint128 public a; // Slot 0 uint64 public b; // Slot 0 uint64 public c; // Slot 0 uint256 public d; // Slot 1 } // Gas inefficient (each variable in separate slot) contract UnpackedStorage { uint256 public a; // Slot 0 uint256 public b; // Slot 1 uint256 public c; // Slot 2 uint256 public d; // Slot 3 }uint128 uint64 uint64恰好 32 字节共享 Slot 0而四个uint256各占一槽。打包能显著降低高频写入场景的成本作为权衡读取被打包的窄类型时 EVM 需要额外的掩码/移位操作因此应依据读多写少还是写多读少的实际情况选择打包粒度。这也解释了为何上面的GasEfficient示例将独立参数保持在uint256更优。函数参数用calldata而非memory对外部函数而言calldata参数直接引用交易 calldata 的只读区域零拷贝memory参数则要先在内存中复制一份完整数组contract GasOptimized { // More gas efficient function processData(uint256[] calldata data) public pure returns (uint256) { return data[0]; } // Less efficient function processDataMemory(uint256[] memory data) public pure returns (uint256) { return data[0]; } }当只读使用外部传入的数组/结构体时一律优先calldata仅当需要在函数内修改或构造新数据时才使用memory。calldata还有安全性加成它天然只读无法被误改。恰当场景下用事件替代链上存储事件Event被写入交易日志成本远低于 SSTORE且天然支持索引检索是审计追踪 历史数据的廉价载体contract EventStorage { // Emitting events is cheaper than storage event DataStored(address indexed user, uint256 indexed id, bytes data); function storeData(uint256 id, bytes calldata data) public { emit DataStored(msg.sender, id, data); // Dont store in contract storage unless needed } }indexed参数最多三个可供链下索引器高效过滤。务必记住其边界事件对链上合约不可读只有链下服务能消费因此仅适合需要公开存证但无需链上读回的数据任何必须被其他合约读取的状态仍要落存储。上线前自检常见漏洞检查清单逐条对照details.md 以一段注释型清单合约收尾把全文要点浓缩为一张 15 项检查表。下面将其展开为可逐条勾稽的表格并标注每条对应的防御落点检查项含义对应防御手段Reentrancy protection重入防护ReentrancyGuard或 CEI 模式见上文重入攻击与CEI 模式Integer overflow/underflow整数溢出/下溢Solidity 0.8 内建检查或 SafeMathAccess control访问控制Ownable、角色映射与自定义修饰符Input validation输入校验函数最前端的系列require断言Front-running mitigation抢跑缓解commit-reveal 机制按场景选用Gas optimizationGas 优化存储打包、calldata、合理使用uint256Emergency stop mechanism急停机制Pausable的whenNotPaused 所有者开关Pull over push for payments支付采用拉取模式记账 用户自行withdraw()No delegatecall to untrusted不对不可信合约delegatecalldelegatecall在调用方上下文执行等同将存储与权限交给对方须严格校验目标并审计其全部实现Notx.originfor auth身份认证禁用tx.origin一律用msg.sender见访问控制一节Proper event emission正确的事件声明关键资金/状态变更函数中补充语义完整的事件External calls at end外部调用放函数末尾CEI 模式的 INTERACTIONS 阶段Check return values检查外部调用返回值require(success, ...)或Address.sendValue等封装No hardcoded addresses无硬编码地址地址经构造函数/治理机制注入避免环境切换时误操作Upgrade mechanism升级机制若用代理Transparent / UUPS / Beacon 代理下的初始化器保护与权限收敛清单在代码中的原始形态如下可直接作为审计注释模板放入合约头部// Security Checklist Contract contract SecurityChecklist { /** * [ ] Reentrancy protection (ReentrancyGuard or CEI pattern) * [ ] Integer overflow/underflow (Solidity 0.8 or SafeMath) * [ ] Access control (Ownable, roles, modifiers) * [ ] Input validation (require statements) * [ ] Front-running mitigation (commit-reveal if applicable) * [ ] Gas optimization (packed storage, calldata) * [ ] Emergency stop mechanism (Pausable) * [ ] Pull over push pattern for payments * [ ] No delegatecall to untrusted contracts * [ ] No tx.origin for authentication (use msg.sender) * [ ] Proper event emission * [ ] External calls at end of function * [ ] Check return values of external calls * [ ] No hardcoded addresses * [ ] Upgrade mechanism (if proxy pattern) */ }上表把details.md中注解式清单逐条还原并补充了防御映射方便你在提交专业审计前按项自检。用自动化测试证明安全性Hardhat 攻击复现写对不等于防住SKILL.md 额外提供了用 Hardhat Chai 编写安全回归测试的范式。其价值在于把前文的每条防线变成可重复执行的断言——未来任何改动只要破坏了防线测试就会红灯报警// Hardhat test example const { expect } require(chai); const { ethers } require(hardhat); describe(Security Tests, function () { it(Should prevent reentrancy attack, async function () { const [attacker] await ethers.getSigners(); const VictimBank await ethers.getContractFactory(SecureBank); const bank await VictimBank.deploy(); const Attacker await ethers.getContractFactory(ReentrancyAttacker); const attackerContract await Attacker.deploy(bank.address); // Deposit funds await bank.deposit({ value: ethers.utils.parseEther(10) }); // Attempt reentrancy attack await expect( attackerContract.attack({ value: ethers.utils.parseEther(1) }), ).to.be.revertedWith(ReentrancyGuard: reentrant call); }); it(Should prevent integer overflow, async function () { const Token await ethers.getContractFactory(SecureToken); const token await Token.deploy(); // Attempt overflow await expect(token.transfer(attacker.address, ethers.constants.MaxUint256)) .to.be.reverted; }); it(Should enforce access control, async function () { const [owner, attacker] await ethers.getSigners(); const Contract await ethers.getContractFactory(SecureContract); const contract await Contract.deploy(); // Attempt unauthorized withdrawal await expect(contract.connect(attacker).withdraw(100)).to.be.revertedWith( Ownable: caller is not the owner, ); }); });三个用例分别对应前文三条防线形成严密的攻击复现—防御验证闭环重入测试部署一个专门的ReentrancyAttacker合约对SecureBank发起递归攻击断言以ReentrancyGuard: reentrant call回滚——与ReentrancyGuard修饰符内置的错误字符串一一对应溢出测试以ethers.constants.MaxUint256作为超大转账金额尝试触发溢出断言交易整体 revert对应 Solidity 0.8 的内建检查或 SafeMath权限测试用非所有者的attacker签名调用withdraw断言回滚原因恰为 OpenZeppelinOwnable标准的Ownable: caller is not the owner。这种断言精确回滚原因的写法值得推广相比笼统断言交易失败revertedWith能确保你防住的正是预期的那一种攻击向量。审计就绪用 NatSpec 把合约写成可被专业审计安全审计员必须快速理解每个函数的权限模型、副作用与资金流向。SKILL.md 为此给出了 NatSpec 文档化的范本要点是用title/dev/notice/param讲清合约职责与函数行为并在提款函数中以注释显式标注 CEI 三段contract WellDocumentedContract { /** * title Well Documented Contract * dev Example of proper documentation for audits * notice This contract handles user deposits and withdrawals */ /// notice Mapping of user balances mapping(address uint256) public balances; /** * dev Deposits ETH into the contract * notice Anyone can deposit funds */ function deposit() public payable { require(msg.value 0, Must send ETH); balances[msg.sender] msg.value; } /** * dev Withdraws users balance * notice Follows CEI pattern to prevent reentrancy * param amount Amount to withdraw in wei */ function withdraw(uint256 amount) public { // CHECKS require(amount balances[msg.sender], Insufficient balance); // EFFECTS balances[msg.sender] - amount; // INTERACTIONS (bool success, ) msg.sender.call{value: amount}(); require(success, Transfer failed); } }审计就绪的文档还应回答谁可以调用权限、调用后果资金如何流动、失败行为是否回滚、原因是什么、以及为何采用当前顺序如notice Follows CEI pattern to prevent reentrancy。这些元信息同样能被静态分析工具与后续维护者直接消费是合约长期可维护性的基础。小结让 solidity-security 技能成为你的合约防线围绕 references/details.md 的全部内容本文完成了从威胁建模到审计交付的完整梳理四大高危漏洞重入、溢出、权限缺失、抢跑均给出漏洞代码 修复代码的对照且修复版全部可直接落地四条编码纪律CEI、Pull-Over-Push、输入校验、熔断器构成防御性编程的骨架Gas 优化uint256、存储打包、calldata、事件在守住安全红线的前提下降低成本15 项检查清单可作为提交专业审计前的逐项自检表Hardhat 回归测试与NatSpec 文档分别回答防住了吗与审计员看得懂吗两个交付问题。当你作为 Agent 在本仓库的blockchain-web3生态中编写或审计合约时可让 blockchain-developer Agent 调用本技能先经 SKILL.md 概览定位再深入其 references/details.md 获取上述全部模式与可运行示例按清单自检、按测试范式验证最终交付审计就绪的合约代码。【免费下载链接】agentsMulti-harness agentic plugin marketplace for Claude Code, Codex, Cursor, OpenCode, GitHub Copilot, and Google Antigravity项目地址: https://gitcode.com/GitHub_Trending/agents24/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻