签名重放

漏洞原理



关于签名重放,从字面意思不难理解,就是相同的签名被重复使用,攻击者利用已经被使用过的签名获利。



漏洞原因



有以下几种情况可能造成签名重放:

1.合约未追踪已被使用的消息签名,导致消息被重用。

2.签名被其他合约使用。

3.签名可能被跨链使用。

4.由于签名的延展性,由一个使用过的签名可以推出另一个有效签名。



漏洞演示与解决


// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.22;
contract example{ mapping (address => uint256) public balanceOf; constructor(){ balanceOf[msg.sender] = 1000; }
//0xd957e414d8e68839bcb16e9fafe1806d8461060c8c1ff01faff1f93ddc7c660a39d34342848067a3c94974e8d5523c28cb2d8c3a9706008808a4b5d120b541421c function transfer (address from, address to ,uint256 amount,bytes memory signature) public returns(bool){ bytes32 msghash = getMsgHash(from,to,amount); require(verify(from, msghash, signature),"InVaild Signer!"); balanceOf[from] -= amount; balanceOf[to] += amount; return true; }
function getMsgHash(address from,address to,uint256 amount) public pure returns(bytes32 hash){ hash = keccak256(abi.encodePacked(from,to, amount)); }
function verify(address owner,bytes32 msghash,bytes memory signature) public pure returns(bool success){ if (signature.length == 65) { bytes32 r; bytes32 s; uint8 v; assembly { r := mload(add(signature, 0x20)) s := mload(add(signature, 0x40)) v := byte(0, mload(add(signature, 0x60))) } address signer = ecrecover(msghash, v, r, s); if (signer == owner) { success = true; } } }}

此合约提供一个函数来实现转账,需要from地址向to地址提供链下签名,这样to地址就可以调用transfer函数来转账。

现在合约的部署者address1(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4)向address2(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2)提供了这样一段签名信息,授权address2转移自己100代币

from:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2amount:100signature:0xd957e414d8e68839bcb16e9fafe1806d8461060c8c1ff01faff1f93ddc7c660a39d34342848067a3c94974e8d5523c28cb2d8c3a9706008808a4b5d120b541421c

我们尝试一下,可以发现addres2可以通过调用transfer函数获得100代币。

但是由于未做任何限制,address2和链上任意其他地址可以重复使用这段签名,直到耗尽address1的余额。

那么如何防范这个问题呢?

可以在消息中增添一个nonce属性,然后追踪已经使用的消息,签名延展性也可以通过加入nonce解决。

+ mapping (bytes32 => bool) public usedHash;
+ function transfer (address from, address to ,uint256 amount,uint256 nonce,bytes memory signature) public returns(bool){+ bytes32 msghash = getMsgHash(from,to,amount,nonce);+ require(!usedHash[msghash],"Used Hash!"); require(verify(from, msghash, signature),"InVaild Signer!");+ usedHash[msghash] = true; balanceOf[from] -= amount; balanceOf[to] += amount; return true; }
+ function getMsgHash(address from,address to,uint256 amount,uint256 nonce) public pure returns(bytes32 hash){+ hash = keccak256(abi.encodePacked(from, to, amount, nonce)); }

这样在address1提供的相关签名就只能被使用一次

例:

from:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4to:0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2amount:100nonce:1signature:0x32c07b0ddca1d561bec1fc351c9db1c1f82ece099e9ea40db668915c829a604b147fbcde21b77de31838d7f0c41ff8bf06703808d06caee54d4829d043f673f81c

下一个问题,如果存在其他合约和此合约有相同的消息格式,例如,另外一个合约2的消息打包也为`hash = keccak256(abi.encodePacked(from, to, amount, nonce));`并且恰好address1在合约2也有足够的代币余额,那么address2就可以在合约2重复使用这个签名,跨链重放也是此原理。可以通过部署两个相同的合约,签名在每个合约都可以使用来简单验证。

如何解决

在打包消息时添加address(this)字段,此外,还可以加上chainId

    function getMsgHash(address from,address to,uint256 amount,uint256 nonce) public view returns(bytes32 hash){        hash = keccak256(abi.encodePacked(block.chainid ,from, to, amount, nonce,address(this)));    }


总结



签名重放漏洞是利用已使用过的签名重复执行操作的安全漏洞。解决方法包括添加唯一nonce使用链标识和合约地址等措施,以防止签名被重复使用和跨链攻击。


作者:张凯

编辑:舒婷

原文始发于微信公众号(ChainSecLabs):签名重放

版权声明:admin 发表于 2024年3月28日 上午9:38。
转载请注明:签名重放 | CTF导航

相关文章