以太坊漏洞分析————5、权限验证错误

引子:横看成岭侧成峰,远近高低各不同。不识庐山真面目,只缘身在此山中。

                                                                                                                                         —— 《题西林壁》苏轼

智能合约作为以太坊上各种加密数字货币的基础,承载着巨大的经济利益。放眼以太坊的繁忙景象,日交易频率在50-75万次之间,而五月和二月更有两日交易次数高达100万次。合约的调用在整个以太坊生态系统犹如战场上穿梭的子弹,不计其数。

可见其地位举足轻重,功能五花八门,但万变不离其宗的是,每个合约都有一个共性,会继承一个地址对象,也就是说合约的基础是地址。回顾前四期漏洞分析,我们了解攻击者使出了浑身解数从合约的各个部分试图不劳而获,而从合约的地址漏洞入手,釜底抽薪自然也会成为攻击手段之一。

下面我们就来聊聊,与地址有关的tx.origin变量和ecrecover()函数的相关漏洞。

一言以蔽之

Solidity作为以太坊的官方语言,将地址定为20个字节,160位。所有合约都有一个地址对象,也可以对其他地址的代码进行调用。地址类型的成员有我们上一期讲到的call()和delegatecall()。不同的合约地址代表了不同的合约,也代表了他们所拥有的权限。所以合约地址就相当于合约的“身份证”。

本期相关两个名词专业的解释是, tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。ecrecover()是内嵌的函数,可以用来恢复签名公钥,传值正确的情况下,可以利用这个函数来验证地址。

通俗来说,tx.origin是整个交易过程最初的那个合约拥有者。当这个合约A直接调用合约B的时候,拥有者也是中间人没错,都是合约A。

但是当中间多出了一个合约C之后,合约A调用合约C,合约C调用合约B,这时中间人是C,但是tx.origin是合约A。

利用tx.origin的漏洞可以比喻为:

小明未满十八岁,但对手中的游戏爱不释手,但是游戏方为了限制未成年人的游戏时间,只有身份证号验证已满十八周岁的玩家才能无限制畅玩。于是小明使用长辈的身份证号进行防沉迷验证。这样,本来是游戏方对小明身份的验证,变成了对其长辈身份的验证,验证结果予以通过,给予小明他不应有的权限和利益。

至于ecrecover函数的问题,上面说到传值正确的情况下,使用是没有问题的,但普遍存在的问题是,传值如果异常,没有进行相应的判定和排除。

我们来具体分析案例进行代码层面的剖析。

深度分析

1.   tx.origin使用错误

漏洞分析 

tx.origin是Solidity的一个全局变量,它遍历整个调用栈并返回最初发送调用(或事务)的帐户的地址。在智能合约中使用此变量进行身份验证会使合约容易受到类似网络钓鱼的攻击。有关进一步阅读,请参阅StackExchangeQuestion, PeterVenesses博客和Solidity-tx.origin攻击。

对如下案例合约进行分析:

该合约有三个函数:constructor构造函数,指定合约owner;fallback函数,通过添加payable关键字以便接收用户转账;withdrawAll函数,对tx.origin进行判断,如果tx.origin是owner,则将合约地址所拥有的ether发送到_recipient中。

现在,一个攻击者创建了下列合约:

攻击者诱使原合约(Phishable.sol)的owner发送ether到攻击合约(POC.sol)地址,然后调用攻击合约的fallback函数,执行attack()函数,此时phOwner == msg.sender,将会调用原合约的withdrawAll()函数,程序执行进入原合约,此时msg.sender是攻击合约的地址,tx.origin是最初发起交易的地址,即原合约的owner,require(tx.origin == owner);条件满足,_recipient.transfer(this.balance);可以执行,即将原合约地址里的ether转给攻击者。

漏洞修复

tx.origin不应该用于智能合约的授权。这并不是说永远不应该使用tx.origin变量。它在智能合约中确实有一些合法的用例。例如,如果想要拒绝外部合约调用当前合约,他们可以通过require(tx.origin ==msg.sender)实现。 这可以防止使用中间合约来调用当前合约[1]。

·   参考链接:https://blog.sigmaprime.io/solidity-security.html#tx-origin

2. ecrecover 未作0地址判断

漏洞分析

keccak256() 和  ecrecover()都是内嵌的函数, keccak256() 可以用于计算公钥的签名, ecrecover()可以用来恢复签名公钥。传值正确的情况下,可以利用这两个函数来验证地址。

我们来看一个案例合约:

这个合约看似正常,但思索再三,如果ecrecover传入错误参数(例如_v = 29,),函数返回0地址。如果合约函数传入的校验地址也为零地址,那么将通过断言,导致合约逻辑错误。

函数transferProxy中,如果传入的参数_from为0,那么ecrecover函数因为输入参数错误而返回0值之后,if判断将通过,从而导致合约漏洞[2]。

函数 decode()传入经过签名后的数据,用于验证返回地址是否是之前用于签名的私钥对应的公钥地址[3]。以太坊提供了web3.eth.sign方法来对数据生成数字签名。上面的签名数据可以通过下面的js代码获得:

js代码运行结果如下:

漏洞修复

对0x0地址做过滤,例如:

参考资料

·    transferProxy-keccak256

·    approveProxy-keccak256

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值