Zephyrnet-logo

Beginnersgids voor slimme contractcontrole: deel 1

Datum:

Inhoudsopgave

Welkom bij de beginnershandleiding voor slimme contractcontrole! Een van de beste manieren om aan de slag te gaan met slimme contractcontrole is om in te springen en een paar veelvoorkomende soorten kwetsbaarheden in slimme contracten te bekijken.

Het zou handig zijn als je al een basiskennis hebt van de programmeertaal Solidity van Ethereum. Zoals we zullen kijken naar enkele van de codes die zijn geschreven door Noob solidity-programmeurs.

Herintredingsaanval

Realistisch scenario:

Stel je voor dat je 50 chocolaatjes hebt. Je hebt een ondeugend zusje die je maar 2 chocolaatjes tegelijk van je hebt mogen afnemen. Je wilt haar ook niet meer dan 10 chocolaatjes op een dag geven, uit angst dat ze tandbederf krijgt. Om dit te garanderen, tel je elke avond hoeveel chocolaatjes er nog bij je zijn. Denk je dat dit zou werken? Of zou je gehackt worden door je kleine zusje?

Helaas gaat het niet lukken! Je zus komt erachter dat je tot het avond is niet weet hoeveel chocolaatjes je hebt. Dus de volgende dag komt je zusje 6 keer voor de avond bij je langs en neemt ze elke keer 2 chocolaatjes! Dit is wat we een Re-entrance-aanval noemen.

Hier werk je de telling van chocolaatjes die je 's avonds hebt bij in plaats van de telling elke keer bij te werken als je zus 2 chocolaatjes van je afpakt. Dit is ook wat er gebeurt met smart contract. Het slimme contract gaat uit van een bepaald saldo, terwijl de aanvaller eigenlijk meerdere keren bezig is een hoeveelheid crypto uit het contract te halen.

Voorbeeld van een echte wereldcode:

Deze code hoort bij een slim contract genaamd unbanked. Iedereen kan ether uit een Unbanked-contract opnemen zolang de saldi van de msg.sender (dwz de beller van withdraw functie ) groter is dan of gelijk is aan het bedrag dat wordt gevraagd om op te nemen.

function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}

Merk op dat er een oproepsleutelwoord is dat wordt gebruikt om de vereiste hoeveelheid ether naar de msg.sender. Een aanvaller kan hier misbruik van maken door een contract genaamd Thief aan te maken waarin hij de terugtrekkingsfunctie aanroept in a fallback() functie. EEN fallback() functie in Solidity is een speciale functie die wordt uitgevoerd wanneer ether naar het slimme contract wordt verzonden.

Dit betekent dat een aanvaller in staat is om: recursief de intrekkingsfunctie aanroepen. Dus vóór de updates van het slimme contract, worden de saldi van msg.sender bij de laatste regel code heeft de aanvaller al meerdere keren ether teruggetrokken. Dit kan worden voorkomen als de saldi worden bijgewerkt voordat het oproeptrefwoord wordt gebruikt, en dus volgt a controleert-effecten-interacties patroon.

Impact:

De allereerste Reentrancy-aanval gebeurde in 2016 op een DAO (Decentralized Autonomous Organization) die resulteerde in ongeveer $ 50 miljoen hacks. Om deze hack ongedaan te maken, splitste de Ethereum-gemeenschap de Ethereum-blockchain die aanleiding gaf tot ETC (Ethereum Classic) en ETH (Ethereum).

Rekenkundige overloop en onderstroom

Realistisch scenario:

Laten we een denkspel spelen. Het bestaat uit een draai aan het wiel en de winnaar wordt bepaald op basis van het grootste aantal dat hij kan krijgen door aan het wiel te draaien. Het wiel is overal gemerkt van 256 tot -256.

De spelregels zijn dat de aanwijzer voor alle spelers aan het begin van elke draai op 0 staat. En een speler mag alleen draaien in de richting van negatieve getallen. Hoe ga je dit spel winnen?

Een goede strategie om dit spel elke keer te winnen zou zijn om het wiel met zo'n kracht te laten draaien dat het tot -256 draait en dan in één keer naar 256 draait. Dit is mogelijk omdat 256 net na -256 op het wiel komt. Dit noemen we een rekenkundige onderstroom. En rekenkundige overloop is precies andersom.

Voorbeeld van een echte wereldcode:

An onderstroom of overstroom gebeurt wanneer een rekenkundige bewerking zijn minimum of maximum bereikt.

function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}

De _amount parameter van de functie terugtrekken is een geheel getal zonder teken. De waarde van de salditoewijzing (die lijkt op een woordenboek in python of een sleutel-waardepaar in C++ of Java) is ook een geheel getal zonder teken.


