智能合约自动审核指南。 第2部分:Slither

分析器:Slither
说明:Solidity的开源静态分析框架
吉比特(Githib): https//github.com/trailofbits/slither


这是用python编写的静态代码分析器。 他知道如何监视变量,调用并检测这样的漏洞列表 。 每个漏洞都有一个带有描述的链接,如果您是Solidity的新手,那么对每个人都有认识是很有意义的。


Slither可以用作python模块,并为程序员提供根据自己的计划进行审核的接口。 在这里可以看到有关滑行器可以做什么的简单说明性示例。


我们将在本文结尾处返回分析场景,但是现在运行Slither:


git clone https://github.com/trailofbits/slither.git cd slither docker build -t slither . 

并尝试分析我们的合同。


我们通过constructor-eth-booking进入该目录,并尝试从docker运行一点:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 

我们得到错误“源文件需要不同的编译器版本”,现在我们需要将solc=0.4.20的版本放到更简单的solc=0.4.20 。 要做到这一点,我们纠正了Slither本身的Dockerfile,如引言中第2节所述 。 在Dockerfile末尾的某处, 我们添加以下行:


 COPY --from=ethereum/solc:0.4.20 /usr/bin/solc /usr/bin 

,重建图像,运行,欢呼,所有内容都会编译。


我们看到输出,警告有关不同的“ pragma”和有关不正确的变量名称,如Parameter '_price' of Booking.Booking (flattened.sol#73) is not in mixedCase 。 分析器发出很多警告,但我们正在寻找真正的错误,因此我们不会关注这些琐事。 我们过滤掉了所有关于mixedCase的消息,但现在不符合样式:


 $ docker run -v $(pwd)/contracts:/slither/contracts slither contracts/flattened.sol 2>&1 | fgrep -v 'mixedCase' 

真正的程序员会错过所有绿色的东西,而会看到所有红色的东西,这是Slither在合同中发现的那些错误肯定的东西:


 Booking.refundWithoutCancellationFee (flattened.sol#243-250) sends eth to arbirary user Dangerous calls: - client.transfer(address(this).balance) (flattened.sol#249) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations INFO:Detectors: Booking.refundWithCancellationFee (flattened.sol#252-259) sends eth to arbirary user Dangerous calls: - owner.transfer(m_cancellationFee) (flattened.sol#257) - client.transfer(address(this).balance) (flattened.sol#258) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description#functions-that-send-ether-to-arbitrary-destinations 

现在查看合同中此功能的问题:


  /************************** PRIVATE **********************/ function refundWithoutCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.OFFER); client.transfer(address(this).balance); } function refundWithCancellationFee() private { address client = m_client; m_client = address(0); changeState(State.CANCELED); owner.transfer(m_cancellationFee); client.transfer(address(this).balance); } 

在这种情况下,例如,函数refundWithoutCancellationFee()的调用如下:


  function rejectPayment() external onlyOwner onlyState(State.PAID) { refundWithoutCancellationFee(); } function refund() external onlyClient onlyState(State.PAID) { refundWithoutCancellationFee(); } 

嗯,正式而言没有错误:呼叫受到各种onlyOwner保护,但是Slither发誓,他们说,广播在发送时不带任何检查而被包含在一体化的ReplyWithoutCancellationFee()中。 他是对的,该函数本身实际上几乎没有任何限制。 将其设为私有,并从包装器中调用“ rejectPayment()”和“ refund()”,并提供必要的限制,但是在这种形式下,如果您修改合同,则很有可能忘记这些限制,并将refundWithoutCancellationFee()调用refundWithoutCancellationFee()在其他地方,可供攻击者使用。 因此,即使正式不存在漏洞,该信息也被证明是有用的-如果转让计划进一步开发合同代码,则该信息至少是“警告”级别。 在这种情况下,来自不同参与者的两个功能使用相同的代码,并且此决定是为了节省燃油-合同是一次性的,其计算成本是一个重要因素。


我重新检查了Slither是否在咒骂任何广播,并将功能主体直接转移到上面的“ rejectPayment()”和“ refund()”,警告消失了,也就是说, Slither意识到现在没有地址检查就不会发送广播。 伟大的开始!


