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