Solidity进阶之静态分析
静态分析是相对容易掌握的工具,对开发复杂的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的业务逻辑比较复杂的时候,审计机构不一定能精确地理解每一个业务逻辑。
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »