Умный контракт, хорошая практика для атак повторного входа
Я новичок, работающий с технологиями солидности и блокчейна, и я читал некоторые хорошие практики для улучшения своего кода.
И у меня есть вопрос о коде, который я не очень хорошо понимаю:
Источник: https://github.com/ConsenSys/smart-contract-best-practices/blob/master/docs/known_attacks.md
// INSECURE
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
require(msg.sender.call.value(amountToWithdraw)()); // At this point, the caller's code is executed, and can call withdrawBalance again
userBalances[msg.sender] = 0;
}
В приведенном выше коде говорят, что он небезопасен, потому что злонамеренный агент может вызвать шаг 2, сколько раз нам нужно. Мой вопрос по этому поводу заключается в том, как злонамеренный агент может вызвать неправильное использование и вызывать эту строку кода более 1 раза. Я явно что-то здесь упускаю.
1 ответ
Это известно как атака повторного входа.
Это небезопасно, поскольку баланс пользователя устанавливается на 0 только после того, как снятие было обработано. Кроме того, вывод обрабатывается с использованием кода операции evall CALL, который передает управление на адрес получения.
Если получающий адрес является контрактом, он может перехватить эту передачу с помощью функции отката. Внутри этой резервной функции он может проверить, превышает ли остаток в договоре отправки отправленную сумму. Если это произойдет, он позвонит withdrawBalance
снова, пока баланс договора снятия средств не будет исчерпан.
Простой контракт злоумышленника может выглядеть примерно так:
contract Attacker {
function startattack() {
victim.withdrawBalance();
}
function() payable {
if (victim.balance > msg.value) {
victim.withdrawBalance();
}
}
}
По телефону startattack
Вы инициируете вывод средств. Когда require(msg.sender.call.value(amountToWithdraw)());
выполняется строка, она запускает код в резервной функции. С этой точки зрения, msg.value
является userBalances[msg.sender]
, Атакующий может проверить, есть ли у контракта жертвы больше эфира, чем userBalances[msg.sender]
и снова выполните вывод средств (что приведет к зацикливанию этого процесса, пока баланс не опустится ниже userBalances[msg.sender]
).
Этого можно избежать, переключив порядок строк на:
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
userBalances[msg.sender] = 0;
require(msg.sender.call.value(amountToWithdraw)());
}
Теперь, даже если злоумышленник звонит withdrawBalance
Опять же, баланс пользователя уже был установлен на 0, и дальнейший вывод будет невозможен.