拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WTF-Solidity 合约安全实战:权限管理漏洞剖析与 OpenZeppelin 防御实践

WTF-Solidity 合约安全实战权限管理漏洞剖析与 OpenZeppelin 防御实践【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity权限管理漏洞是智能合约中最常见也最致命的安全问题之一——它曾导致跨链桥 Poly Network 被盗 6.11 亿美元、BSC 上 DeFi 项目 ShadowFi 损失 30 万美元。本文以 WTF-Solidity 仓库中 S04_AccessControlExploit 专题含葡萄牙语版 Languages/pt-br/S04_AccessControlExploit/readme.md为核心结合 OpenZeppelin 源码逐行拆解权限配置错误与授权检查缺失两类漏洞的成因、攻击路径与修复方案帮助读者掌握用onlyOwner与allowance机制为合约特殊函数正确加锁的实战能力。漏洞背景一次权限失误数亿美金蒸发智能合约中的权限管理定义了不同角色普通用户、合约所有者、管理员、守护者等在应用中的操作边界。通常来说代币铸造mint、提取资金withdraw、暂停合约pause等功能属于高权限操作只有经过授权的账户才能调用。一旦权限配置错误攻击者就能以普通用户身份执行特权操作造成不可估量的损失。历史上两起典型事故印证了这一风险Poly Network6.11 亿美元其跨链桥合约中负责转移守护者keeper的函数没有配置相应权限黑客把守护者地址改成自己的地址后顺利提走了合约中约 6.11 亿美元的资产——本质上就是权限配置错误。ShadowFi30 万美元BSC其代币合约在burn()销毁函数中忘记检查调用者的授权额度攻击者借此随意销毁其他地址尤其是流动性池的代币之后只需卖出少量代币就能抽干池中全部 BNB获利约 30 万美元——这是典型的授权检查缺失。从仓库源码结构看本专题围绕一个演示合约展开完整实现位于 S04_AccessControlExploit/AccessControlExploit.sol葡萄牙语注释版见 Languages/pt-br/S04_AccessControlExploit/AccessControlExploit.sol其内部同时保留了错误写法与正确写法各两个函数方便读者对照学习。漏洞类型一权限配置错误——任何人都能mint第一类漏洞出在特殊函数根本没有权限控制上。若合约中诸如mint()这样的函数没有任何权限校验那么任何地址都可以直接调用它无限增发代币、稀释所有人的资产或配合其他逻辑掏空合约资金。演示合约中的错误写法如下// 错误的mint函数没有限制权限 function badMint(address to, uint amount) public { _mint(to, amount); }该函数直接调用 OpenZeppelinERC20的内部函数_mint(to, amount)从零地址向to铸造amount枚代币并增加总供应量。由于badMint是public且没有任何onlyOwner、require之类的守卫任何外部账户都能调用它。在 Poly Network 事件中攻击者正是利用了类似缺失权限校验的函数把自己提升为守护者后再发起转账。漏洞类型二授权检查缺失——能销毁他人资产第二类漏洞的典型场景是 ERC20 的burn()销毁函数。按标准语义代币只能由持有人自己销毁或由持有人的**授权方spender**在授权额度范围内代为销毁。如果burn()直接调用内部函数_burn()而跳过对调用者身份的校验攻击者就能销毁任意地址的代币。错误写法如下// 错误的burn函数没有限制权限 function badBurn(address account, uint amount) public { _burn(account, amount); }_burn(account, amount)会直接减少account的余额与总供应量而调用者msg.sender根本不需要是account本人、也不需要持有account的任何授权。ShadowFi 事件中黑客正是先调用类似的错误burn函数销毁流动性池中的 SDF 代币再卖出少量剩余代币、抽走池中所有 BNB最终获利 30 万美元。防御手段一用onlyOwner给特殊函数上锁修复权限配置错误最直接的方式是引入 OpenZeppelin 的权限管理库Ownable用onlyOwner修饰器把mint等特权函数的调用者限制为合约所有者// 正确的mint函数使用 onlyOwner 修饰器限制权限 function goodMint(address to, uint amount) public onlyOwner { _mint(to, amount); }从 lib/openzeppelin-contracts/contracts/access/Ownable.sol 的源码可以看清其实现原理合约维护一个私有状态变量address private _owner构造函数接收initialOwner并通过_transferOwnership完成初始化若传入零地址会触发OwnableInvalidOwner回滚modifier onlyOwner()内部先执行_checkOwner()再继续执行函数体_checkOwner()会比较owner()与_msgSender()不一致时抛出自定义错误OwnableUnauthorizedAccount只有通过transferOwnership或renounceOwnership才能变更所有者且这两者本身也受onlyOwner保护。演示合约通过contract AccessControlExploit is ERC20, Ownable同时继承了标准 ERC20 与 Ownable其中文版构造器写法为constructor() ERC20(Wrong Access, WA) Ownable(msg.sender) {}——即把部署者设为合约所有者部署时务必注意传入正确的所有者地址。防御手段二在函数逻辑中校验调用者授权修复授权检查缺失的关键是在销毁逻辑中确认调用者对目标账户余额拥有足够的授权额度。正确写法如下// 正确的burn函数如果销毁的不是自己的代币则会检查授权 function goodBurn(address account, uint amount) public { if(msg.sender ! account){ _spendAllowance(account, msg.sender, amount); } _burn(account, amount); }这段代码的逻辑分两层本人销毁当msg.sender account时用户销毁自己的代币天然拥有处置权无需额外授权代他人销毁当调用者不是账户持有人时必须通过_spendAllowance(account, msg.sender, amount)消耗account对msg.sender的授权额度额度不足会直接回滚从而阻止越权销毁。_spendAllowance的实现同样位于 lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol其关键行为包括先读取allowance(owner, spender)的当前授权额度若授权额度不是type(uint256).max即未设置无限授权则要求额度 value否则抛出ERC20InsufficientAllowance错误授权足够时在unchecked块中执行_approve(owner, spender, currentAllowance - value, false)扣减额度且不额外发出Approval事件以节省 gas。这套机制与标准transferFrom完全同源事实上 ERC20.sol 中的_mint/_burn都只是校验零地址后调用统一的_update记账逻辑真正的权限边界完全取决于外层调用函数是否做了校验——这正是本漏洞的本质所在。完整修复示例与本地验证将上述两个正确函数合并即可得到一个同时覆盖mint与burn两种高危操作的加固版合约完整源码见 S04_AccessControlExploit/AccessControlExploit.sol// SPDX-License-Identifier: MIT pragma solidity ^0.8.34; import openzeppelin/contracts/token/ERC20/ERC20.sol; import openzeppelin/contracts/access/Ownable.sol; // 权限管理错误例子 contract AccessControlExploit is ERC20, Ownable { // 构造函数初始化代币名称和代号 constructor() ERC20(Wrong Access, WA) Ownable(msg.sender) {} // 错误的mint函数没有限制权限 function badMint(address to, uint amount) public { _mint(to, amount); } // 正确的mint函数使用 onlyOwner 修饰器限制权限 function goodMint(address to, uint amount) public onlyOwner { _mint(to, amount); } // 错误的burn函数没有限制权限 function badBurn(address account, uint amount) public { _burn(account, amount); } // 正确的burn函数如果销毁的不是自己的代币则会检查授权 function goodBurn(address account, uint amount) public { if(msg.sender ! account){ _spendAllowance(account, msg.sender, amount); } _burn(account, amount); } }本地运行与验证建议仓库为只读仅可编译与测试不可修改项目基于 Foundry 管理依赖配置见 foundry.tomlOpenZeppelin 依赖位于 lib/openzeppelin-contracts编译版本要求为 Solidity^0.8.34在 Remix 中直接粘贴上述代码并部署后可用普通账户分别调用badMint与goodMint前者会成功增发后者因触发OwnableUnauthorizedAccount而回滚二者对比可直观感受权限边界对burn的验证可构造两个测试账户用账户 A 调用badBurn(B, amount)能成功销毁 B 的余额而goodBurn(B, amount)未授权时会因ERC20InsufficientAllowance回滚仓库提供了 scripts/run-forge-tests.sh 供批量运行测试参考本专题当前未附带专属测试用例读者可自行补写setUp 上述断言场景。总结权限管理漏洞主要呈现为两种形态权限配置错误特权函数无任何守卫如badMint与授权检查缺失销毁等操作不校验调用者授权如badBurn。防御的核心思路也有两条对应本讲的两个正确函数入口加锁使用 OpenZeppelin 的Ownable或AccessControl等更细粒度的 RBAC 库为铸造、提款、暂停等特殊函数配置onlyOwner等权限修饰器逻辑校验在涉及他人资产的操作中借助 ERC20 标准的allowance授权机制_spendAllowance确保调用者拥有足够授权杜绝越权销毁与转移。从本仓库 S04_AccessControlExploit 目录 的完整实现出发将这两条原则落实为代码中的具体守卫即可有效规避同类高危事故。【免费下载链接】WTF-SolidityWTF Solidity 极简入门教程供小白们使用。Now supports English! 官网: https://wtf.academy项目地址: https://gitcode.com/GitHub_Trending/wt/WTF-Solidity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门