精通以太坊-智能合约安全

九、智能合约安全

9.3 重入

原理:调用外部fallback函数来进行转账,导致合约可重入

防范技术:

  1. 用transfer()进行转账,虽然会带来额外gas
  2. 使用“检查-生效-交互”模式
  3. 使用互斥锁

案例:DAO攻击

9.4 算术溢出

原理:Solidity中整数存在上下限

防范技术:

  1. 使用SafeMath库合约

案例:PoWHC

9.5 意外的以太币

原理:强制发送以太币导致合约无法执行,self-destruct或预先发送都可以实现

防范技术:

  1. 少用this.balance,使用状态变量保存余额

9.6 delegatecall

原理:delegatecall保持上下文,状态或存储变量(可以在独立的交易之间保持其数值的变量)会按照它们被引入合约的顺序放置在存储槽中,这时我们改变的变量可能不是我们期望改变的。

防范技术:

  1. 使用library关键字可以保证库合约是无状态和不会自我销毁的

案例:Parity多重签名钱包的第二次攻击

9.7 默认的可见性

原理:Solidity函数可见性默认为public,可能出现纰漏

防范技术:为每个函数都指定可见性

案例:Parity多重签名钱包的首次攻击

9.8 无序错觉

原理:为了在以太坊中引入无序性(即随机性),使用哈希值、区块号等可被操控的区块变量作为随机值

防范技术:无序性必须来自区块链外部

案例:PRNG合约

9.9 外部合约引用

原理:可以故意将地址指向错误的合约,假如引用的合约不包含要调用的函数,将执行回退函数。如果用户可以修改合约引用的合约库,那么基本上他们就可以使其他用户在不知情的情况下运行任意代码。

防范技术:

  1. new一个要引用的合约
  2. 对外部合约进行硬编码

一般建议将合约地址设置为public以方便用户检查引用的合约

案例:可重入的蜜罐

9.10 短地址攻击

原理:当实际发送的数据长度小于标准编码长度时,EVM将在数据末尾补0,这种操作可能影响末尾参数

防范技术:对参数进行校验

9.11 未检查的调用返回值

原理:send和call外部调用失败仅返回false,不revert

防范技术:

  1. 检查返回值
  2. 使用withdraw模式

9.12 预先交易

原理:gas高的优先被打包

防范技术:

  1. 设置gas上限
  2. 使用commit-reveal模式
  3. 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。

疑惑

  1. 谁可以调用合约的self-destruct
  2. 外部合约引用的防范技术没看懂