查看原文
其他

智能合约中的“高铁座霸”|存储器局部变量未初始化——漏洞分析连载之七

链安科技 区块链大本营 2018-10-26




安全,区块链领域举足轻重的话题,本期我们带你分析存储器局部变量未初始化会带来什么后果?我们又需要注意什么?


「区块链大本营」携手「成都链安科技」团队重磅推出「合约安全漏洞解析连载」,以讲故事的方式,带你回顾区块链安全走过的历程;分析漏洞背后的玄机。让开发者在趣味中学习,写出更加牢固的合约,且防患于未然。


当然,这些文章并不是专为开发者而作的,即使你不是开发者,当你读完本连载,相信再有安全问题爆出时,你会有全新的理解。


引子:行身践规矩,甘辱耻媚灶。——韩愈                 


上回讲到:

区块链游戏江山如画

安全防护未规划

一片残阳西挂


我们在上一期的区块链游戏漏洞的汇总和分析中将目前游戏合约出现的问题与前几期的漏洞连载分析进行了联动,发现游戏合约的漏洞很大一部分是在重复之前代币合约的重大错误。开发者在被鲜亮外衣包裹的游戏合约吸引更多眼球的同时,也需要对安全问题提高重视,才能获得更长远的发展。


本回咱们来聊聊:

本地变量存储措手不及

意外变量覆盖易帜拔旗


最近新闻上的“座霸”事件,在社会中引起了强烈的反响,一个理应对号入座的乘车环境,在某些人不守规矩的情况下,导致买了票的乘客没有座位,以及车厢内的秩序混乱。





于是我们联想到,没有对号入座而引起混乱的这个问题,其实在智能合约漏洞问题当中也有类似的情况。



基础小知识


大家都清楚,谈到存储,变量被存储时都会被分配一个存储位置。这个位置可以被理解为乘车时的座位。


在智能合约语言 Solidity当中,存在Storage(存储器)Memory(内存)两个不同的概念。Storage变量是指永久存储在区块链中的变量。Memory变量是临时的,这些变量在外部调用结束后会被移除。


但是Solidity目前对复杂的数据类型,比如array(数组)和struct(结构体),在函数中作为局部变量时,会默认储存在Storage当中。


此外,Solidity对于状态变量,存储次序一般是按照出现的先后顺序依次排列的。这些状态变量的位置就相当于它们的座位。



问题出在哪


Solidity与传统语言有一个很明显的不同,就是允许定义一个指向Storage的引用未初始化的外部指针(引用)会默认指向起始地址,如果不加以初始化,直接进行赋值,0地址上的状态变量就会被覆写。


拿乘坐列车打个比方,第一批乘客上车时没有安排相应的座位号,于是大家都是按照上车顺序,从前往后坐。到了新的目的地,第二批乘客上车时没有被指定座位号,坐剩余的座位,又想从前往后坐,第一批乘客原本坐在座位上,现在直接被“座霸”赶走,无位可坐。






合约中的“座霸”实例


开发中的缺一手


这种漏洞原先体现为由于开发者疏忽遗留出的已被攻击者利用的漏洞,例如有关安全公司发现的BancorLender相关的指针问题。





如图所示,状态变量agreements一开始被声明在第一个黄色框内,进入起始位置slot 0x00


第二个黄色框框是在函数offerToLend()中试图声明一个新的局部变量agreement,但其未做初始化处理,所以起始位置slot 0x00会被新的局部变量agreement占据。更具体些讲,从粉红色划线处开始的后面三项赋值操作都会覆盖slot 0x00slot 0x03上原有的值。


最后导致了代码逻辑紊乱,功能无法正常实现[1]


蜜罐中的留一手


此外,联系上一期我们提到的游戏合约,这个漏洞不出意外的在游戏合约中也出现了,但是出现的形式是蜜罐,蜜罐我们之前也提到过,是故意放置明显的破绽让略懂技术的玩家以为有机可趁,但实际上更深处有合约拥有者留给自己的不公平获利操作空间。所以我们将这种情况归为此类漏洞的第二种具体情况。


