欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

从一道CTF题看智能合约的安全问题

来源:本站整理 作者:佚名 时间:2018-07-21 TAG: 我要投稿
private 误区
通过了时间检查之后,合约的调用还需要满足 require(secret == _secret); 即传入的 secret 值需要与合约中设定的一个值相同。这个值在合约初始化时设定,属性为 private。 在传统的编程语言中我们习惯于使用public和 private来区分类中对象的可见性,一般而言声明为 private 的对象是外部不可见的。智能合约中同样沿用了这一规定,在智能合约给外部提供的接口中确实没有 private 对象的访问路径。但是智能合约与其他传统可执行程序不同,合约本身连同其所有数据(storage) 都是保存在区块上的,而区块对所有人可见,这也就意味着即使是 private 对象在区块上也是可见的(实际上对于传统可执行程序而言,private 属性对象在内存中也是可见的)。 在本例中,_secret是一个全局对象,又称为 StateVarible ,其存储位置为 storage,和合约一起位于区块中。在调试合约时查看合约的 storage ,可以清晰的看到合约存储情况,在 slot 3 位置保存着我们初始化合约时填入的 secret 值
0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b: Object
key: 0x0000000000000000000000000000000000000000000000000000000000000003
value: 0x41000000000000000000000000000000
如果合约的编写者对 private 的了解存在误区,将合约的某些敏感功能依赖于 private 对象,那么就有可能出现权限问题。
小心 Storage Pointer
最后还需要绕过检查 require(msg.sender == owner);,owner 是在合约初始化时设定的一个 StateVarible,存储在 storage 空间中,对应的 slot 为 2。
0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace: Object
key: 0x0000000000000000000000000000000000000000000000000000000000000002
value: 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
想要绕过这个检查需要将合约的 owner 设置为自己的,但是在合约中我们并没有发现相应的接口。
在 Solidity 的编译阶段存在这样一个奇怪的现象,即函数中声明的临时变量“指针”默认的存储位置 slot 都为 0。按照 EVM 的规定,临时变量的存储位置也在 storage 中,这就造成了一个现象,即临时变量的存储位置和全局变量相重叠,通过修改这个临时变量可以覆盖全局变量的值。
在这个合约中就存在这样的问题,invest函数中的临时对象 investment 是结构体Investment的一个实例,当新增用户时,这个 inverstment 对象还处于未初始化状态,此时使用这个未初始化的 “对象指针” 就会从默认 slot 开始计算偏移并执行操作。那么在执行investment.owner = msg.sender;语句时就会导致问题,覆盖 StateVarible owner。
一般而言所有在函数中进行的临时 struct 变量或者 Array 变量的操作都存在这种威胁的可能,但是从我们审计过的合约来看,大部分使用了 struct 的合约都是以Investment storage investment = balances[account];这种形式调用的。这种方式初始化的临时对象在实际操作时实际上会转化成直接操作原始对象,即本合约中的investment.amount += msg.value;在实际操作时会变成balances[account].amount += msg.value;,因此绝大多数合约还是安全的,但是不排除像本例中的合约一样的可能。
不可控的 this.balance
在绕过了上述的检查之后理论上是可以调用 confiscate了,但是在实际的调用中我们发现还是有一点问题。在调用msg.sender.transfer(total); 的时候会发生回滚。那么原因自然是 total 计算错误。
正如之前所说的,StateVarible 和临时变量所占用的位置是一样的,通过临时变量可以修改 StateVarible ,同样通过 StateVarible 也可能修改临时变量。investment.amount = msg.value; 操作会覆盖 StateVarible balance 数组,数组成员在 EVM 中是以 hash 的形式存储的,在数组对象对应的 slot 中存放数组的长度。即在这个合约中 数组长度 和 investment.amount是存储在同一位置的。investment.amount = msg.value;覆盖了数组的长度,而balances.push(investment); 又会将 investment.amount增加,从而导致实际加入的 amount 比 msg.value 要大。最终导致 total 比 this.balance 大。
解决这一问题可以通过向合约额外转账来实现,但是合约并没有实现 fallback 是不能以常规路径接收转账的。可以通过调用 selfdestruct给合约转账,selfdestruct 的功能是销毁当前合约,并将合约中剩余的 ether 转给指定地址。这样我们就完成了增加 目标合约 ether的功能。
这也就意味FrogSec着合约的 balance 属性其实不是自己完全可控的,外部合约完全可以在合约本身未知的情况下给合约转账。那么在合约中使用 this.balance来进行判断其实是不安全的。经过我们对现有合约的审计情况来看,this.balance 的使用情况还是很多的,在某些赌博合约中会使用 this.balance 作为某些操作的控制条件,这一系列合约就存在一定的威胁。
这道题是一道相当经典的智能合约综合问题,融合了许多在现实合约中可能出现的问题,对我们理解 ETH 以及审计合约有着很大的帮助。
 

上一页  [1] [2] 

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载