Умный контракт, хорошая практика для атак повторного входа

Я новичок, работающий с технологиями солидности и блокчейна, и я читал некоторые хорошие практики для улучшения своего кода.

И у меня есть вопрос о коде, который я не очень хорошо понимаю:

Источник: 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, и дальнейший вывод будет невозможен.

Другие вопросы по тегам