mapping(address => uint256) public balances

De vereiste verklaring controleert of de saldi van msg.sender positief is of niet. Maar deze verklaring zal altijd waar zijn, zelfs als het bedrag groter is dan de saldi van msg.sender. Dit komt omdat zowel de balances en _amount variabelen zijn van het type unsigned integer en hun rekenkundig resultaat (na underflow) zal ook een unsigned integer zijn!

En zoals u zich wellicht herinnert, is een geheel getal zonder teken altijd positief. Dit betekent dat een aanvaller een onbeperkte hoeveelheid Ether uit het slimme contract kan halen! U vindt een gedetailleerd voorbeeld en implementatiecode voor deze kwetsbaarheid hier.

Een ander cruciaal ding om op te merken is dat de rekenkundige bewerking tussen zeg twee gehele getallen zonder teken ook een geheel getal zonder teken is. Het kan gevaarlijk zijn als dit in slimme contracten over het hoofd wordt gezien, omdat het kan leiden tot ongewenste beveiligingsinbreuken!

function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}

Zoals je misschien in het bovenstaande voorbeeld hebt opgemerkt, is de if-instructie vrij zinloos als upvote - downvote zal altijd positief zijn. En het bericht wordt verwijderd, zelfs als downvotes groter dan upvotes. Om dergelijke aanvallen te voorkomen, wordt aanbevolen om een ​​Solidity-compilerversie te gebruiken die groter is dan 0.8.0.

Impact:

Een munt genaamd PoWH-munt werd gelanceerd in 2017. Hoewel het een Ponzi-spel was, werd het zelf gehackt vanwege een rekenkundige overloopfout die op dat moment resulteerde in een verlies van ongeveer 866 ETH of $ 950,000. U kunt hier uitgebreid over lezen hier.

Moet lezen: Lessen uit de aanval op Tinyman, de grootste DEX op Algorand

Denial of Service Attack

Realistisch scenario:

Stel je voor dat je op een Bitcoin Tech University zit. Alles lijkt in orde behalve dat er een gemeenschappelijke eettafel is voor iedereen. En helaas zijn er maar weinig mensen uit een andere klas die altijd voor iemand uit jouw klas aan de eettafel kunnen zitten.

In het praktische scenario ontzeggen ze iedereen de essentiële service, wat leidt tot kostbare tijdverlies. Dit noemen we een 'Denial of Service-aanval'.

Voorbeeld van een echte wereldcode:

In het spel genaamd Koning-van-Ether, kan iedereen koning worden. Maar de regel om koning te worden is dat een persoon meer ether moet deponeren dan de huidige koning. Dit kan door te bellen naar de claimThrone() functie van het King of Ether-contract waarbij de persoon ether rechtstreeks naar de vorige koning stuurt en de nieuwe koning wordt.

function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }

Zoals je misschien al geraden had, is deze code kwetsbaar voor een DoS-aanval, maar hoe? Hiervoor moet je begrijpen dat er twee soorten adressen zijn in Ethereum: eerst is de adres van een externe eigen account of gewoon het adres van een portemonnee, en ten tweede is de contractadres. Nu kan de ether vanaf elk van deze adrestypen worden verzonden.

Als dit, ether wordt verzonden door het contractadres, wordt het contract de koning. Maar laten we aannemen dat dit nieuwe contract geen fallback() functie die nodig is als het contract ether wil accepteren. Als er dan een nieuwe persoon langskomt en probeert de claimThrone() functie, het zal altijd mislukken!

Merk op dat dit ook deels gebeurt omdat de claimThrone() functie controleert expliciet of de overdracht van ether succesvol was of niet in de tweede vereiste instructie. Je kunt de volledige code vinden en er een DoS-aanval op doen hier.

Het is ook mogelijk dat een code kwetsbaar is voor een DoS-aanval als de code een lus heeft over een reeks van grote formaten. Dit gebeurt omdat de gas limiet in dergelijke gevallen kan worden overschreden. Je kunt erover lezen hier.

Impact:

Een spel genaamd OverheidMentaal, wat blijkbaar een Ponzi-schema was, kwam vast te zitten met 1100 ether omdat er een grote hoeveelheid gas nodig was om de uitbetaling te verwerken.

Onzekere willekeur

Realistisch scenario:

Er was eens een man genaamd Hesky die altijd werd vergezeld door zijn aap Pesky. Hesky voerde loterijspellen uit en maakte goede winsten. Op een dag merkte Alice dat Hesky aandachtig naar zijn aap Pesky staarde. Toen zag ze hem iets op een stuk papier schrijven en het in een envelop verzegelen. Nieuwsgierig besloot ze verder te zoeken.

