solidity漏洞类型学习笔记(一)
以下代码内容皆参考于RICKGRAY师傅之前的文章《以太坊智能合约安全入门了解一下》,在此记录我在复现中发现的一些问题和学习记录。
Reentrancy - 重入
首先我们先参考代码实现一个类似公共钱包的代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
balances定义了一个下标为[address]的公共钱包,deposit函数向钱包中调用者的位置存入相应的value值,withdraw函数检查提币账户的余额与该合约资产是否大于参数amount,之后向to地址发送相应Ether。
部署成功后我们调用deposit函数向钱包中存入25ether,可在balanceof处输入"0xca3...a733c"的地址查看钱包中该地址的余额是否为25*10^18wei。随后我们将这个钱包中的余额转给另一个外部用户:
此时的调用者依然是"0xca3...",拷贝第二个外部账户的地址"0x147..."。输入参数"0x147...",25调用withdraw()函数,成功转账。
这里存在着一个问题:当外部账户或其他合约向一个合约地址发送ether时,会执行该合约的fallback函数(当调用合约时没有匹配到函数,也会调用没有名字的fallback函数——The DAO)。且call.value()会将所有可用Gas给予外部调用(fallback函数),若在fallback函数中再调用withdraw函数,则会导致递归问题。攻击者可以部署一个恶意递归的合约将公共钱包这个合约账户里的Ether全部提出来。【1、call.value()提供了足够的Gas 2、资产的修改在转币之后】
Solidity 中 <address>.transfer(),<address>.send() 和 <address>.gas().call.vale()() 都可以用于向某一地址发送 ether,他们的区别在于:
<address>.transfer()
* 当发送失败时会 throw; 回滚状态
* 只会传递 2300 Gas 供调用,防止重入(reentrancy)
<address>.send()
* 当发送失败时会返回 false 布尔值
* 只会传递 2300 Gas 供调用,防止重入(reentrancy)
<address>.gas().call.value()()
* 当发送失败时会返回 false 布尔值
* 传递所有可用 Gas 进行调用(可通过 gas(gas_value) 进行限制),不能有效防止重入(reentrancy)
以下是rickgray师傅实现的攻击代码,有小修改,攻击流程在他的博客中也有GIF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
可能是Remix的原因我在一开始复现时就是不成功,后来查原因之后在输入参数处加上引号即可。
The DAO:
第一处红线向攻击者账户转钱,第二处withdrawRewardFor函数:
在payout中调用攻击者_recipient,但没有指定具体函数则调用fallback函数,在fallback函数中会再次调用splitDAO函数,实现恶意递归。在方框中的修改余额代码执行之前,就完成了偷钱操作。