作者:Al1ex @ 知道创宇404区块链安全研究团队
时间:2020年8月20日
前言
智能合约的概念可以追溯到1994年,由Nick Szabo提出,但直到2008年才出现采用智能合约所需的区块链技术,而最终于2013年,作为以太坊智能合约系统的一部分,智能合约首次出现。
智能合约包含了有关交易的所有信息,只有在满足要求后才会执行结果操作,智能合约和传统纸质合约的区别在于智能合约是由计算机生成的,因此,代码本身解释了参与方的相关义务,与此同时,用户可以根据规则开发自己想要的智能合约。
而随着智能合约普及,合约的安全问题也引起了众多合约开发者和安全研究人员关注,比较典型的就是随之建立的DASP Top10( https://www.dasp.co/)。近期,笔者在对一些智能合约进行代码审计时发现有很多合约存在可疑的后门漏洞,具备特殊权限的地址用户(合约的owner)或合约账号具备控制用户资产的权限,可对任意用户的资产进行销毁操作,本文将对此进行简要分析。近期,笔者在对一些智能合约进行代码审计时发现有很多合约存在可疑的后门漏洞,具备特殊权限的地址用户(合约的owner)或合约账号具备控制用户资产的权限,可对任意用户的资产进行销毁操作,本文将对此进行简要分析。
函数漏洞
burn()
合约地址:https://etherscan.io/address/0x705051bbfd9f287869a412cba8bc7d112de48e69#code
利用条件:合约的owner权限
漏洞代码:
漏洞分析:如上图所示在智能合约中提供了burn函数,该函数主要用于销毁其它地址的token,当要销毁的token数量小于目标账户所拥有的token值时就可以成功销毁目标账户的token,且这里的地址可以指定为任意用户的地址,所以只要我们能够调用该函数即可通过赋予_ from为任意地址账户,_ unitAmout为任意数量(不超过from账户拥有的数量)就可以销毁_from账户的代币,下面我们再来看一下此处对于函数调用者身份限定的修饰器—onlyOwner
由以上代码可知,当函数的调用者为合约的owner地址账户时可以销毁任意地址用户的代币,甚至将其归0
burnFrom()
合约地址:https://etherscan.io/address/0x365542df3c8c9d096c5f0de24a0d8cf33c19c8fd#code
利用条件:合约的owner,同时mintingFinished为"False"
漏洞代码:
漏洞分析:如上图所示合约中的burnFrom函数用于销毁代币,但是该函数只能被合约的owner调用,而且由于地址参数可控故合约的owner可以操控任意地址用户的代币,销毁任意地址用户任意数量的代币(数量小于等于用户代币总量),由于该函数被canMint修饰,所以查看一下该修饰器
之后通过"Read Contract"来查看当前"mintingFinished"的值:
可以看到"mintingFinished"为"False",即满足"canMint"修饰器条件,所以此时的burnFrom函数可被合约的owner调用来操控任意用户的代币。
burnTokens
合约地址:https://etherscan.io/address/0x662abcad0b7f345ab7ffb1b1fbb9df7894f18e66#code
利用条件:合约的owner权限
漏洞代码:
漏洞分析:如上图所示,burnTokens用于销毁用户的代币,由于销毁的地址参数、销毁的代币数量都可控,所以合约的调用者可以销毁任意用户的代币,但是该函数只能被合约的ICO地址用户调用,下面跟踪一下该账户看看其实现上是否可以
从上面可以看到合约在初始化是对icoContract进行了赋值,下面通过etherscan.io中的readcontract溯源一下:
之后再次进入icoContract中跟踪是否可以成功调用:
从代码中可以看到burnTokens(关于修饰器的跟踪分析与之前类似,这里不再赘述):
这里的cartaxiToken即为之前的合约地址:
同时发现存在调用的历史记录:https://etherscan.io/tx/0xf5d125c945e697966703894a400a311dc189d480e625aec1e317abb2434131f4
destory()
合约地址:https://etherscan.io/address/0x27695e09149adc738a978e9a678f99e4c39e9eb9#code
利用条件:合约的owner权限
漏洞代码:
如上图所示,在智能合约当中提供了destory函数,用于销毁目标账户的代币,在该函数当中增加了对msg.sender、accountBalance的判断,从整个逻辑上可以看到主要有两种销毁途径:
- 途径一:合约的owner赋予allowManuallyBurnTokens为"true"的条件下,地址账户自我销毁自己的代币
- 途径二:无需allowManuallyBurnTokens为"true"的条件,合约的owner销毁任意地址用户的代币
自然,途径一自我销毁代币合情合理,但是途径二却导致合约的owner可以操控任意地址用户的代币,例如:销毁地址用户的所有代币,导致任意地址用户的代币为他人所操控,这自然不是地址用户所期望的。
destroyTokens()
合约地址:https://etherscan.io/address/0xf7920b0768ecb20a123fac32311d07d193381d6f#code
利用条件:Controller地址账户
漏洞代码:
如上图所示,destroyTokens函数用于销毁代币,其中地址参数可控,在函数中只校验了销毁地址账户的代币是否大于要销毁的数量以及当前总发行量是否大于要销毁的数量,之后进行更新代币总量和地址账户的代币数量,不过该函数有onlyController修饰器进行修饰,下面看以下该修饰器的具体内容:
之后通过ReadContract可以看到该controller的地址:
之后再Etherscan中可以查看到该地址对应的为一个地址账户,故而该地址账户可以操控原合约中的任意地址用户的代币:
destroyIBTCToken
合约地址:https://etherscan.io/address/0xb7c4a82936194fee52a4e3d4cec3415f74507532#code
利用条件:合约的owner
漏洞代码:
如上图所示合约中的destroyIBTCToken是用于销毁IBTCToken的,但是由于该函数只能被合约的owner调用,而且要销毁的地址参数to可控,所以合约的owner可以传入任意用户的地址作为参数to,之后销毁任意地址账户的代币,onlyOwner修饰器如下所示:
melt()
合约地址:https://etherscan.io/address/0xabc1280a0187a2020cc675437aed400185f86db6#code
利用条件:合约的owner
漏洞代码:
漏洞分析:如上图所示合约中的melt函数用于销毁代币的token,且该函数只能被合约的CFO调用,同时由于地址参数dst可控,故合约的CFO可以销毁任意地址用户的代币,onlyCFO修饰器代码如下所示
onlyCFO修饰器中的_cfo由构造函数初始化:
Sweep()
合约地址:https://etherscan.io/address/0x4bd70556ae3f8a6ec6c4080a0c327b24325438f3#code
利用条件:合约的owner,同时mintingFinished为"False"
漏洞代码:
如上图所示,合约中的sweep函数用于转发代币,该函数只能被合约的owner调用,在L167行优先通过allowance进行授权操作代币的操作,之后调用transferFrom函数,并在transferFrom函数中做了相关的减法操作,由此抵消授权操作代币:
之后紧接着又调用了_transfer函数:
在transfer函数中判断转账地址是否为空、进行转账防溢出检查、进行转账操作,通过以上逻辑可以发现由于sweep中的地址参数 _ from、_to可控,而且该函数只能被合约的owner调用,所以合约的owner可以通过该函数操控任意用户的balance,并且将其转向任意用户的地址或者自己的地址。
zero_fee_transaction
合约地址: https://etherscan.io/address/0xD65960FAcb8E4a2dFcb2C2212cb2e44a02e2a57E#code
利用条件:合约的owner
漏洞代码:
漏洞分析:在智能合约中常见的转账方式大致有2种,一种是直接转账,例如常见的Transfer函数,该函数有两个参数,一个指定代币接受的地址,另一个为转账的额度,例如:
另外一种为授权其他用户代为转账,这里的其他用户类似于一个中介媒介的作用,其他用户可以对授权用户授予的资金额度进行操作,常见的也是transfer函数,不过参数个数不同而已,其中有三个参数,一个为代为转账的地址,一个为接受代币的地址,一个为接受代币的数量,例如:
了解了常见的两种转账方式,下面我们回过头来看一下漏洞代码:
可以看到在函数zero_fee_transaction中进行了以下判断:
1、判断的当前代为转账的额度是否大于当前转账的数量
2、判断当前转账的数量是否大于0
3、防溢出检查
可以发现这里未对当前函数调用者是否具备授权转账进行检查(暂时忽略onlycentralAccount修饰器)以及授权额度进行检查,只对转账额度以及是否溢出进行了检查,显然这里是存在问题的,而且该函数没有修饰词进行修饰,故默认为public,这样一来所有人都可以调用该函数,在这里我们可以看到在不管修饰器onlycentralAccount的情况下我们可以传递任意地址账户为from、任意地址账户为to、以及任意数量(需要小于from地址账户的代币数量),之后即可无限制的从from地址账户转代币到to账户,直到from地址的代币数量归0。
下面我们看一下onlycentralAccount修饰器对于函数调用者的身份限定:
之后搜索central_account发现central_account由函数set_centralAccount进行赋值操作,此处的修饰器为onlyOwner:
之后查看onlyOwner修饰器可以看到此处需要msg.sender为owner,即合约的owner,在构造函数中进行初始化:
文末小结
智能合约主要依托于公链(例如:以太坊)来发行代币并提供代币的转账、销毁、增发等其它逻辑功能,但用户的代币理应由用户自我进行控制(通过交易增加或减少),并由用户自我决定是否销毁持币数量,而不是由合约的owner或其他特殊的地址账户进行操控。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1300/