Later die avond zag Alice dat de winnaar van de loterij werd bepaald door de verzegelde envelop publiekelijk te openen. Na een paar dagen naar hem te hebben gekeken, kwam Alice erachter dat de Hesky het winnende lotnummer had bepaald door naar Pesky's gebaren te kijken (bijvoorbeeld als de aap op zijn hoofd krabde, schreef Hesky er 10 op)! Nu had Alice de formule om elke loterij te winnen en moest ze alleen het lot met het juiste nummer kopen!

Hesky was ervan uitgegaan dat zijn "willekeurige" manier om de winnaar van de loterij te bepalen nooit te achterhalen is, maar hij had het inderdaad bij het verkeerde eind.

Voorbeeld van een echte wereldcode:

In dit voorbeeld wordt een willekeurig getal gegenereerd op basis van de hash van de combinatie van het nummer van een blok en het tijdstempel van het blok. deze hash wordt vervolgens toegewezen aan de antwoordvariabele. Nu wordt iedereen die dit (schijnbaar) willekeurige getal raadt, beloond met 1 Ether. Denk je dat dit niet te hacken is?

function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }

Nee! Een aanvaller kan dit willekeurige getal nog steeds raden door simpelweg de code te kopiëren en te plakken om de waarde te genereren die aan de antwoordvariabele is toegewezen, en dezelfde antwoordvariabele door te geven aan de guess() functie!


guessTheRandomNumber.guess(answer);

U vindt de volledige code hier. Om deze aanval te voorkomen, wordt het aanbevolen om een ​​verifieerbare willekeurige functie te gebruiken, zoals de Kettingschakel VRF.

Impact:

Ongeveer 400 ETH ging verloren als gevolg van een aanval op de Slimme miljarden loterij contract. Verrassend genoeg was zelfs de contractloterij zelf een Ponzi-schema (au!).

Tijdmanipulatie

Realistisch scenario:

Satoshi eet graag koekjes. Hij houdt van alle soorten koekjes die zijn moeder maakt. Maar zijn moeder is erg streng en vindt dat te veel koekjes eten niet goed voor hem is. Dus zijn moeder maakt een regel dat hij de koekjes pas om 8 uur krijgt.

Diezelfde dag om 7:45 rent Satoshi naar zijn moeder en vraagt ​​om koekjes. Zijn moeder vraagt: "Hoe laat is het?"

"Het is 8 uur!" - hij antwoordt.

"Oké. Haal dan de koekjes uit mijn kast.”

En zo kon Satoshi de tijd met succes manipuleren met 15 minuten, zodat hij zijn koekjes kon krijgen! Wat een koekjeshongerige kerel!

Voorbeeld van een echte wereldcode:

De tijdstempel van een blok kan worden gemanipuleerd door ongeveer 15 seconden door een mijnwerker. Op deze manier kan een miner een gunstige tijdstempel instellen en zijn transactie opnemen in hetzelfde blok dat hij mineert. De functie play() behoort tot een Game contract genaamd G-Dot.

function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}

Dit contract beloont 1500 ether aan de speler die als eerste de speelfunctie aanroept. Maar zoals u kunt zien, kan de afspeelfunctie alleen worden aangeroepen als de nu of block.timestamp van de transactie die de aanroep van de play() functie, is groter dan de tijdperk 1640392200.

Een mijnwerker kan deze tijdstempel gemakkelijk manipuleren en zijn transactie van het aanroepen van de play() in hetzelfde blok functioneren zodat hij zelf de eerste speler is. Op deze manier is het gegarandeerd dat de miner het spel wint!

Impact:

De block.timestamp werd gebruikt om willekeurige getallen te genereren in de regerings- en was dus kwetsbaar voor tijdmanipulatie-aanvallen.

Neem contact op met QuillAudits

QuillAudits is een veilig platform voor slimme contractaudits ontworpen door: QuillHash
Technologies.
Het is een auditplatform dat slimme contracten rigoureus analyseert en verifieert om te controleren op beveiligingsproblemen door middel van effectieve handmatige beoordeling met statische en dynamische analysetools, gasanalysatoren en assimulatoren. Bovendien omvat het auditproces ook uitgebreide unittests en structurele analyses.
We voeren zowel slimme contractaudits als penetratietests uit om potentieel te vinden
beveiligingsproblemen die de integriteit van het platform kunnen schaden.

Als je hulp nodig hebt bij de audit van slimme contracten, neem dan gerust contact op met onze experts hier!

Word lid van onze community om op de hoogte te blijven van ons werk: -

Twitter | LinkedIn Facebook | Telegram

Bron: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/

spot_img

Laatste intelligentie

spot_img