案例来源于一个名为OpenAddressLottery的博彩合约。





这部分代码中的“s”被声明但是并没有做相应的初始化处理,所以实际上之后的赋值操作都会覆盖原有地址上重要的值。


会代替哪些值呢?我们来看谁“坐在”最初始Storage地址上:





所谓的可预测的最终答案是LuckyNumber占据的最初始的位置,所以实际上是会被tx.gasprice*7所覆盖的, 而address owner会被msg.sender代替,但这两个值实际上是一样的。


由于luckyNumberOfAddress的结果以模8(二进制)的形式计算,而被覆盖后tx.gasprice*7真实的结果一定会大于7,所以玩家想赢是绝对不可能的[2]


其中我们还要注意一点,第一部分代码中,require(msg.send==owner),表示只有合约拥有者才能调用这个能覆盖原有值的函数,所以合约的蜜罐意图非常明显。


最终的结果也符合了我们的推断:





合约创建者在转出所有参与者的资金后,启动自毁,逃之夭夭。






表现形式总结与修复建议


总结上述具体案例的情况,我们可以说:


未初始化的存储器局部变量可以指向合约中的状态变量,从而导致故意(即开发人员故意将它们放在那里进行攻击)或无意的漏洞。


我们将一些典型的默认储存在Storage中的变量分为结构体(struct)和数组(Array)展示出错误范例。


典型结构体(struct)错误范例:




当输入

_name="0x0000000000000000000000000000000000000000000000000000000000000001"(63个0),地址任意地址时,会覆盖unlocked的值,使其变为true。


典型数组(Array)错误范例:





当输入

elements=["0x0000000000000000000000000000000000000000000000000000000000000001"](63个0)

会覆盖frozen的值,使其变为true。


漏洞修复建议


Remix-ide等编译器会对未初始化的存储器局部变量进行告警,开发人员不能忽略这个警告,在声明变量时,应对这些存储器局部变量进行初始化,或者根据其使用情况,将其安排在暂时的存储空间Memory上,避免安全漏洞。



良好的秩序,良好的心态


本期介绍的漏洞,是由于Solidity语言的默认存储规则,以及引用未初始化变量的特殊性共同导致的。在传统语言当中,这个情况会在编译器当中报错,无法通过。目前的Solidity版本(0.4.24)却没有进行相同严格的禁止, 只会在编译器中给出告警。


所以我们在这里针对智能合约开发和使用两方面再次强调:


  1. 遵守合约开发规范,缜密筹备安全防护,是我们屡次三番提到的合约开发精神,在区块链这个新兴的技术应用时遵守规范、周全规划,才能更好的帮助新兴技术稳步发展。

  2. 对于蓄谋欺骗大众的投机合约,当发现破绽时,一定要提防合约创建者的蜜罐手段,多留一个心眼,避免上当受骗。


目前的区块链是一个尚未成熟,有待发展的产业,追逐机遇的同时,做到冷静思考,心态平和是成功的必备素养,请大家给变量分配位置的同时,也给自己的心态调整位置。





引用:

[1] Solidity缺陷易使合约状态失控

https://zhuanlan.zhihu.com/p/41412333

[2] How does this honeypot work? 

https://www.reddit.com/r/ethdev/comments/7wp363/how_does_this_honeypot_work_it_seems_like_a/

[3] 构造函数失控、未初始化的存储指针 

https://ethfans.org/posts/comprehensive-list-of-common-attacks-and-defense-part-7


相关阅读:


杨霞  

成都链安科技CEO,创始人。电子科技大学副教授,最早研究区块链形式化验证的专家。一直为航空航天、军事领域提供形式化验证服务。主持国家核高基、装发重大软件课题等近10项国家课题。CC国际安全标准成员、CCF区块链专委会委员。发表学术论文30多篇,申请20多项专利。是成都链安科技有限公司创始人之一,该公司专注于区块链安全领域,其核心技术为形式化验证。


(内容转载请联系微信:qk15732632926)

(商务合作请联系微信:fengyan-1101)


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存