题目转载:Paradigm CTF 2021 最简单一题

环境:Paradigm CTF 2021 使用 Docker 创建隔离的容器,作为各个队伍的的比赛环境,不至互相影响

为了公平,容器内使用 ganache-cli 克隆主链得到私链,比赛在私链上进行,因此队伍间无法看到他人的解题过程

所有题目都是对应智能合约 Setup.sol 的实例,其构造函数执行出题逻辑,公有函数 function solve() public; 测试题目是否成功解答

一道题目可以有多个 setup contract 实例,每次请求出题都会得到重新克隆的私链,多条私链将会共存,因此队伍内成员间可以同时解题,不会影响

每次请求出题,除了返回 setup contract 之外,还会返回 uuid,请求通关时需要输入,表示通关哪个实例

ganache-cli 监听在随机端口上,不与外界互通,因此无法将其作为 Web3 Provider 节点提交解题交易

容器启动时会创建代理服务,默认监听在 8545 端口上,与外界互通,参赛者必须将解题交易提交至代理服务,由其转发给私链

代理服务遵循 Ethereum JSON-RPC Specification,只是请求时 header 中必须包括 X-Auth-Key 字段,作为鉴权;它是个随机数,每个容器唯一,其他队伍无法猜出

JavaScript
pragma solidity 0.5.12;

contract ERC20Like {
    function transfer(address dst, uint qty) public returns (bool);
    function transferFrom(address src, address dst, uint qty) public returns (bool);
    function approve(address dst, uint qty) public returns (bool);

    function balanceOf(address who) public view returns (uint);
}

contract TokenModule {
    function deposit(ERC20Like token, address from, uint amount) public {
        token.transferFrom(from, address(this), amount);
    }

    function withdraw(ERC20Like token, address to, uint amount) public {
        token.transfer(to, amount);
    }
}

contract Wallet {
    address public owner = msg.sender;

    mapping(address => bool) _allowed;
    mapping(address => bool) _operators;

    modifier onlyOwner {
        require(msg.sender == owner);
        _;
    }

    modifier onlyOwnerOrOperators {
        require(msg.sender == owner || _operators[msg.sender]);
        _;
    }

    function allowModule(address module) public onlyOwner {
        _allowed[module] = true;
    }

    function disallowModule(address module) public onlyOwner {
        _allowed[module] = false;
    }

    function addOperator(address operator) public onlyOwner {
        _operators[owner] = true;
    }

    function removeOperator(address operator) public onlyOwner {
        _operators[owner] = false;
    }

    function execModule(address module, bytes memory data) public onlyOwnerOrOperators {
        require(_allowed[module], "execModule/not-allowed");
        (bool ok, bytes memory res) = module.delegatecall(data);
        require(ok, string(res));
    }
}

JavaScript
pragma solidity 0.5.12;

import "./Wallet.sol";

contract WETH9 is ERC20Like {
    function deposit() public payable;
}

contract Setup {
    WETH9 public constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
    uint public constant WANT = 50 ether;

    Wallet public wallet;

    constructor() public payable {
        require(msg.value == WANT);

        address tokenModule = address(new TokenModule());

        wallet = new Wallet();
        wallet.allowModule(tokenModule);

        WETH.deposit.value(msg.value)();
        WETH.approve(address(wallet), uint(-1));

        wallet.execModule(tokenModule, abi.encodeWithSelector(TokenModule(0x00).deposit.selector, WETH, address(this), msg.value));
    }

    function isSolved() public view returns (bool) {
        return WETH.balanceOf(address(this)) == WANT;
    }
}

审题:使目标合约拥有 50 WETH,提供的帐户始终以5000 ETH开始,如果设置合同有50(W)ETH,则挑战得到解决。

JavaScript
pragma solidity 0.5.12;

import "public/Setup.sol";

contract Exploit {
    WETH9 public constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    constructor(Setup setup) public payable {
        WETH.deposit.value(50 ether)();
        WETH.transfer(address(setup), 50 ether);
    }
}

启发:不一定所有题目都要用到高级的漏洞挖掘技巧,本题考察的还是基本的逻辑能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值