精通以太坊-智能合约安全
九、智能合约安全
9.3 重入
原理:调用外部fallback函数来进行转账,导致合约可重入
防范技术:
- 用transfer()进行转账,虽然会带来额外gas
- 使用“检查-生效-交互”模式
- 使用互斥锁
案例:DAO攻击
9.4 算术溢出
原理:Solidity中整数存在上下限
防范技术:
- 使用SafeMath库合约
案例:PoWHC
9.5 意外的以太币
原理:强制发送以太币导致合约无法执行,self-destruct或预先发送都可以实现
防范技术:
- 少用this.balance,使用状态变量保存余额
9.6 delegatecall
原理:delegatecall保持上下文,状态或存储变量(可以在独立的交易之间保持其数值的变量)会按照它们被引入合约的顺序放置在存储槽中,这时我们改变的变量可能不是我们期望改变的。
防范技术:
- 使用library关键字可以保证库合约是无状态和不会自我销毁的
案例:Parity多重签名钱包的第二次攻击
9.7 默认的可见性
原理:Solidity函数可见性默认为public,可能出现纰漏
防范技术:为每个函数都指定可见性
案例:Parity多重签名钱包的首次攻击
9.8 无序错觉
原理:为了在以太坊中引入无序性(即随机性),使用哈希值、区块号等可被操控的区块变量作为随机值
防范技术:无序性必须来自区块链外部
案例:PRNG合约
9.9 外部合约引用
原理:可以故意将地址指向错误的合约,假如引用的合约不包含要调用的函数,将执行回退函数。如果用户可以修改合约引用的合约库,那么基本上他们就可以使其他用户在不知情的情况下运行任意代码。
防范技术:
- new一个要引用的合约
- 对外部合约进行硬编码
一般建议将合约地址设置为public以方便用户检查引用的合约
案例:可重入的蜜罐
9.10 短地址攻击
原理:当实际发送的数据长度小于标准编码长度时,EVM将在数据末尾补0,这种操作可能影响末尾参数
防范技术:对参数进行校验
9.11 未检查的调用返回值
原理:send和call外部调用失败仅返回false,不revert
防范技术:
- 检查返回值
- 使用withdraw模式
9.12 预先交易
原理:gas高的优先被打包
防范技术:
- 设置gas上限
- 使用commit-reveal模式
- submarine sends
案例:erc20的approve函数
9.13 拒绝服务
原理:各种原因使合约不可用,比如说事先向合约转账使合约无法通过某些限制条件
防范技术:各个情况方法不同
9.14 区块时间戳操纵
原理:区块时间戳可被矿工指定。
防范技术:不应使用区块时间戳作为随机数。
案例:GovernMental
9.15 小心使用构造函数
原理:solidity0.4.22前,构造函数和合约同名;若合约名称和构造函数名称不同,构造函数将变成一个普通函数。
防范技术:使用constructor关键字。
案例:Rubixi
9.16 未初始化的存储指针
原理:状态变量和复杂类型的局部变量都存储在storage中;状态变量按照顺序保存在storage中(存储槽),未初始化的局部变量可能指向已有的状态变量。
防范技术:用storage和memory关键字明确指定变量的存储位置。
案例:OpenAddressLottery
9.17 浮点数和精度
原理:solidity的除法会舍去小于除数的所有精度。
防范技术:先执行加法和乘法,再执行减法和除法。
案例:Ethstick
9.18 Tx.Origin验证
原理:全局变量tx.origin表示最初发起交易的账户地址;若使用tx.origin作为判定条件,合约易受到钓鱼攻击。
防范技术:少用tx.origin作为判定条件。
可用require(tx.origin == msg.sender)来拒绝外部合约调用。
9.19 合约程序库
OpenZepplin包含丰富的库合约。
ZeppelinOS使开发者更简单地发布DApp。
疑惑
- 谁可以调用合约的self-destruct
- 外部合约引用的防范技术没看懂