Ethernaut 题库闯关 #14 — Gatekeeper Two

Ethernaut 题库闯关连载第14篇

今天这篇是Ethernaut 题库闯关连载的第14篇,难度等级:有点难。

我自己经过这 14 篇的翻译,感觉自己对 Solidity 理解更深了, 完成 Ethernaut 挑战真的是非常好的提升 Solidity 编程的方式。今天这篇又是非常高质量的一篇,了解在构造函数中进行攻击的方法。只有我们知道更多的攻击方法后,才能写出更好的代码。

欢迎大家订阅专栏:Ethernaut 题库闯关,现在只需要 55 个学分,等系列更新完毕之后, 专栏价格将上涨到 100 个学分,现在订阅绝对超值哦。

挑战 14: Gatekeeper Two

和上一个挑战一样,为了解决这个挑战,我们需要通过三个不同修改器, 也就是合约代码里的 3 个 "门",每一个“门”都有不同的要求, 要求使 enter 成功返回 true。

挑战合约GatekeeperTwo 源码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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) == uint64(0) - 1);
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}
  • gateOne 和上一关是一样的。

  • gateTwo 中的 assembly关键字允许合约访问汇编代码。更多信息见这里extcodesize调用将获的在给定地址的合约代码大小 -- 你可以在黄皮书的第7节中了解更多关于它的信息。

  • gateThree中的^字符是一个位操作(XOR: 异或运算),要完成这个关卡,硬币翻转关也会给我们一些帮助。

在查看解题思路之前,可以先自己想一想,自己会怎么做?

研究合约

为了解决这个挑战,我们需要解开三个不同的门,让我们分别解释每个门,并逐个击破。

gateOne:msg.sendertx.origin

要破解这个门,我们必须了解msg.sendertx.origin是什么,它们之间有什么区别。

让我们看看Solidity文档对这些全局变量的描述。

  • msg.sender (address): 消息的发送者 (当前调用)
  • tx.origin (address): 交易的发送者(完整的调用链)

当交易由EOA (外部账号)发起时,它直接与智能合约交互,这两个变量将具有相同的值。但是,如果它与一个中间人合约A交互,然后通过直接(call)调用(不是delegatecall)另一个合约B,在 B 合约里这些值将是不同的,在这种情况下。

  • msg.sender将是A合约的地址
  • tx.origin将是EOA地址地址。

因为要使gateOne不被回退,我们需要让msg.sender != tx.origin,这意味着我们必须从智能合约中调用enter,而不是直接从EOA中调用。

但另外,我建议你阅读文末的进一步阅读中列出的关于tx.origin的一些安全问题和最佳实践,以及什么时候不应该使用它,尽管这不是挑战的一部分。

gateTwo:extcodesize背后的奥秘

这里是学习更多关于合约如何部署以及合约在部署过程中的生命周期的绝佳机会。 让我们看看这个函数修改器的代码:


modifier gateTwo(...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ethernaut CTF
Ethernaut CTF
信奉 CODE IS LAW.