文章前言
以太坊合约中的加密签名实现通常假定签名是唯一的,但是可以在不具备私钥的情况下实现对签名的更改,并且签名仍然有效,在EVM规范定义了几个\\”预编译\\”合约,其中一个合约ecrecover主要用于执行椭圆曲线公钥恢复,恶意用户可以稍微修改三个值r,v,s来创建其他有效签名,此时如果签名是已签名消息哈希的一部分,则在合约级别执行签名验证的系统可能会受到攻击,恶意用户可能创建有效的签名,以重放以前签名的消息来获利或实施恶意攻击。
漏洞示例
transaction_malleablity.sol
pragma solidity ^0.4.24;contract transaction_malleablity{mapping(address => uint256) balances;mapping(bytes32 => bool) signatureUsed;constructor(address[] owners, uint[] init){require(owners.length == init.length);for(uint i=0; i < owners.length; i ++){balances[owners[i]] = init[i];}}function transfer(bytes _signature,address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicreturns (bool){bytes32 txid = keccak256(abi.encodePacked(getTransferHash(_to, _value, _gasPrice, _nonce), _signature));require(!signatureUsed[txid]);address from = recoverTransferPreSigned(_signature, _to, _value, _gasPrice, _nonce);require(balances[from] > _value);balances[from] -= _value;balances[_to] += _value;signatureUsed[txid] = true;}function recoverTransferPreSigned(bytes _sig,address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicviewreturns (address recovered){return ecrecoverFromSig(getSignHash(getTransferHash(_to, _value, _gasPrice, _nonce)), _sig);}function getTransferHash(address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicviewreturns (bytes32 txHash) {return keccak256(address(this), bytes4(0x1296830d), _to, _value, _gasPrice, _nonce);}function getSignHash(bytes32 _hash)publicpurereturns (bytes32 signHash){return keccak256(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", _hash);}function ecrecoverFromSig(bytes32 hash, bytes sig)publicpurereturns (address recoveredAddress){bytes32 r;bytes32 s;uint8 v;if (sig.length != 65) return address(0);assembly {r := mload(add(sig, 32))s := mload(add(sig, 64))v := byte(0, mload(add(sig, 96)))}if (v < 27) {v += 27;}if (v != 27 && v != 28) return address(0);return ecrecover(hash, v, r, s);}}
防御措施
在检查消息是否之前已由合约处理时,在消息哈希中不应包含签名,修改后的代码如下:
pragma solidity ^0.4.24;contract transaction_malleablity{mapping(address => uint256) balances;mapping(bytes32 => bool) signatureUsed;constructor(address[] owners, uint[] init){require(owners.length == init.length);for(uint i=0; i < owners.length; i ++){balances[owners[i]] = init[i];}}function transfer(bytes _signature,address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicreturns (bool){bytes32 txid = getTransferHash(_to, _value, _gasPrice, _nonce);require(!signatureUsed[txid]);address from = recoverTransferPreSigned(_signature, _to, _value, _gasPrice, _nonce);require(balances[from] > _value);balances[from] -= _value;balances[_to] += _value;signatureUsed[txid] = true;}function recoverTransferPreSigned(bytes _sig,address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicviewreturns (address recovered){return ecrecoverFromSig(getSignHash(getTransferHash(_to, _value, _gasPrice, _nonce)), _sig);}function getTransferHash(address _to,uint256 _value,uint256 _gasPrice,uint256 _nonce)publicviewreturns (bytes32 txHash) {return keccak256(address(this), bytes4(0x1296830d), _to, _value, _gasPrice, _nonce);}function getSignHash(bytes32 _hash)publicpurereturns (bytes32 signHash){return keccak256(\\\"\\\\x19Ethereum Signed Message:\\\\n32\\\", _hash);}function ecrecoverFromSig(bytes32 hash, bytes sig)publicpurereturns (address recoveredAddress){bytes32 r;bytes32 s;uint8 v;if (sig.length != 65) return address(0);assembly {r := mload(add(sig, 32))s := mload(add(sig, 64))v := byte(0, mload(add(sig, 96)))}if (v < 27) {v += 27;}if (v != 27 && v != 28) return address(0);return ecrecover(hash, v, r, s);}}
修改前后的代码差异对比如下:
原创文章,作者:七芒星实验室,如若转载,请注明出处:https://www.sudun.com/ask/34258.html