区块链安全—详谈合约攻击(二)
字数 1309 2025-08-22 18:37:15
智能合约安全详解与防御措施
一、智能合约概述
1.1 智能合约概念
智能合约是代码与数据的集合,被部署在区块链的某个具体地址中。它具有以下特性:
- 自动化执行:在特定时间或事件触发下自动执行
- 状态修改:可以修改区块链状态
- 信息传递:能够传递信息和价值
- 账户系统:拥有自己的账户系统
智能合约最早在20世纪90年代提出,但直到区块链技术出现后才获得可信执行环境。
1.2 智能合约编程语言
不同区块链平台使用不同的智能合约语言:
- 以太坊:主要使用Solidity语言,编译为EVM字节码
- Corda:使用Java
- HyperLedger Fabric:使用Java和Go开发ChainCode链码
二、DApp开发流程
- 用Solidity编写智能合约
- 用solc编译器将合约编译为EVM字节码
- 将编译好的字节码发送到DApp前端
- 前端将合约部署到区块链中
- 区块链返回智能合约地址
- 前端通过地址+ABI+nonce调用智能合约
- 智能合约开始处理
三、EVM虚拟机安全限制
3.1 变量类型错误
问题示例:
for(var i = 0; i < employees.length; i++) {
// 循环体
}
问题分析:
var i = 0中,i的类型是uint8(存储范围0-255)- 如果数组元素超过255个,循环将不会终止,直到Gas耗尽
解决方案:
for(uint i = 0; i < employees.length; i++) {
// 使用uint而非var
}
3.2 Gas限制问题
问题示例:
for(uint i = 0; i < employees.length; i++) {
uint bonus = calculatebonus(employee); // 复杂计算消耗大量Gas
employee.send(bonus);
}
问题分析:
- 复杂计算在循环中会快速消耗Gas
- 一旦Gas耗尽,所有状态变化会被回滚,但Gas费用不退
解决方案:
- 将复杂计算移出循环
- 使用映射预先存储计算结果
mapping(address => uint) bonuses;
function calculateBonus(address employee) returns (uint) {
uint bonus = 0; // 可调控
bonuses[employee] = bonus;
}
3.3 调用堆深度限制
问题描述:
- EVM调用深度限制为1024
- 攻击者可递归调用1023次后调用目标函数,使send失败
攻击示例:
function hack() {
var count = 0;
while (count < 1023) {
this.hack();
count++;
}
if (count == 1023) {
thecallingaddr.call("sendether");
}
}
防御措施:
- 检查send的返回值
- 失败时抛出异常
function sendether() {
if (!addr.send(20 ether)) {
throw; // 防止调用深度攻击
}
}
四、交易顺序依赖性攻击
4.1 攻击模型
- 攻击者部署有奖竞猜合约
- 监听网络等待用户提交答案
- 发现答案提交后,立即发送新交易降低奖金数额
- 提高gas使修改交易优先处理
- 结果:提交者获得极低奖励,攻击者免费获取答案
4.2 ERC20标准中的approve漏洞
漏洞原理:
- 用户A批准B转移N个token
- A后来想改为批准M个token,再次调用approve
- B快速发送交易转移N个token,使第一次approve先执行
- 然后B又转移M个token
攻击结果:
- B成功转移N+M个token,而非预期的M个
防御建议:
- 使用increaseApproval/decreaseApproval而非直接approve
- 或先设置为0,再设置新值
五、总结与最佳实践
-
变量类型:
- 明确指定变量类型,避免使用var
- 注意整数类型的范围限制
-
Gas优化:
- 避免在循环中进行复杂计算
- 预先计算并存储结果
- 考虑Gas成本设计合约逻辑
-
调用深度:
- 检查关键操作(如send)的返回值
- 失败时正确处理(回滚或记录)
-
交易顺序:
- 设计合约时考虑交易顺序可能的影响
- 对关键状态变更增加时间锁或确认机制
-
ERC20安全:
- 谨慎使用approve函数
- 考虑使用更安全的授权模式
通过理解这些安全问题和防御措施,开发者可以编写更健壮、更安全的智能合约,保护用户资产免受攻击。