Смарт-контракт ловушка в сети Ethereum

    Недавно, просматривая опубликованные смарт контракты в сети Эфириум, наткнулся на один интересный контракт с уязвимостью внутри. С первого взгляда, разработчик ошибся в коде и вы можете получить деньги контракта, но если внимательно проанализировать логику контракта, все выглядит совершенно иначе.

    Вот адрес контракта для тех, кто хочет посмотреть как он устроен в блокчейне. А вот его исходный код:

    pragma solidity ^0.4.19;
    
    contract NEW_YEARS_GIFT {
        string message;
        bool passHasBeenSet = false;
        address sender;
        bytes32 public hashPass;
    
        function () public payable {}
    
        function GetHash(bytes pass) public constant returns(bytes32) {
            return sha3(pass);
        }
    
        function SetPass(bytes32 hash)  public payable {
            if ((!passHasBeenSet && (msg.value > 1 ether)) || hashPass == 0x0) {
                hashPass = hash;
                sender = msg.sender;
            }
        }
    
        function SetMessage(string _message) public {
            if (msg.sender == sender) {
                message = _message;
            }
        }
    
        function GetGift(bytes pass) external payable returns(string) {
            if (hashPass == sha3(pass)) {
                msg.sender.transfer(this.balance);
                return message;
            }
        }
    
        function Revoce() public payable {
            if (msg.sender == sender) {
                sender.transfer(this.balance);
                message = "";
            }
        }
    
        function PassHasBeenSet(bytes32 hash) public {
            if (msg.sender == sender && hash == hashPass) {
                passHasBeenSet = true;
            }
        }
    }

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

    1. Вы кладете деньги на контракт, с помощью метода SetPass, при этом задавая хэш SHA-3 вашего пароля, который доступен получателю (как романтично).
    2. Вы отправляете сообщение получателю с помощью метода SetMessage
    3. Также можете отказаться от подарка методом Revoce
    4. А получатель получает деньги и сообщением методом GetGift

    Ну не красота ли? Плюс эту картину дополняют три транзакции:



    Первые две из них, это:

    1. Публикация контракта
    2. Вызов функции SetPass с неким хэшем и пополнением баланса контракта на 1 Эфир.

    Заметим, что только одна функция была вызвана.

    Третья транзакция это «неудачная» попытка взломать контракт с вызовом метода GetGift и случайным набором данных.

    А теперь собственно ловушка:

    Давайте внимательно рассмотрим проверку в методе SetPass:

    function SetPass(bytes32 hash)  public payable {
            if ((!passHasBeenSet && (msg.value > 1 ether)) || hashPass == 0x0) {
                hashPass = hash;
                sender = msg.sender;
            }
        }

    Как видите, она основана на переменной passHasBeenSet, которую необходимо устанавливать отдельным одноименным методом PassHasBeenSet. Этот метод вызван не был, следовательно переменная до сих пор имеет значение false. Причем, данный метод может вызвать только тот, кто сначала вызвал метод SetPass.

    То есть, теоретически, любой кто вызовет метод SetPass, с пополнением баланса больше чем 1 Эфир, станет sender'ом. Причем, чтобы уже никто другой не стал им, нужно просто сразу вызвать метод PassHasBeenSet или просто один из методов Revoce/GetGift для вывода денег.

    И вроде бы все логично — просто вызываешь эти два метода и 1 Эфир твой. Но, как мы знаем, в Эфириуме есть атака опережением (front-running). Смысл ее в следующем: злоумышленник наблюдает пул ожидающих транзакций и ждёт вашу транзакцию. Как только транзакция, связанная с контрактом, появляется в пуле транзакций, злоумышленник выполняет транзакцию с большей ценой газа. Транзакция злоумышленника пришла последней в текущем раунде, но благодаря наивысшей цене газа она в реальности будет исполнена раньше, чем ваша транзакция.

    Как думаете, какой метод исполнит злоумышленник? Конечно же PassHasBeenSet.
    Это даст ему возможность избежать смены sender'а и кроме того, все ваши деньги, отосланные методом SetPass, благополучно осядут в контракте. Ну а дальше он просто их выведет.

    Будьте внимательны при работе с блокчейнами!
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 11

      0
      Правильно ли я понимаю, что для этой атаки он(а) теперь сидит онлайн 24/7 и ждет жертву?
      Просто кажется тут какая то незавершенность.
      +12
      Тут все намного проще.
      Никто не сидит 24/7.
      Владелец просто уже вызвал PassHasBeenSet через контракт-прокси. Эта транзакция на etherscan не отображается.
      Значение переменной passHasBeenSet легко проверяется через web3.eth_getStorageAt()

      Пару недель назад на etherscan просмотрщик исходного кода был без wordwrap, и поэтому было множество контрактов-ловушек, где часть кода пряталась за горизонтальной полосой прокрутки.
      После настойчивых обращений разработчики включили wordwrap.

      У меня есть большая коллекция ловушек. Некоторые — просто шедевры.
        +14

        ловушки в студию, пожалуйста

          –2
          Возможно, я когда-нибудь о них расскажу, но сегодня мне лень.
          +8
          А запилите-ка нам статейку.
          0

          Думаю, прежде чем что то кому то начать, нужно все проверить на хабре, а так ли я делаю. И только потом уже в дело пускать

            –1
            Интересно, а если после появления транзы злоумышленника докинуть газа на свою транзу? Предусмотрел ли он такой вариант? =)
              –1
              Есть еще вариант — самому смайнить блок с транзакцией окешивания ловушки, не принимая в него транзакцию злоумышленника. Для этого транзакцию окешивания даже в сеть передавать не надо.
                –1
                Да да да. Я тоже об этом подумал.
              0
              Здрайствуйте, как начинающий пользователь сети Ethereum столкнулся с проблемой некоторый биржи делают вывод eth c помощью смарт- контракта, и если перевести на адрес другую биржу то это будет как Internal Transactions оно так там и висит с помощью какого метода или функций можно получить eth, или достаточно знать приватный ключ от кошелька чтоб можно было использовать монеты по новому адресу? Заранее спасибо

              Only users with full accounts can post comments. Log in, please.