引言
智能合约一旦部署到公链,代码通常会进入一个近乎不可逆的运行环境。传统 Web 应用出现漏洞,可以通过发布新版本、回滚数据库或者临时关闭服务解决;智能合约则不同,一旦漏洞涉及资产转移、权限控制或核心业务逻辑,攻击者可能在项目团队发现问题之前完成套利,后续即使修复代码,也无法自动追回已经转移的资产。
因此,智能合约安全不能等同于“上线前跑一次扫描器”。
真正有效的安全审计,是从业务模型、资产流向、权限边界、状态转换、外部调用、经济机制和链上运行环境出发,对合约进行多层次验证。
OWASP 2026 Smart Contract Top 10 已经将访问控制、业务逻辑、价格预言机操纵、闪电贷辅助攻击、输入校验、未检查外部调用、算术错误、重入、整数溢出下溢以及代理与升级漏洞列为重点风险类别。这一变化也说明,现代智能合约攻击早已不只是传统意义上的 Solidity 代码 Bug,越来越多严重事件来自协议设计和经济模型本身。(OWASP Smart Contract Security)
本文以 EVM/Solidity 合约为主要对象,从项目进入审计前的准备开始,一直到人工审计、静态分析、动态测试、漏洞复现、修复验证和部署前 Gate,系统梳理一套能够用于生产项目的智能合约安全审计流程。
一、智能合约审计真正要解决什么问题
很多团队理解的审计是:
源码
↓
Slither
↓
发现几个 Warning
↓
修复
↓
部署
这种方式远远不够。
安全审计真正要回答的是四个问题:
- 谁可以改变关键状态?
- 谁可以把资产从系统中取走?
- 在什么条件下资产数量会发生变化?
- 攻击者能否通过组合多个合法操作,构造出一个协议设计者没有预料到的非法结果?
因此,一个完整的审计对象实际上应该是:
Smart Contract System
│
┌────────────────┼────────────────┐
│ │ │
Code Business Economics
│ Logic │
│ │ │
Solidity State Machine Token Model
│ │ │
└────────────────┼────────────────┘
│
External Systems
│
Oracle / Token / Bridge
│
Blockchain Runtime
如果只检查 Solidity 代码,而不理解业务逻辑,审计很容易出现一种危险情况:
代码没有明显 Bug,但协议依然可以被攻击。
二、部署前审计的标准流程
一套比较完整的企业级流程可以拆成九个阶段:
01 需求与范围确认
↓
02 架构与威胁建模
↓
03 自动化静态分析
↓
04 人工代码审查
↓
05 单元测试与模糊测试
↓
06 攻击路径构造
↓
07 漏洞复现与风险评级
↓
08 修复与回归审计
↓
09 部署前最终安全 Gate
这里有一个非常重要的原则:
自动化工具负责扩大覆盖面,人工审计负责理解语义。
近期针对智能合约安全分析器的研究也表明,不同工具在真实合约上的检测效果差异明显,并存在误报、漏报以及解释能力不足的问题。因此,不能把静态扫描器的“0 vulnerabilities”直接等价于“合约安全”。(arXiv)
三、第一阶段:明确审计范围
审计开始之前,第一件事不是看代码,而是建立 Scope。
至少需要确认:
项目名称
Git Commit
Solidity Version
Compiler Version
依赖版本
部署链
部署地址
代理结构
Oracle
Token
Bridge
管理员地址
多签地址
例如:
audit:
commit: "8a7c91e"
contracts:
- src/Vault.sol
- src/Router.sol
- src/Oracle.sol
compiler:
solidity: "0.8.24"
network:
- Ethereum
- Arbitrum
upgradeable: true
oracle:
type: Chainlink
必须固定 Git Commit。
否则会出现非常常见的问题:
审计版本:
commit A
↓
开发团队修改代码
↓
部署版本:
commit B
审计报告针对的是 A,实际上链上运行的是 B。
因此生产环境必须建立:
Audited Commit
=
Deployment Commit
这应该成为上线 Gate,而不是依靠人工记忆。
四、第二阶段:先画资产流和权限图
对于 DeFi 项目,审计人员最先应该回答:
钱在哪里?
例如一个 Vault:
User
│
│ deposit
↓
Vault
│
├── ERC20
│
└── Strategy
│
↓
DEX
│
↓
Oracle
进一步标记资产出口:
Vault
│
├── withdraw()
├── emergencyWithdraw()
├── harvest()
├── migrate()
└── upgrade()
这些函数应该进入高优先级审计清单。
权限图同样重要:
Admin
│
├── upgrade()
├── setOracle()
├── setFee()
└── pause()
Operator
│
└── execute()
User
│
├── deposit()
└── withdraw()
审计人员需要验证:
普通用户
X
upgrade()
普通用户
X
setOracle()
Admin
√
upgrade()
访问控制问题在 OWASP 2026 中位列 SC01,因为管理员、治理和升级路径一旦暴露,往往可以直接造成协议级失陷。(OWASP Smart Contract Security)
五、第三阶段:静态分析
静态分析的目标不是证明安全,而是快速发现明显问题。
常用工具包括:
Slither
Mythril
Semgrep
Solhint
Foundry
以 Slither 为例:
slither .
可以检查:
Reentrancy
Access Control
Unchecked Calls
Dangerous Solidity
State Variables
Inheritance
CI 中可以进一步建立安全 Gate:
slither . \
--exclude-dependencies \
--fail-high
但不要把所有 Warning 都视为漏洞。
例如:
Detector
↓
Potential Issue
↓
Manual Review
↓
True Positive?
↓
Exploitability
↓
Risk Rating
自动化扫描应该成为第一层筛选,而不是最终结论。
六、第四阶段:人工代码审查
人工审计最重要的不是逐行阅读,而是建立“攻击者视角”。
建议按照以下顺序审查:
资产入口
↓
状态变化
↓
资产计算
↓
外部调用
↓
资产出口
↓
权限函数
↓
升级函数
↓
紧急函数
重点关注以下函数:
deposit()
withdraw()
borrow()
repay()
liquidate()
swap()
claim()
mint()
burn()
transfer()
upgrade()
initialize()
setOracle()
setAdmin()
尤其是所有能够:
改变余额
改变价格
改变权限
改变实现地址
转移资产
的函数。
七、重入攻击:最经典但仍然危险的漏洞
1. 重入的基本原理
重入攻击的核心是:
合约在内部状态完成更新之前调用外部合约,而外部合约又重新进入原合约。
典型代码:
function withdraw(uint256 amount) external {
require(balance[msg.sender] >= amount);
(bool success,) =
msg.sender.call{value: amount}("");
require(success);
balance[msg.sender] -= amount;
}
问题在于:
检查余额
↓
发送 ETH
↓
攻击合约 fallback()
↓
再次 withdraw()
↓
余额还没有减少
攻击者可以反复执行。
2. 攻击过程
假设:
Victim Balance = 100 ETH
第一次:
withdraw(100)
进入:
balance >= 100
然后:
call attacker
攻击合约重新调用:
withdraw(100)
由于:
balance
仍然是 100,第二次检查依然成功。
形成:
withdraw
↓
external call
↓
fallback
↓
withdraw
↓
external call
↓
fallback
直到资金耗尽或者交易 Gas 耗尽。
八、重入漏洞的修复
最基本的方法是遵循:
Checks → Effects → Interactions
也就是:
function withdraw(uint256 amount) external {
require(balance[msg.sender] >= amount);
balance[msg.sender] -= amount;
(bool success,) =
msg.sender.call{value: amount}("");
require(success);
}
状态先修改:
Check
↓
Update State
↓
External Call
而不是:
Check
↓
External Call
↓
Update State
OpenZeppelin 的 ReentrancyGuard 提供了标准化的 nonReentrant 防护机制,同时官方文档也明确建议结合 Checks-Effects-Interactions 等设计模式使用;对于付款场景,PullPayment 等模式可以进一步减少主动向外部地址推送资产所带来的风险。(OpenZeppelin Docs)
例如:
import {ReentrancyGuard} from
"@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract Vault is ReentrancyGuard {
mapping(address => uint256) public balance;
function withdraw(
uint256 amount
)
external
nonReentrant
{
require(
balance[msg.sender] >= amount,
"insufficient balance"
);
balance[msg.sender] -= amount;
(bool ok,) =
msg.sender.call{value: amount}("");
require(ok, "transfer failed");
}
}
但是需要注意:
nonReentrant不是万能药。
它主要解决特定的函数重入问题,而复杂 DeFi 系统还可能存在跨合约重入、跨函数重入以及 read-only reentrancy 等问题。
因此审计时必须追踪完整调用图:
Contract A
│
├── call B
│ │
│ └── callback A
│
└── call C
│
└── callback B
不能只搜索:
nonReentrant
然后结束审计。
九、预言机操纵:DeFi项目最容易被低估的风险
预言机负责把链外或者其他市场的数据带入智能合约。
例如:
ETH/USD = $3000
借贷协议根据这个价格计算:
Collateral Value
Borrow Limit
Liquidation
问题是:
如果价格来源本身可以被攻击者控制呢?
十、AMM现货价格不能天然当作安全预言机
例如:
uint256 price =
reserveTokenA *
1e18 /
reserveTokenB;
看起来很简单。
但是 AMM 的储备量会随着交易发生变化。
攻击者可以:
Flash Loan
↓
大量买入 Token
↓
改变 Pool Reserve
↓
Spot Price 改变
↓
协议读取错误价格
↓
借款 / 清算 / 兑换
↓
套利
这就是典型的:
价格操纵 + 闪电贷 + 业务逻辑
组合攻击。
OWASP 2026 将价格预言机操纵列为 SC03,并特别指出,弱预言机可能导致抵押不足借款、不公平清算以及错误定价交易;闪电贷则可以进一步放大这种价格偏差。(OWASP Smart Contract Security)
十一、预言机审计应该检查什么
审计时不能只看:
price = oracle.latestAnswer();
而应该检查:
Oracle来源
↓
更新机制
↓
价格新鲜度
↓
价格范围
↓
Decimals
↓
异常值
↓
Fallback
↓
停机行为
例如 Chainlink 类型预言机,需要重点关注:
Round ID
Updated At
Answered In Round
Decimals
以及:
require(
updatedAt + MAX_DELAY >= block.timestamp,
"stale price"
);
不能接受一个已经长时间没有更新的价格。
十二、TWAP与价格保护
相比直接使用 AMM Spot Price,可以考虑时间加权平均价格:
Block 1 Price 3000
Block 2 Price 3010
Block 3 Price 2990
Block 4 Price 3005
计算一定时间窗口内的平均值:
TWAP ≈ Average Price
攻击者就很难仅靠一个区块的大额交易把参考价格永久推到异常位置。
但 TWAP 也不是万能的。
审计人员还需要检查:
窗口长度
流动性深度
价格更新频率
交易对流动性
异常行情
操纵成本
真正的问题不是:
“有没有使用 TWAP?”
而是:
“操纵这个价格需要多少钱?”
如果攻击成本为 100 万美元,而攻击收益为 1000 万美元,那么从经济安全角度看,这个预言机仍然可能存在高危风险。
十三、业务逻辑漏洞
这是很多自动扫描器最难解决的一类问题。
例如:
function claimReward() external {
uint256 reward =
calculateReward(msg.sender);
token.transfer(
msg.sender,
reward
);
}
代码语法完全正确。
但如果:
claimReward()
没有更新:
lastClaimTime
用户就可能:
claim
↓
claim
↓
claim
↓
claim
不断领取同一笔奖励。
这种漏洞不是 Solidity 语法错误,而是:
业务状态机错误
所以审计必须建立不变量(Invariant)。
例如:
用户累计领取奖励
≤
协议应付奖励
又比如:
Total Shares
=
所有用户 Shares 之和
再比如:
Vault Assets
≥
所有用户可赎回资产
一旦这些不变量被攻击者打破,就应该进一步分析是否存在可获利路径。
十四、整数与精度问题
Solidity 0.8.x 对常规整数溢出和下溢提供了运行时检查,但这并不意味着数学计算已经安全。
DeFi 中更加常见的是:
精度损失
舍入方向
Decimals转换
比例计算
Share Price
利率计算
例如:
uint256 shares =
assets * totalShares /
totalAssets;
如果:
assets * totalShares
先发生精度问题,最终结果可能对用户产生经济影响。
审计时需要检查:
乘法是否应该先做
除法是否应该后做
是否发生截断
谁承担 rounding loss
例如:
a / b * c
和:
a * c / b
在整数数学中通常并不等价。
十五、访问控制与权限升级漏洞
很多严重攻击并不需要复杂的 Solidity 技巧。
只需要:
admin权限错误
例如:
function setOracle(
address newOracle
) external {
oracle = newOracle;
}
如果没有权限控制:
Attacker
↓
setOracle(attackerOracle)
↓
伪造价格
↓
借款
↓
Drain
生产代码至少应该使用明确的角色体系:
bytes32 public constant
ORACLE_ADMIN =
keccak256("ORACLE_ADMIN");
function setOracle(address oracle_)
external
onlyRole(ORACLE_ADMIN)
{
oracle = oracle_;
}
审计还需要检查:
DEFAULT_ADMIN_ROLE
UPGRADER_ROLE
PAUSER_ROLE
OPERATOR_ROLE
ORACLE_ROLE
以及:
谁可以授予角色?
谁可以撤销角色?
谁可以修改管理员?
管理员是否是 EOA?
是否使用 Multisig?
是否存在 Timelock?
权限体系实际上是一棵树:
Root Admin
│
├── Upgrade
│
├── Governance
│
├── Oracle
│
└── Emergency
只要 Root Admin 被攻破,下面所有权限都可能失效。
十六、代理与升级合约审计
Upgradeable Contract 是现代 Solidity 项目中非常重要的一类风险。
典型结构:
User
│
↓
Proxy
│
│ delegatecall
↓
Implementation
Proxy 保存:
Storage
Implementation 保存:
Logic
审计时重点检查:
initialize()
upgradeTo()
upgradeToAndCall()
_authorizeUpgrade()
implementation()
admin()
最危险的问题之一是初始化函数没有正确保护:
function initialize()
external
{
owner = msg.sender;
}
如果部署后任何人都能初始化:
Attacker
↓
initialize()
↓
owner = attacker
↓
upgrade()
↓
malicious implementation
因此升级合约审计不能只审 Implementation。
必须把:
Proxy
+
Implementation
+
Admin
+
Upgrade Process
作为一个整体。
十七、模糊测试:让攻击者替你跑代码
单元测试通常是:
输入A
→
预期结果A
而 Fuzz Testing 是:
随机输入
+
随机状态
+
随机调用顺序
例如 Foundry:
function testFuzz_DepositWithdraw(
uint256 amount
) public {
vm.assume(amount > 0);
vm.assume(amount < 1e24);
deposit(amount);
withdraw(amount);
assertEq(
balanceOf(address(this)),
0
);
}
真正有价值的测试不是:
正常流程是否成功
而是:
异常输入是否破坏协议不变量
十八、Invariant Testing
例如一个 Vault:
资产总量
=
所有用户余额之和
+
协议费用
可以表达为:
function invariant_TotalAssetsSafe()
public
{
assertGe(
vault.totalAssets(),
totalUserClaims
);
}
然后让 Foundry 自动生成大量:
deposit()
withdraw()
transfer()
claim()
harvest()
组合调用。
如果发现:
Invariant violated
就进一步缩小攻击路径。
这种方法对于复杂 DeFi 协议尤其重要,因为攻击者往往不是调用一个函数,而是:
A()
→ B()
→ C()
→ A()
→ D()
最终打破一个业务不变量。
十九、攻击路径复现
发现漏洞之后,不应该只在报告里写:
存在重入风险。
必须证明:
攻击前状态
↓
攻击交易
↓
状态变化
↓
资产变化
↓
攻击者收益
例如:
Vault Balance
= 1,000 ETH
Attacker Balance
= 0 ETH
↓
Reentrancy Attack
↓
Vault Balance
= 0 ETH
Attacker Balance
≈ 1,000 ETH
对于高危漏洞,最好提供 PoC。
测试环境:
Local Fork
↓
Mainnet State
↓
Attack Contract
↓
Exploit Transaction
这样才能判断:
理论漏洞
还是:
真实可利用漏洞
二十、漏洞风险评级
推荐至少分为:
| 等级 | 典型影响 |
|---|---|
| Critical | 直接或大规模盗取协议资产 |
| High | 严重资产损失或核心权限接管 |
| Medium | 有限资产影响、DoS或关键业务破坏 |
| Low | 局部安全问题 |
| Informational | 代码质量与最佳实践 |
但是评级不能只根据 Bug 类型。
应该综合:
Impact
×
Likelihood
×
Exploitability
×
Affected Assets
例如:
Reentrancy
如果只能影响一个无价值的测试函数,与:
Reentrancy
+
$100M TVL
+
无需权限
+
可直接获利
风险等级显然不同。
二十一、审计报告应该怎么写
一个合格的漏洞报告至少包括:
Title
Severity
Affected Contract
Affected Function
Root Cause
Attack Scenario
Proof of Concept
Impact
Recommendation
Fixed Version
Retest Result
例如:
[High] Oracle price can be manipulated through
low-liquidity AMM spot price
然后明确:
Affected:
PriceOracle.sol:42
Impact:
攻击者可以操纵抵押品价格,
造成超额借款。
Root Cause:
协议直接读取低流动性池的
spot price。
Recommendation:
使用可信预言机或经过验证的
TWAP/多源价格机制。
不要写成:
这里可能存在安全风险,建议关注。
这种表述对于工程团队没有实际价值。
二十二、修复之后必须重新审计
漏洞修复:
发现问题
↓
开发修复
↓
重新测试
但不能直接关闭问题。
必须检查:
原漏洞是否消失
↓
是否引入新漏洞
↓
是否改变原业务逻辑
↓
是否影响其他合约
例如:
原问题:
withdraw()
开发人员为了修复重入:
加入 nonReentrant
结果可能导致:
withdraw()
↓
claim()
↓
withdraw()
原本合法的调用路径无法运行。
因此需要:
Regression Test
+
Security Test
+
Business Test
二十三、部署前最终安全 Gate
真正上线前,建议建立硬性 Gate:
Build
│
↓
Static Analysis
│
↓
Unit Tests
│
↓
Fuzz Testing
│
↓
Invariant Testing
│
↓
Manual Audit
│
↓
Issue Retest
│
↓
Deployment Commit
│
↓
Mainnet
CI/CD 可以设计为:
security_gate:
static_analysis: required
unit_test: required
fuzz_test: required
invariant_test: required
critical_issue: 0
high_issue: 0
audit_commit_match: true
任何一个条件失败:
STOP DEPLOYMENT
而不是:
先部署
以后再修
智能合约尤其不适合这种传统互联网式的“先上线再迭代”。
二十四、企业级智能合约安全架构
对于资金规模较大的协议,可以建立这样的安全体系:
Git Repository
│
↓
CI/CD
│
┌────────────────┼────────────────┐
│ │ │
Slither Foundry Solhint
│ │ │
└────────────────┼────────────────┘
↓
Security Gate
│
↓
Manual Audit
│
↓
┌─────────┴─────────┐
│ │
Fuzzing Invariant
│ │
└─────────┬─────────┘
↓
Final Review
│
↓
Multisig
│
↓
Timelock
│
↓
Mainnet
上线之后仍然需要:
On-chain Monitoring
│
├── Large Transfer
├── Admin Change
├── Oracle Deviation
├── Upgrade
├── Abnormal Borrow
└── Exploit Pattern
因此,真正成熟的安全体系不是:
Audit → Finish
而是:
Design
↓
Develop
↓
Audit
↓
Deploy
↓
Monitor
↓
Incident Response
↓
Upgrade
二十五、审计中最容易犯的五个错误
1. 只依赖自动化扫描
工具发现的是模式,不是完整业务语义。
2. 只看单个合约
DeFi 攻击经常跨越:
Vault
+
DEX
+
Oracle
+
Token
+
Flash Loan
单独看任何一个合约都可能“没问题”。
3. 忽视经济模型
代码逻辑正确:
x * y = k
并不意味着:
协议一定安全
攻击者真正利用的是:
经济激励
+
价格机制
+
流动性
+
套利空间
4. 忽视管理员权限
很多项目把大量能力放在:
owner
一个地址被盗:
owner
↓
upgrade
↓
malicious implementation
↓
all assets
因此多签、Timelock、权限分离不是“锦上添花”,而是高价值协议的基础控制措施。
5. 审计后继续修改代码
这是实际项目中非常危险的一点。
审计版本:
commit A
部署版本:
commit C
中间:
commit B
可能已经引入新的漏洞。
所以必须做到:
Audited Commit
=
Release Commit
=
Deployment Commit
二十六、从“代码安全”走向“协议安全”
智能合约审计正在从单纯的代码审查转向协议级安全验证。
过去关注:
有没有 reentrancy?
有没有 overflow?
有没有 access control?
现在更加重要的是:
攻击者能不能赚钱?
这会进一步引出:
攻击成本
攻击收益
资本需求
闪电贷可获得性
流动性深度
价格影响
MEV
跨协议组合
治理权限
例如一个价格操纵漏洞:
理论偏差 = 5%
看起来并不严重。
但如果:
TVL = $100M
攻击成本 = $100K
可提取资产 = $5M
那么这个问题就是非常现实的高危攻击面。
因此现代审计必须加入:
Economic Security Review
即经济安全审查。
二十七、结语
智能合约审计的核心,从来不是找到多少个 Warning,而是判断:
在攻击者拥有公开链上全部可见信息、可以自由组合交易、可以调用所有公开函数的情况下,协议是否仍然能够维持自己的安全不变量?
一套真正能够用于生产环境的审计流程应该形成完整闭环:
代码审查
+
自动化分析
+
模糊测试
+
Invariant Testing
+
攻击模拟
+
经济模型分析
+
权限审计
+
部署验证
+
链上监控
其中,重入攻击、预言机操纵、访问控制、业务逻辑、闪电贷组合攻击、代理升级等问题,都不能脱离协议整体进行判断。
尤其需要注意的是,“没有发现漏洞”并不等于“证明没有漏洞”。智能合约审计本质上是风险降低过程,而不是绝对安全证明。对于高价值协议,应当把形式化验证、自动化分析、人工审计、攻击模拟和持续链上监控组合起来。
最终目标不是得到一份漂亮的审计报告,而是在代码真正获得资产控制权之前,把最危险的攻击路径找出来、复现出来、修复掉,并验证修复后的代码确实与最终部署版本一致。
这才是智能合约安全审计真正的价值。
参考资料
OWASP Smart Contract Top 10 — 2026:涵盖访问控制、业务逻辑、价格预言机操纵、闪电贷、未检查外部调用、重入及代理升级等主要智能合约风险。OWASP Smart Contract Top 10 (OWASP Smart Contract Security)
OpenZeppelin Contracts — Security:提供
ReentrancyGuard、Pausable、PullPayment等经过广泛使用的安全组件,并说明了 Checks-Effects-Interactions 等防护思路。OpenZeppelin Contracts Security 文档 (OpenZeppelin Docs)Marchesi et al., Security Checklists for Ethereum Smart Contract Development:从设计、编码、测试和部署生命周期讨论智能合约安全模式与审计检查方法。论文:Security checklists for Ethereum smart contract development (arXiv)
- 网渡科技官方文档:智能合约审计流程详解
https://www.wangdu.net.cn/announcements/58