Jerrytriple8/sol-reentrancy-scanner

GitHub: Jerrytriple8/sol-reentrancy-scanner

一款基于纯 Python 正则表达的 Solidity 智能合约重入漏洞静态分析工具,支持 CI/CD 集成与多级严重度报告。

Stars: 0 | Forks: 0

# sol-reentrancy-scanner 用于检测 Solidity 智能合约中重入漏洞的静态分析工具。没有外部 Solidity 解析器依赖 —— 纯 Python 正则表达式分析。 ## 什么是 Reentrancy? 重入是最具破坏性的智能合约漏洞之一。在 [2016 DAO 攻击](https://blog.openzeppelin.com/15-lines-of-code-that-could-have-prevented-thedao-hack)中,攻击者利用重入漏洞窃取了约 360 万个 ETH(当时价值约 6000 万美元)。 这种模式通常发生在合约执行以下操作时: 1. 发起一次 **外部调用**(发送 ETH 或调用其他合约) 2. 被调用的合约 **回调** 原始函数 3. 状态尚未更新,因此攻击者可以 **重复执行** 存在漏洞的代码 ``` Attacker VulnerableContract │ │ │──── withdraw() ─────────────>│ │ │─── .call{value} ──> Attacker │ │ ↑ │──── withdraw() ─────────────>│ (callback!) │ │ balance not zeroed! │ │─── .call{value} ──> Attacker │ │ │<─── ETH ─────────────────────│ (double withdrawal) ``` ## 工具检测内容 ### 1. 单函数重入 (HIGH) 在**同一函数**中,外部调用之后紧跟状态更改。 ``` function withdraw() { // ← VULNERABLE uint256 amount = balances[msg.sender]; (bool ok,) = msg.sender.call{value: amount}(""); // ← External call balances[msg.sender] = 0; // ← State change AFTER call } ``` ### 2. 跨函数重入 (MEDIUM) **函数 A** 中的外部调用可以通过共享状态的**函数 B** 被利用。 ``` function withdraw() external { // ← Makes external call uint256 amount = balances[msg.sender]; (bool ok,) = msg.sender.call{value: amount}(""); balances[msg.sender] = 0; } function transfer(address to, ...) { // ← Shares 'balances' state balances[msg.sender] -= amount; // ← Attacker can call this during callback balances[to] += amount; } ``` ### 3. 只读重入 (LOW) ``` function withdraw() external { // ← External call (bool ok,) = msg.sender.call{value: balances[msg.sender]}(""); balances[msg.sender] = 0; } function getBalance() external view { // ← Reads state during callback return balances[msg.sender]; // ← May return stale data } ``` ### 4. 跨合约重入 (MEDIUM) 向合约 X 发起外部调用,随后进行状态更改。合约 X 可能会进行回调。 ``` // ContractA function swap() external { // ← Calls ContractB uint256 tokenBal = tokenB.balanceOf(address(this)); tokenB.transfer(msg.sender, tokenBal); // ← External call tokenB.balance -= tokenBal; // ← State change after } ``` ## 安装 ``` git clone https://github.com/Jerrytriple8/sol-reentrancy-scanner.git cd sol-reentrancy-scanner pip install -r requirements.txt ``` 唯一依赖:用于格式化输出的 `rich>=13`。解析部分是纯 Python 实现。 ## 用法 ### 扫描单个文件 ``` python cli.py examples/VulnerableVault.sol ``` ### 扫描目录 ``` python cli.py ./contracts/ ``` ### JSON 输出(用于 CI 流水线) ``` python cli.py ./contracts/ --json ``` ### 按严重程度过滤 ``` python cli.py ./contracts/ --severity HIGH python cli.py ./contracts/ --severity MEDIUM # shows HIGH + MEDIUM python cli.py ./contracts/ --severity LOW # shows HIGH + MEDIUM + LOW ``` ### CI 集成 ``` # Exit code: 0 = 未发现 HIGH/MEDIUM,1 = 发现 HIGH/MEDIUM python cli.py ./contracts/ --json --severity MEDIUM > report.json EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then echo "Reentrancy vulnerabilities found!" cat report.json exit 1 fi ``` ### 静默模式(抑制进度消息) ``` python cli.py ./contracts/ -q --json ``` ## 示例输出 ### Rich 表格(默认) ``` Scanning 3 file(s)... Reentrancy Scanner Results — 2 HIGH, 1 MEDIUM, 1 LOW ┌──────────┬──────────────────┬────────────────────┬──────────────────┬──────────┬────────────────┬──────────────────────────────┐ │ Severity │ Pattern │ Contract │ Function │ Call Line│ State Var │ File │ ├──────────┼──────────────────┼────────────────────┼──────────────────┼──────────┼────────────────┼──────────────────────────────┤ │ HIGH │ Single-Function │ VulnerableVault │ withdraw │ 22 │ balances │ VulnerableVault.sol │ │ HIGH │ Single-Function │ CrossFunctionVault │ withdraw │ 20 │ balances │ CrossFunctionVault.sol │ │ MEDIUM │ Cross-Function │ CrossFunctionVault │ withdraw │ 20 │ balances │ CrossFunctionVault.sol │ │ LOW │ Read-Only │ VulnerableVault │ withdraw │ 22 │ balances │ VulnerableVault.sol │ └──────────┴──────────────────┴────────────────────┴──────────────────┴──────────┴────────────────┴──────────────────────────────┘ Detailed Findings: 1. [HIGH] single reentrancy in VulnerableVault.withdraw File: examples/VulnerableVault.sol External call: line 22 — (bool success, ) = msg.sender.call{value: balance}(""); State variable at risk: balances State variable 'balances' is written at line 26 after external call at line 22 in function 'withdraw'. Fix: Apply checks-effects-interactions pattern: perform all state changes BEFORE external calls. ``` ### JSON 输出 ``` { "total_findings": 3, "summary": { "HIGH": 2, "MEDIUM": 1 }, "findings": [ { "severity": "HIGH", "pattern_type": "single", "contract": "VulnerableVault", "function": "withdraw", "function_line": 17, "call_line": 22, "call_text": "(bool success, ) = msg.sender.call{value: balance}(\"\")", "state_variable": "balances", "file": "/path/to/VulnerableVault.sol", "description": "State variable 'balances' is written at line 26 after external call at line 22 in function 'withdraw'.", "suggestion": "Apply checks-effects-interactions pattern: perform all state changes BEFORE external calls." } ] } ``` ## 运行测试 ``` python tests/test_scanner.py ``` ## CI/CD 集成 ### GitHub Actions ``` name: Reentrancy Scan on: [push, pull_request] jobs: scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.11' - run: pip install -r requirements.txt - name: Scan contracts run: python cli.py ./contracts/ --json --severity MEDIUM > findings.json - name: Upload findings if: always() uses: actions/upload-artifact@v4 with: name: reentrancy-findings path: findings.json ``` 如果检测到 HIGH 或 MEDIUM 严重程度的发现,工具将以退出代码 1 退出,这使其非常适用于 CI。 ## 识别出的安全模式 扫描器会识别并跳过以下安全模式: | 模式 | 检测行为 | |---------|-----------| | `nonReentrant` 修饰符 | 跳过该函数 | | ReentrancyGuard 导入 | 将合约标记为受保护 | | `.transfer()` 调用 | 标记为 INFO(2300 gas 限制)| | `.send()` 调用 | 标记为 INFO(2300 gas 限制)| | 遵循 CEI 模式 | 不生成发现 | ## 局限性 - **基于正则表达式的解析**:不是完整的 Solidity AST 解析器。可能会遗漏复杂/嵌套语法中的边缘情况。 - **误报**:状态变量是通过启发式方法检测的;局部变量可能会被错误分类为状态变量。 - **无数据流分析**:无法跟踪值在表达式中的流动方式。 - **无跨文件解析**:Import 解析较为浅层 —— 仅检查 ReentrancyGuard 的 import 语句。 - **修饰符检测**:仅检测 `nonReentrant`;自定义的重入防护可能无法被识别。 - **未解析 Assembly**:不对内联 Assembly(`assembly { ... }`)进行分析。 - **可升级合约**:代理模式可能会导致误报/漏报。 请将此工具作为初步筛选手段,结合更深入的分析(Slither、Mythril、形式化验证)一起使用。 ### 添加新检测器 1. 在 `detectors.py` 中创建一个包含 `detect(contracts)` 方法的类 2. 返回一个 `Finding` 对象列表 3. 将该检测器添加到 `run_all_detectors()` 中 4. 添加一个包含示例 `.sol` 文件的测试用例 ## License MIT
标签:逆向工具