静态分析是相对容易掌握的工具,对开发复杂的Defi应用非常有帮助。

简介

合约的安全性自动化检测有静态分析、动态分析和形式化验证。静态分析不执行合约代码,通过对合约代码做模式匹配或者语义分析来检测漏洞。动态分析需要执行合约,通过大量的模糊测试来观察合约的状态是否会出现问题。形式化验证是将合约的业务逻辑用数学表达式来描述,只要证明数学表达式是正确的,则合约的业务逻辑也是正确的(不代表合约的实现没有问题)。

静态分析的优点是使用简单,速度快,但只能检测已知的安全漏洞。动态分析能检测出未知的安全问题,但是成本高、速度慢。形式化验证的使用范围窄,比较适用于一些公共库合约。

开发者对合约做静态分析是最基本的要求,使用静态分析工具可以快速检测是否存在一些常见的漏洞,比如:

  • 权限缺失,比如Oracle的更新没有设置权限
  • 重入,这个出的问题最多
  • 整数溢出
  • DDOS,攻击或者缺陷会导致合约无法执行正常的业务逻辑
  • 价格操纵

但静态分析工具不能检测出跟业务逻辑特定相关的问题,还需要开发人员通过自检去做人工静态分析。

Solhint

Solhint能提供一些代码规范和安全检查,一些推荐的代码规范比如:

  • 对字符串使用双引号
  • 使用驼峰命名规则
  • 明确指定状态变量的可见性
  • 避免使用call,delegatecall等底层操作码
  • 避免使用tx.origin
  • 同一个方法中避免多次使用msg.value
  • 避免使用block.number和block.timestamp

Solhint的能力较弱,只能做到语法层面的一些检查,但对规范代码比较有用。

Semgrep

Semgrep是一个通用型的静态分析工具,支持多种语言,对solidity的支持目前还较弱。Semgrep跟Solhint一样也是采用模式匹配来进行检测,Solhint的规则是内置的,Semgrep能自定义规则。比如下面这个规则compound-sweeptoken-not-restricted

rules:
 -
    id: compound-sweeptoken-not-restricted
    message: function sweepToken is allowed to be called by anyone
    metadata:
        references:
        - https://medium.com/chainsecurity/trueusd-compound-vulnerability-bc5b696d29e2
        - https://chainsecurity.com/security-audit/compound-ctoken/
        - https://blog.openzeppelin.com/compound-comprehensive-protocol-audit/
        - https://etherscan.io/address/0xa035b9e130f2b1aedc733eefb1c67ba4c503491f # Compound
        category: access-control
        tags:
        - compound
        - tusd
    patterns:
    - pattern-inside: |
        function sweepToken(...) {
        ...
        }
    - pattern: token.transfer(...);
    - pattern-not-inside: |
        require(msg.sender == admin, "...");
        ...
    languages: 
    - solidity
    severity: WARNING

这个规则专门针对的Compound曾经出现过的TUSD漏洞,由于有很多其它链的项目fork了Compound,因此这个规则可以快速检测出这些项目是否有类似的问题。

Slither

Slither的功能包括:

  • 漏洞自动检测
  • 提供代码优化建议
  • 展现代码的拓扑结构
  • 通过API能自定义漏洞检测规则

Slither的原理是将Solidty抽象语法树(AST)作为输入:

  • 第一步,先解析出合约间的继承图、控制循环图(CFG)和表达式。
  • 第二步,将合约代码转换成SlitherIR(一种内部表达码)。
  • 第三步,对SlitherIR执行一系列单一静态分析(SSA)来完成漏洞检测。

Solhint和Semgrep都是在语法级别进行规则匹配,相比而言Slither能在语义级别进行分析。Slither也可以通过插件来实现自定义的漏洞检测规则,实现上要比Semgrep这种配置文件的方式复杂点。

自检

相对于自动检测工具而言,开发者的自检能完成更复杂的静态分析。比如

address[] public minters;
function setMinter() external {
    minters.push(msg.sender);
}

静态分析工具没法知道修改minters这个状态变量需要什么权限,因为这属于业务逻辑的范围。再比如

if (a > 100) {
    b++;
}

如果开发者误将a >= 100写成了a > 100,这种业务逻辑错误静态分析工具也没法处理。

合约的业务逻辑都是主要在接口中实现的,因此接口检查就很重要:

  • 参数是否有校验,尤其是需要注意是否有任意输入。
  • 接口必须是external或者public吗?如果把一个internal或者private接口暴露出去会非常危险。
  • 需要加payable吗?不加的话没法接收eth,但是若无必要则一定不要加。
  • 接口会修改状态变量吗?修改这些变量需要权限吗?这一点往往是静态分析工具无法检测到的漏洞。
  • 通过call或者delegatecall的调用对象是可信的吗?
  • 外部调用需要设置gasLimit吗?外表调用的返回结果需要处理吗?
  • 在有外部调用的代码前后遵守了Checks-Effects-Interactions规范吗?
  • 外部调用能重入到合约中的其它接口然后通过旁路回到本接口吗?

如果接口涉及到Token的转移,则需要的检查有:

  • 如果Token转移过程中内部扣费会影响业务逻辑吗?
  • Token转移过程中会有钩子函数回调发送者或者接收者吗?
  • Token如果是可升级合约,对业务逻辑有影响吗?
  • 有使用地址的eth余额参与控制逻辑吗?eth的余额是可以通过挖矿或者selfdestruct强制增加的。

对于借贷相关的合约,一般需要使用价格,则需要的检查有:

  • Offchain oracle是可靠的吗?
  • Onchain oracle的价格容易被操纵吗?
  • LP token的价格计算算法是正确的吗?

总之,所有的检查都围绕几个核心:

  • 敏感的权限是否能被转移到任意地址
  • 资产是否有可能被较小的代价转走
  • 资产是否有可能无法取出

结束语

静态分析是相对容易掌握的工具,对开发复杂的Defi应用非常有帮助。不同的静态分析工具可以结合使用,可以先使用Solhint来规范代码,然后使用Semgrep来识别已知的漏洞,接着使用Slither来识别一些语义级别的问题。

开发者更需要自己检查代码,最好是邀请同行互审。最后还是需要审计机构审计代码,不过也不要迷信审计机构,尤其是当Defi的业务逻辑比较复杂的时候,审计机构不一定能精确地理解每一个业务逻辑。

转载自:https://learnblockchain.cn/article/4270