现在让我们检查Slither如何监视变量的初始化,为此,我们对两个初始化进行评论:


 - m_fileHash = _fileHash; + // m_fileHash = _fileHash; - m_price = _price; + // m_price = _price; 

就漏洞而言,除了浪费资源之外,第一个不是很重要,因为m_fileHash并未在任何地方使用,因此在创建合同时它只是存储在区块链上。 但是使用了m_price,尽管Slither正确地发誓尽管已使用m_price,但并未在任何地方对其进行初始化:


 Booking.m_price (flattened.sol#128) is never initialized. It is used in: - fallback (flattened.sol#144-156) 

好吧,这是一个简单的技巧,正如预期的那样,一切正常。


现在,我们将合同中每个人都喜欢的reentranc添加到合同中:在外部呼叫后,我们将更改合同的状态。 我们进行以下更改:


  function refundWithoutCancellationFee() private { address client = m_client; - m_client = address(0); - changeState(State.OFFER); - client.transfer(address(this).balance); + client.call.value(address(this).balance)(); + m_client = address(0); + changeState(State.OFFER); } 

我不得不用呼叫代替转移,因为 传输变体不会发誓,因为传输会以最少的耗气发送呼叫,并且不可能进行回叫(尽管在以太坊中切换到君士坦丁堡货叉时,耗气价格已更改,这重新启用了使用transfer的重入攻击。


再入搜索结果:


 Reentrancy in Booking.refundWithoutCancellationFee (flattened.sol#243-253): External calls: - client.call.value(address(this).balance)() (flattened.sol#245) State variables written after the call(s): - m_client (flattened.sol#246) 

很好,至少在外部调用后不会提供状态变量,这很好。


如果沿列表移动,则列表中的其余漏洞仅是搜索代码中的特定方法或已知模式,当然,如果可以使用python访问标记,则这些模式将非常可靠地工作。 即 Slither不会错过的众所周知的模式。


现在,我将进行一些更改,以完美显示静态分析器的工作细节:


 - client.transfer(address(this).balance + for (uint i=0; i < 1; i++) { + client.transfer(address(this).balance - 999999999999999999); + } 

结果:


 Booking.refundWithoutCancellationFee has external calls inside a loop: - client.transfer(address(this).balance - 999999999999999999) (flattened.sol#252) Reference: https://github.com/trailofbits/slither/wiki/Vulnerabilities-Description/_edit#calls-inside-a-loop 

该循环执行一次,并退化-因此,发出的警告为假肯定,而没有关于危险算术的警告为假否定。 类型分析,操作结果,呼叫计数-任务不适用于静态分析器。 因此,清楚地了解Slither将发现哪些错误,以及使用其他工具应寻找哪些错误。


我们承诺会提到自己编写测试脚本的可能性,以及使用--print键输出有关合同的任何有趣信息的可能性。 从这个角度来看,Slither是CI的出色工具。 大型合同系统的开发人员知道安全关键变量的名称:余额,佣金大小,标志,并且可以编写测试脚本来阻止代码中的任何更改,例如,覆盖重要变量,或在外部调用后更改状态变量,以及其高度可预测的分析是在挂钩中使用的绝佳工具。
Slither的任务是使您摆脱愚蠢的错误,找到众所周知的危险模式并警告开发人员。 在此版本中,它是Solidity的新手开发人员的良好工具,可立即提示如何正确地在Solidity中编写代码。


总结


在我的个人测试中,出于通用性,简单性和易用性以及简单易懂的测试脚本和对CI的适应性,我将Slither放在第四位。


Slither自信地找到了与使用发送广播功能相关的真实警告,它发现了所有引入的错误。 他不能只应付动态分析,而动态分析是正式不应该做的,否则他将不得不牺牲通用性,可预测性和易用性。


在下一篇文章中,我们将讨论Mythril分析器,但这是准备或计划编写的文章的目录:


第1部分。 简介。 编译,展平,Solidity版本
第2部分。Slither(本文)
第3部分。 秘银
第4部分。Manticore(在写作过程中)
第5部分。针chi(在编写过程中)

Source: https://habr.com/ru/post/zh-CN438338/


All Articles