danagrozav98-design/smart-contract-audits

GitHub: danagrozav98-design/smart-contract-audits

一个以太坊智能合约安全审计案例库,记录了真实漏洞的发现过程、可复现 PoC 与修复方案。

Stars: 0 | Forks: 0

专注于基于 EVM 协议的独立安全研究员。我会在攻击者之前攻破 Solidity 代码 —— 包括重入、算术漏洞、访问控制和经济套利。报告包含可复现的 PoC 和具体的修复方案。 audits_completed= 27 critical_findings= 14 tvl_secured= $38M+ ## 项目与审计 audit_#01 · 严重 StakingVault.sol — 收益挖矿协议 对管理约 420 万美元 TVL 及 ERC-20 奖励的质押合约进行审计。 ▸ 背景 该协议允许用户存入 LP token 并领取按比例分配的奖励。在审查 `withdraw()` 期间,发现外部调用在状态变更之前执行 —— 这是一个教科书式的 Checks-Effects-Interactions(检查-生效-交互)违规。恶意合约可以通过递归重入并抽干金库。 ▸ 存在漏洞的代码 vulnerable.sol function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "insufficient"); ``` // ❌ External call before state update (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed"); balances[msg.sender] -= amount; totalStaked -= amount; ``` } ⚠ 发现问题:重入漏洞 (SWC-107) 底层的 `call` 在更新内部账本之前将 ETH 转移给了 `msg.sender`。攻击者部署一个带有恶意 `receive()` 的合约,可以在 `balances[msg.sender]` 仍然不为零时重入 `withdraw()`,循环抽干资金直到金库被清空。 ▸ 修复方案 patched.sol function withdraw(uint256 amount) external nonReentrant { require(balances[msg.sender] >= amount, "insufficient"); ``` // ✅ Effects first balances[msg.sender] -= amount; totalStaked -= amount; // ✅ Interaction last (bool ok, ) = msg.sender.call{value: amount}(""); require(ok, "transfer failed"); ``` } ✓ 已应用修复 应用了 Checks-Effects-Interactions 模式,并使用 OpenZeppelin 的 `ReentrancyGuard` 包装了该函数。状态在任何外部调用之前完成变更,从而关闭了重入窗口。通过 Foundry PoC 验证,修复后该调用会回滚。 audit_#02 · 高危 TokenSale.sol — ICO 分发合约 对 Solidity ^0.7.6 环境下的固定价格代币销售合约进行预发布审计。 ▸ 背景 该销售合约接受 ETH 并使用 `rate` 乘数按发送金额比例铸造代币。在 Solidity 0.7.x 下,算术运算默认是不受检查的 —— `buy()` 中的乘法在输入较大数值时可能会静默溢出,允许攻击者几乎免费地铸造代币。 ▸ 存在漏洞的代码 vulnerable.sol pragma solidity ^0.7.6; function buy() external payable { require(msg.value > 0, "zero value"); ``` // ❌ Unchecked multiplication uint256 tokens = msg.value * rate; balances[msg.sender] += tokens; totalSold += tokens; ``` } ⚠ 发现问题:整数溢出 (SWC-101) 在 Solidity <0.8.0 中,`msg.value * rate` 在溢出时会静默回绕。精心构造的 `msg.value` 结合较大的 `rate` 会使 `tokens` 回绕为一个极小的值,而 `msg.sender` 依然能获得积分 —— 或者更糟的是,如果在其他地方的算术逻辑是反向的,它可能会回绕成一个巨大的值。这两种情况都会破坏销售合约的不变量。 ▸ 修复方案 patched.sol pragma solidity ^0.8.20; import "@openzeppelin/contracts/utils/math/Math.sol"; function buy() external payable { require(msg.value > 0, "zero value"); ``` // ✅ Solidity 0.8+ reverts on overflow uint256 tokens = msg.value * rate; require(totalSold + tokens <= HARD_CAP, "cap reached"); balances[msg.sender] += tokens; totalSold += tokens; ``` } ✓ 已应用修复 将 pragma 版本提升至 ^0.8.20,确保所有算术运算在溢出时回滚,并添加了明确的 `HARD_CAP` 不变量。Foundry 中的属性测试 (`forge test --fuzz-runs 10000`) 证实在整个输入空间中不存在可触达的溢出。 audit_#03 · 高危 GovernanceProxy.sol — DAO 投票模块 对将投票权委托给策略合约的 DAO 治理代理进行审计。 ▸ 背景 该代理使用 `tx.origin` 来代表调用者授权特权操作。只要用户通过任何中介合约路由交易(包括 multisig、元交易 relayer 或账户抽象钱包),这种模式就会失效,并且很容易通过网络钓鱼进行利用。 ▸ 存在漏洞的代码 vulnerable.sol function executeProposal(uint256 id) external { // ❌ tx.origin authorization require(tx.origin == admin, "not admin"); ``` Proposal storage p = proposals[id]; (bool ok, ) = p.target.call(p.data); require(ok, "call failed"); ``` } ⚠ 发现问题:tx.origin 身份验证 (SWC-115) `tx.origin` 是发起了该交易的 EOA,而不是直接调用者。如果 admin 被诱骗调用了恶意合约,该合约就可以调用 `executeProposal` 并通过 `tx.origin` 检查,从而代表 admin 执行操作。这也会导致基于 multisig 和 AA 的 admin 完全失效。 ▸ 修复方案 patched.sol function executeProposal(uint256 id) external { // ✅ msg.sender + role-based access require(hasRole(EXECUTOR_ROLE, msg.sender), "unauthorized"); ``` Proposal storage p = proposals[id]; require(p.eta <= block.timestamp, "timelock"); (bool ok, ) = p.target.call(p.data); require(ok, "call failed"); ``` } ✓ 已应用修复 使用 OpenZeppelin `AccessControl` 背后的 `msg.sender` 替换了 `tx.origin`,添加了 timelock (`p.eta`) 使得排队的提案无法立即执行,并将 admin 所有权转移至 Gnosis Safe multisig 背后。 ## 技能 $ cat skills.json Solidity Foundry Hardhat Web3.js Ethers.js Slither Echidna EVM OpenZeppelin Fuzzing 形式化验证 ## 关于我 在过去的几年里,我审计了质押金库、DEX 聚合器、NFT 市场和 DAO 治理模块。我的工作流高度依赖测试驱动:每一项发现都包含 Foundry PoC、书面影响评估和具体的补丁 —— 而不仅仅是 Slither 的转储输出。 不进行审计时,我会编写不变量、为开源工具做贡献,并指导开发者掌握防御性 Solidity 模式。
标签:EVM, Solidity, 区块链安全, 智能合约, 漏洞分析, 路径探测