Существует ли защита от повторного использования подписей в разных сетях?
Этот пункт проверки касается критической уязвимости нескольких сетей. Поскольку приватный ключ пользователя контролирует его адрес во всех сетях EVM, подпись, созданная для контракта на Ethereum, может быть повторно использована при идентичном развертывании этого контракта на Polygon, Arbitrum или любой другой сети EVM. Если подпись не содержит идентификатор блокчейна, она становится универсальным ключом, который злоумышленник может использовать в разных сетях.
Рассмотрим контракт VulnerableVault, предназначенный для того, чтобы его владелец мог изменить получателя с помощью подписанного сообщения.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract VulnerableVault {
address public owner;
address public recipient;
mapping(bytes32 => bool) public isConsumed;
constructor(address _owner, address _recipient) {
owner = _owner;
recipient = _recipient;
}
function changeRecipient(address _newRecipient, uint256 _expiry, bytes memory _signature) external {
require(block.timestamp <= _expiry, "Signature expired");
// VULNERABILITY: Missing chain ID in the signature data
bytes32 messageHash = keccak256(abi.encode(
msg.sender, // In the PoC, this is the test contract's address
_newRecipient,
_expiry
));
// For PoC, use a simple signature check
bytes32 signedHash = abi.decode(_signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
require(!isConsumed[messageHash], "Signature already used");
isConsumed[messageHash] = true;
recipient = _newRecipient;
}
}
MessageHash включает нового получателя и метку времени истечения срока действия, но в нем отсутствует block.chainid. Это означает, что подпись, сгенерированная для этого действия, является переносимой, т. е. злоумышленник может взять действительную подпись из одной сети и использовать ее в другой.
Исправление: встраивание данных, специфичных для цепочки, в подписи.
Прямым исправлением является включение block.chainid в данные, которые хэшируются и подписываются. Это навсегда привязывает подпись к одной сети. Для более надежного и стандартизированного решения используйте EIP-712, который встраивает эту защиту непосредственно в свою структуру через разделитель домена.
Вот простое исправление, примененное к новому контракту SecureVault.
// Corrected changeRecipient function (inside a new SecureVault contract)
contract SecureVault {
// ... same state variables and constructor as VulnerableVault ...
address public owner;
address public recipient;
mapping(bytes32 => bool) public isConsumed;
constructor(address _owner, address _recipient) {
owner = _owner;
recipient = _recipient;
}
function changeRecipient(address _newRecipient, uint256 _expiry, bytes memory _signature) external {
require(block.timestamp <= _expiry, "Signature expired");
// REMEDIATION: Include the chain ID to make the signature chain-specific.
bytes32 messageHash = keccak256(abi.encode(
msg.sender,
_newRecipient,
_expiry,
block.chainid // Added chain ID
));
// For PoC, use a simple signature check
bytes32 signedHash = abi.decode(_signature, (bytes32));
require(signedHash == messageHash, "Invalid signature");
require(!isConsumed[messageHash], "Signature already used");
isConsumed[messageHash] = true;
recipient = _newRecipient;
}
}
Атаки с повторным воспроизведением используют действительные намерения пользователей в недействительных контекстах. Понимая, как злоумышленники могут дублировать действия во времени или между сетями, мы можем встроить в наши контракты более точную и безопасную логику проверки.
Разработка безопасных контрактов требует верной точки зрения: