2016 年 6 月 17 日 The DAO 攻擊中使用的第二個漏洞是什麼?
來自koeppelmann 的 The DAO 搶劫常見問題解答:
攻擊究竟是如何進行的?攻擊者設法結合了 2 個漏洞。第一個漏洞是遞歸呼叫 split DAO 函式. 這意味著第一個正常呼叫將觸發該函式的第二個(不規則)呼叫,而第二個呼叫將觸發另一個呼叫,依此類推。以下呼叫是在攻擊者的餘額設置回 0 之前的狀態下完成的。這允許攻擊者在每筆交易中拆分 20 次(必須查找確切的數字)。他不能做得更多——否則交易會變得太大,最終會達到區塊限制。這種攻擊已經很痛苦了。然而 - 真正令人痛苦的是,被攻擊者設法一次又一次地從相同的兩個地址用相同的令牌複製這種攻擊(每個地址大約 250 次)。因此,攻擊者發現了第二個漏洞,該漏洞允許在不破壞主 DAO 中的令牌的情況下進行拆分. 他們設法在將令牌發送到地址 0x0 之前將其轉移走,然後才將它們發送回)兩種攻擊的結合使效果成倍增加。對它的一次攻擊將是非常資本密集型的(您需要預先提出 1/20 的被盜金額)——第二次攻擊將花費很長時間。
第一個漏洞在什麼是遞歸呼叫漏洞?.
問:第二個允許攻擊者“使用相同的令牌從相同的兩個地址一遍又一遍地複制此攻擊(每個從 2 個地址大約 250 次)”的第二個漏洞是什麼?
來自The DAO code,
splitDAO(...)
功能程式碼如下:function splitDAO( uint _proposalID, address _newCurator ) noEther onlyTokenholders returns (bool _success) { Proposal p = proposals[_proposalID]; // Sanity check if (now < p.votingDeadline // has the voting deadline arrived? //The request for a split expires XX days after the voting deadline || now > p.votingDeadline + splitExecutionPeriod // Does the new Curator address match? || p.recipient != _newCurator // Is it a new curator proposal? || !p.newCurator // Have you voted for this split? || !p.votedYes[msg.sender] // Did you already vote on another proposal? || (blocked[msg.sender] != _proposalID && blocked[msg.sender] != 0) ) { throw; } // If the new DAO doesn't exist yet, create the new DAO and store the // current split data if (address(p.splitData[0].newDAO) == 0) { p.splitData[0].newDAO = createNewDAO(_newCurator); // Call depth limit reached, etc. if (address(p.splitData[0].newDAO) == 0) throw; // should never happen if (this.balance < sumOfProposalDeposits) throw; p.splitData[0].splitBalance = actualBalance(); p.splitData[0].rewardToken = rewardToken[address(this)]; p.splitData[0].totalSupply = totalSupply; p.proposalPassed = true; } // Move ether and assign new Tokens uint fundsToBeMoved = (balances[msg.sender] * p.splitData[0].splitBalance) / p.splitData[0].totalSupply; if (p.splitData[0].newDAO.createTokenProxy.value(fundsToBeMoved)(msg.sender) == false) throw; // Assign reward rights to new DAO uint rewardTokenToBeMoved = (balances[msg.sender] * p.splitData[0].rewardToken) / p.splitData[0].totalSupply; uint paidOutToBeMoved = DAOpaidOut[address(this)] * rewardTokenToBeMoved / rewardToken[address(this)]; rewardToken[address(p.splitData[0].newDAO)] += rewardTokenToBeMoved; if (rewardToken[address(this)] < rewardTokenToBeMoved) throw; rewardToken[address(this)] -= rewardTokenToBeMoved; DAOpaidOut[address(p.splitData[0].newDAO)] += paidOutToBeMoved; if (DAOpaidOut[address(this)] < paidOutToBeMoved) throw; DAOpaidOut[address(this)] -= paidOutToBeMoved; // Burn DAO Tokens Transfer(msg.sender, 0, balances[msg.sender]); withdrawRewardFor(msg.sender); // be nice, and get his rewards totalSupply -= balances[msg.sender]; balances[msg.sender] = 0; paidOut[msg.sender] = 0; return true; }
balances[msg.sender] = 0;
底部的語句splitDAO(...)
應該已經阻止了同一個地址splitDAO(...)
多次呼叫函式成功轉賬。並且從問答中是否有任何方法可以確定 DAO 攻擊者部署攻擊需要多長時間?,每筆交易(第一筆和第二筆至少來自我的手動計數)呼叫了
splitDAO(...)
29 次。但是 29 xsplitDAO(...)
呼叫被重複呼叫,創建了27996 次內部交易,13996 次是非零內部轉賬。計算:13996 筆交易 x 258.05656476 ETH = 3,611,759.68038 乙太幣,大約是 3,641,694.241898506 乙太幣(59,578,117.80 美元)被轉移到賬戶0x304a554a310c7e546dfe434669c0d628020832。
這不是一個漏洞,但攻擊巧妙地在兩個賬戶之間轉移其 DAO 令牌,通過使用
function transfer(address _to, uint256 _amount)
.所以攻擊合約的回退函式如下所示:
function() { transfer DAO tokens to other attacking contract invoke splitDAO }
有 2 個攻擊合約將 DAO 代幣相互轉移。當一個攻擊合約的交易完成時,
balances[msg.sender] = 0
將正確設置,但代幣已轉移到另一個合約。現在另一個合約執行攻擊,直到它的交易完成。進攻契約交替進行。@Roland 的回答提到了 TheDAO 是如何阻止這種情況的。