Solidity Best Practices voor slimme contractbeveiliging

blog 1NieuwsOntwikkelaarsEnterpriseBlockchain ExplainedEvenementen en conferentiesPersNieuwsbrieven

Contents

Abonneer op onze nieuwsbrief.

E-mailadres

Wij respecteren uw privacy

HomeBlogBlockchain-ontwikkeling

Solidity Best Practices voor slimme contractbeveiliging

Van monitoring tot tijdstempeloverwegingen, hier zijn enkele professionele tips om ervoor te zorgen dat uw slimme Ethereum-contracten worden versterkt. door ConsenSys 21 augustus 2020 Geplaatst op 21 augustus 2020

soliditeit beste praktijken held

Door ConsenSys Diligence, ons team van blockchain-beveiligingsexperts.

Als u zich bewust bent van de slimme contractbeveiliging en grip krijgt op de eigenaardigheden van de EVM, is het tijd om enkele beveiligingspatronen te overwegen die specifiek zijn voor de programmeertaal Solidity. In deze samenvatting zullen we ons concentreren op aanbevelingen voor veilige ontwikkeling voor Solidity die ook leerzaam kunnen zijn voor het ontwikkelen van slimme contracten in andere talen. 

Oké, laten we erin springen.

Gebruik assert (), vereist (), revert () correct

De gemaksfuncties beweren en vereisen kan worden gebruikt om te controleren op voorwaarden en een uitzondering te genereren als niet aan de voorwaarde wordt voldaan.

De beweren functie mag alleen worden gebruikt om te testen op interne fouten, en om invarianten te controleren.

De vereisen functie moet worden gebruikt om ervoor te zorgen dat aan geldige voorwaarden, zoals invoer, of contractstatusvariabelen wordt voldaan, of om retourwaarden van oproepen naar externe contracten te valideren. 

Door dit paradigma te volgen, kunnen formele analysehulpmiddelen verifiëren dat de ongeldige opcode nooit kan worden bereikt: wat betekent dat er geen invarianten in de code worden geschonden en dat de code formeel wordt geverifieerd.

pragma stevigheid ^ 0,5,0; contract Deler {function sendHalf (adres te betalen adres) openbaar te betalen retouren (uint-saldo) {vereist (msg.value% 2 == 0, "Zelfs waarde vereist."​// Require () kan een optionele berichtstring hebben uint balanceBeforeTransfer = adres (this) .balance; (bool success,) = addr.call.value (msg.value / 2) (""​vereisen (succes); // Aangezien we teruggingen als de overboeking mislukte, zou er geen manier moeten zijn voor ons om nog steeds de helft van het geld te hebben. assert (adres (dit) .balance == balanceBeforeTransfer – msg.value / 2); // gebruikt voor interne foutcontrole retouradres (this) .balance; }} Codetaal: JavaScript (javascript)


Zien SWC-110 & SWC-123

Gebruik modificatoren alleen voor cheques

De code in een modificator wordt meestal uitgevoerd vóór de hoofdtekst van de functie, dus elke statuswijziging of externe oproepen schenden de Controles-effecten-interacties patroon. Bovendien kunnen deze verklaringen ook onopgemerkt blijven door de ontwikkelaar, aangezien de code voor modifier ver verwijderd kan zijn van de functieverklaring. Een externe call in modifier kan bijvoorbeeld leiden tot de herintredingsaanval:

contractregister {adres eigenaar; functie isVoter (adres _addr) externe retouren (bool) {// Code}} contract Verkiezing {Registerregister; modifier isEligible (adres _addr) {vereist (registry.isVoter (_addr));​} function vote () isEligible (msg.sender) public {// Code}} Codetaal: JavaScript (javascript)

In dit geval kan het registercontract een re-entracy-aanval uitvoeren door Election.vote () aan te roepen binnen isVoter ().

Notitie: Gebruik modificatoren om dubbele voorwaardecontroles in meerdere functies te vervangen, zoals isOwner (), gebruik anders de functie, of ga terug naar de functie. Dit maakt uw slimme contractcode beter leesbaar en gemakkelijker te controleren.

Pas op voor afronding met deling van gehele getallen

Alle deling van gehele getallen wordt naar beneden afgerond op het dichtstbijzijnde gehele getal. Als je meer precisie nodig hebt, overweeg dan om een ​​vermenigvuldiger te gebruiken, of sla zowel de teller als de noemer op.

(In de toekomst zal Solidity een vast punt type, wat dit gemakkelijker zal maken.)

// slechte uint x = 5/2; // Resultaat is 2, alle delen van gehele getallen rondt OMLAAG af op het dichtstbijzijnde gehele getal Codetaal: JavaScript (javascript)

Het gebruik van een vermenigvuldiger voorkomt afronding naar beneden, met deze vermenigvuldiger moet in de toekomst rekening worden gehouden bij het werken met x:

// goede uint-multiplier = 10; uint x = (5 * multiplier) / 2; Codetaal: JavaScript (javascript)

Door de teller en de noemer op te slaan, kunt u het resultaat van de teller / noemer off-chain berekenen:

// goede uint-teller = 5; uint noemer = 2; Codetaal: JavaScript (javascript)

Let op de afwegingen tussen abstracte contracten en interfaces

Zowel interfaces als abstracte contracten bieden een aanpasbare en herbruikbare benadering voor slimme contracten. Interfaces, die zijn geïntroduceerd in Solidity 0.4.11, lijken op abstracte contracten, maar kunnen geen functies hebben. Interfaces hebben ook beperkingen, zoals het niet kunnen benaderen van opslag of erven van andere interfaces, wat over het algemeen abstracte contracten praktischer maakt. Interfaces zijn echter zeker nuttig bij het opstellen van contracten voorafgaand aan implementatie. Bovendien is het belangrijk om in gedachten te houden dat als een contract erft van een abstract contract, het alle niet-geïmplementeerde functies moet implementeren via overschrijving, anders zal het ook abstract zijn..

Fallback-functies

Houd fallback-functies eenvoudig

Fallback-functies worden aangeroepen wanneer een contract een bericht wordt gestuurd zonder argumenten (of wanneer er geen functie overeenkomt), en heeft alleen toegang tot 2.300 gas wanneer het wordt aangeroepen vanuit een .send () of .transfer (). Als u Ether van een .send () of .transfer () wilt ontvangen, kunt u in een fallback-functie maximaal een gebeurtenis vastleggen. Gebruik een juiste functie als er meer gas moet worden berekend.

// slechte functie () te betalen {saldi [msg.sender] + = msg.value; } // goede functie aanbetaling () te betalen externe {saldi [msg.sender] + = msg.value; } function () payable {vereist (msg.data.length == 0); stuur LogDepositReceived (msg.sender) uit; } Codetaal: JavaScript (javascript)

Controleer de datalengte in fallback-functies

Sinds de fallback-functies is niet alleen nodig voor gewone etheroverdrachten (zonder data), maar ook als er geen andere functie overeenkomt, moet u controleren of de data leeg zijn als de fallback-functie alleen bedoeld is om ontvangen Ether te loggen. Anders zullen bellers het niet merken als uw contract verkeerd wordt gebruikt en worden functies aangeroepen die niet bestaan.

// slechte functie () te betalen {emit LogDepositReceived (msg.sender); } // goede functie () te betalen {vereist (msg.data.length == 0); stuur LogDepositReceived (msg.sender) uit; } Codetaal: JavaScript (javascript)

Markeer expliciet betaalbare functies en toestandsvariabelen

Vanaf Solidity 0.4.0 moet elke functie die ether ontvangt de payable modifier gebruiken, anders heeft de transactie msg.value > 0 zal terugkeren (behalve wanneer gedwongen​.

Notitie: Iets dat misschien niet voor de hand ligt: ​​de betalende modifier is alleen van toepassing op oproepen van externe contracten. Als ik een niet-te betalen functie aanroep in de te betalen functie in hetzelfde contract, zal de niet-te betalen functie niet falen, hoewel msg.value nog steeds is ingesteld.

Markeer expliciet zichtbaarheid in functies en toestandsvariabelen

Benoem expliciet de zichtbaarheid van functies en toestandsvariabelen. Functies kunnen worden gespecificeerd als extern, openbaar, intern of privé. Begrijp alsjeblieft de verschillen tussen hen, extern kan bijvoorbeeld voldoende zijn in plaats van openbaar. Voor toestandsvariabelen is extern niet mogelijk. Door de zichtbaarheid expliciet te labelen, wordt het gemakkelijker om onjuiste aannames op te sporen over wie de functie kan aanroepen of toegang kan krijgen tot de variabele.

  • Externe functies maken deel uit van de contractinterface. Een externe functie f kan niet intern worden aangeroepen (d.w.z. f () werkt niet, maar this.f () werkt). Externe functies zijn soms efficiënter wanneer ze grote reeksen gegevens ontvangen.
  • Publieke functies maken deel uit van de contractinterface en kunnen intern of via berichten worden opgeroepen. Voor openbare toestandsvariabelen wordt een automatische getterfunctie (zie hieronder) gegenereerd.
  • Interne functies en toestandsvariabelen zijn alleen intern toegankelijk, zonder dit te gebruiken.
  • Privéfuncties en toestandsvariabelen zijn alleen zichtbaar voor het contract waarin ze zijn gedefinieerd en niet in afgeleide contracten. Notitie: Alles wat zich in een contract bevindt, is zichtbaar voor alle waarnemers buiten de blockchain, zelfs voor privévariabelen.

// slechte uint x; // de standaard is intern voor statusvariabelen, maar het moet expliciet worden gemaakt. functie buy () {// de standaard is openbaar // openbare code} // goed uint privé y; function buy () extern {// alleen extern aanroepbaar of met behulp van this.buy ()} functiehulpprogramma () public {// extern aanroepbaar, evenals intern: het wijzigen van deze code vereist nadenken over beide gevallen. } function internalAction () internal {// internal code} Codetaal: PHP (php)

Zien SWC-100 en SWC-108

Vergrendel pragma’s voor een specifieke compilerversie

Contracten moeten worden geïmplementeerd met dezelfde compilerversie en vlaggen waarmee ze het meest zijn getest. Door de pragma te vergrendelen, zorgt u ervoor dat contracten niet per ongeluk worden geïmplementeerd met behulp van bijvoorbeeld de nieuwste compiler, die mogelijk een hoger risico op onontdekte bugs heeft. Contracten kunnen ook door anderen worden ingezet en de pragma geeft de door de oorspronkelijke auteurs bedoelde compilerversie aan.

// slechte pragma-stevigheid ^ 0.4.4; // goede pragma-soliditeit 0.4.4; Codetaal: JavaScript (javascript)

Opmerking: een zwevende pragma-versie (dwz. ^ 0.4.25) compileert prima met 0.4.26-nightly.2018.9.25, maar nightly builds mogen nooit worden gebruikt om code voor productie te compileren.

Waarschuwing: Pragma-uitspraken kunnen zweven wanneer een contract bedoeld is voor consumptie door andere ontwikkelaars, zoals in het geval van contracten in een bibliotheek of EthPM-pakket. Anders zou de ontwikkelaar de pragma handmatig moeten bijwerken om lokaal te kunnen compileren.

Zien SWC-103

Gebruik gebeurtenissen om contractactiviteit te volgen

Het kan handig zijn om een ​​manier te hebben om de activiteit van het contract te volgen nadat het is geïmplementeerd. Een manier om dit te bereiken is door naar alle transacties van het contract te kijken, maar dat kan onvoldoende zijn, aangezien berichtoproepen tussen contracten niet in de blockchain worden opgenomen. Bovendien toont het alleen de invoerparameters, niet de feitelijke wijzigingen die in de staat worden aangebracht. Eveneens kunnen gebeurtenissen worden gebruikt om functies in de gebruikersinterface te activeren.

contract Charity {mapping (address => uint) saldi; functie donate () betaalbaar publiek {saldi [msg.sender] + = msg.value; }} contractspel {functie buyCoins () openbaar te betalen {// 5% gaat naar het goede doel charity.donate.value (msg.value / 20) (); }} Codetaal: JavaScript (javascript)

Hier zal het Game-contract een interne oproep doen naar Charity.donate (). Deze transactie wordt niet weergegeven in de externe transactielijst van Charity, maar is alleen zichtbaar in de interne transacties.

Een evenement is een handige manier om iets te registreren dat in het contract is gebeurd. Gebeurtenissen die werden uitgezonden, blijven samen met de andere contractgegevens in de blockchain en zijn beschikbaar voor toekomstige audits. Hier is een verbetering ten opzichte van het bovenstaande voorbeeld, waarbij evenementen worden gebruikt om een ​​geschiedenis van de donaties van het goede doel te geven.

contract Charity {// definieer event event LogDonate (uint _amount); mapping (adres => uint) saldi; functie donate () betaalbaar publiek {saldi [msg.sender] + = msg.value; // emit event verzend LogDonate (msg.value); }} contractspel {functie buyCoins () openbaar te betalen {// 5% gaat naar het goede doel charity.donate.value (msg.value / 20) (); }} Codetaal: JavaScript (javascript)

Hier worden alle transacties die via het liefdadigheidscontract verlopen, al dan niet rechtstreeks, weergegeven in de evenementenlijst van dat contract, samen met het bedrag aan gedoneerd geld.

Opmerking: geef de voorkeur aan nieuwere Solidity-constructies. Geef de voorkeur aan constructies / aliassen zoals selfdestruct (boven zelfmoord) en keccak256 (boven sha3). Patronen zoals vereisen (msg.sender.send (1 ether)) kunnen ook worden vereenvoudigd door transfer () te gebruiken, zoals in msg.sender.transfer (1 ether). Uitchecken Solidity Wijzigingslogboek voor meer vergelijkbare wijzigingen.

Houd er rekening mee dat ‘Ingebouwde functies’ kunnen worden overschaduwd

Het is momenteel mogelijk om schaduw ingebouwde globals in Solidity. Hierdoor kunnen contracten de functionaliteit van ingebouwde ins zoals msg en revert () overschrijven. Hoewel dit is bedoeld, het kan gebruikers van een contract misleiden over het ware gedrag van het contract.

contract PretendingToRevert {functie revert () interne constante {}} contract VoorbeeldContract is PretendingToRevert {functie somethingBad () openbaar {revert ();​

Contractgebruikers (en auditors) moeten op de hoogte zijn van de volledige smart contract-broncode van elke applicatie die ze willen gebruiken.

Gebruik geen tx.origin

Gebruik nooit tx.origin voor autorisatie, een ander contract kan een methode hebben die uw contract oproept (waarbij de gebruiker bijvoorbeeld wat geld heeft) en uw contract autoriseert die transactie aangezien uw adres in tx.origin is.

contract MyContract {adres eigenaar; functie MyContract () openbare {eigenaar = msg.sender; } functie sendTo (adres ontvanger, uint bedrag) openbaar {vereisen (tx.origin == eigenaar); (bool success,) = receiver.call.value (bedrag) (""​vereisen (succes); }} contract AttackingContract {MyContract myContract; adres aanvaller; functie AttackingContract (adres myContractAddress) openbaar {myContract = MyContract (myContractAddress); aanvaller = msg.sender; } function () public {myContract.sendTo (aanvaller, msg.sender.balance); }} Codetaal: JavaScript (javascript)

U moet msg.sender gebruiken voor autorisatie (als een ander contract uw contract oproept, is msg.sender het adres van het contract en niet het adres van de gebruiker die het contract heeft opgeroepen).

U kunt er hier meer over lezen: Solidity docs

Waarschuwing: Naast het probleem met autorisatie is er een kans dat tx.origin in de toekomst uit het Ethereum-protocol wordt verwijderd, dus code die tx.origin gebruikt, is niet compatibel met toekomstige releases Vitalik: ‘Ga er NIET vanuit dat tx.origin bruikbaar of zinvol zal blijven.’

Het is ook vermeldenswaard dat je door tx.origin te gebruiken de interoperabiliteit tussen contracten beperkt, omdat het contract dat tx.origin gebruikt niet kan worden gebruikt door een ander contract, aangezien een contract niet de tx.origin kan zijn.

Zien SWC-115

Tijdstempel afhankelijkheid

Er zijn drie belangrijke overwegingen bij het gebruik van een tijdstempel om een ​​kritieke functie in een contract uit te voeren, vooral wanneer acties een geldoverdracht omvatten.

Tijdstempel manipulatie

Houd er rekening mee dat de tijdstempel van het blok kan worden gemanipuleerd door een mijnwerker. Overweeg dit contract

uint256 constante privé salt = block.timestamp; function random (uint Max) constante private returns (uint256 resultaat) {// haal de beste seed voor willekeur uint256 x = salt * 100 / Max; uint256 y = salt * block.number / (salt% 5); uint256 seed = block.number / 3 + (salt% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (seed)); retourneer uint256 ((h / x))% Max + 1; // willekeurig getal tussen 1 en Max} Codetaal: PHP (php)

Wanneer het contract de tijdstempel gebruikt om een ​​willekeurig getal te zaaien, kan de mijnwerker binnen 15 seconden nadat het blok is gevalideerd, een tijdstempel plaatsen, waardoor de mijnwerker in feite een optie kan vooraf berekenen die gunstiger is voor zijn kansen in de loterij. Tijdstempels zijn niet willekeurig en mogen in die context niet worden gebruikt.

De regel van 15 seconden

De Geel papier (De referentiespecificatie van Ethereum) specificeert geen beperking voor hoeveel blokken in de tijd kunnen afwijken, maar het specificeert dat elke tijdstempel groter moet zijn dan de tijdstempel van de bovenliggende. Populaire implementaties van Ethereum-protocol Geth en Pariteit beide verwerpen blokken met een tijdstempel van meer dan 15 seconden in de toekomst. Daarom is een goede vuistregel bij het evalueren van het gebruik van tijdstempels: als de schaal van uw tijdsafhankelijke gebeurtenis met 15 seconden kan variëren en de integriteit kan behouden, is het veilig om een ​​block.timestamp te gebruiken.

Gebruik block.number niet als tijdstempel

Het is mogelijk om een ​​tijdsverschil te schatten met behulp van de eigenschap block.number en gemiddelde bloktijd, dit is echter niet toekomstbestendig aangezien bloktijden kunnen veranderen (zoals vork reorganisaties en de moeilijkheidsgraad bom​Bij een verkoop die dagen overspant, stelt de regel van 15 seconden iemand in staat om een ​​betrouwbaardere schatting van de tijd te maken.

Zien SWC-116

Waarschuwing voor meervoudige overerving

Bij het gebruik van meervoudige overerving in Solidity, is het belangrijk om te begrijpen hoe de compiler de overervingsgrafiek samenstelt.

contract Final {uint public a; functie Final (uint f) public {a = f; }} contract B is definitief {int openbare vergoeding; functie B (uint f) Final (f) public {} function setFee () public {fee = 3; }} contract C is definitief {int openbare vergoeding; functie C (uint f) Final (f) public {} function setFee () public {fee = 5; }} contract A is B, C {functie A () openbaar B (3) C (5) {setFee (); }} Codetaal: PHP (php)

Wanneer een contract wordt geïmplementeerd, zal de compiler de overerving van rechts naar links lineariseren (nadat het sleutelwoord is, worden de ouders weergegeven van de meest basale naar de meest afgeleide). Hier is de linearisering van contract A:

Laatste <- B. <- C <- EEN

Het gevolg van de linearisering zal een vergoedingswaarde van 5 opleveren, aangezien C het meest afgeleide contract is. Dit lijkt misschien voor de hand liggend, maar stel je scenario’s voor waarin C in staat is om cruciale functies te overschaduwen, booleaanse clausules opnieuw te ordenen en ervoor te zorgen dat de ontwikkelaar exploiteerbare contracten schrijft. Statische analyse levert momenteel geen probleem op met overschaduwde functies, dus deze moet handmatig worden geïnspecteerd.

Om bij te dragen, heeft Github van Solidity een project met alle overervingsgerelateerde problemen.

Zien SWC-125

Gebruik interfacetype in plaats van het adres voor typeveiligheid

Wanneer een functie een contractadres als argument aanneemt, is het beter om een ​​interface of contracttype door te geven in plaats van een onbewerkt adres. Als de functie ergens anders in de broncode wordt aangeroepen, biedt de compiler aanvullende veiligheidsgaranties voor het type.

Hier zien we twee alternatieven:

contract Validator {function validate (uint) externe returns (bool); } contract TypeSafeAuction {// goede functie validateBet (Validator _validator, uint _value) interne returns (bool) {bool valid = _validator.validate (_value); retour geldig; }} contract TypeUnsafeAuction {// slechte functie validateBet (adres _addr, uint _value) interne retouren (bool) {Validator validator = Validator (_addr); bool valid = validator.validate (_value); retour geldig; }} Codetaal: JavaScript (javascript)

De voordelen van het gebruik van het TypeSafeAuction-contract hierboven kunnen dan worden gezien aan de hand van het volgende voorbeeld. Als validateBet () wordt aangeroepen met een adresargument, of een contracttype anders dan Validator, zal de compiler deze fout genereren:

contract NonValidator {} contract Veiling is TypeSafeAuction {NonValidator nonValidator; functie inzet (uint _value) {bool valid = validateBet (nonValidator, _value); // TypeError: ongeldig type voor argument in functieaanroep. // Ongeldige impliciete conversie van contract NonValidator // naar contract Validator aangevraagd. }} Codetaal: JavaScript (javascript)

Gebruik geen extcodesize om te controleren op externe accounts

De volgende modificator (of een vergelijkbare controle) wordt vaak gebruikt om te controleren of er een oproep is gedaan vanaf een account in externe eigendom (EOA) of een contractaccount:

// slechte modifier isNotContract (adres _a) {uint size; assembly {size: = extcodesize (_a)} vereist (size == 0);​} Codetaal: JavaScript (javascript)

Het idee is eenvoudig: als een adres code bevat, is het geen EOA maar een contractaccount. Echter, bij een contract is tijdens de bouw geen broncode beschikbaar. Dit betekent dat terwijl de constructor actief is, deze kan bellen naar andere contracten, maar extcodegrootte voor zijn adres retourneert nul. Hieronder ziet u een minimaal voorbeeld dat laat zien hoe deze controle kan worden omzeild:

contract OnlyForEOA {uint openbare vlag; // slechte modifier isNotContract (adres _a) {uint len; assembly {len: = extcodesize (_a)} vereist (len == 0);​} functie setFlag (uint i) public isNotContract (msg.sender) {flag = i; }} contract FakeEOA {constructor (adres _a) openbaar {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Codetaal: JavaScript (javascript)

Omdat contractadressen vooraf kunnen worden berekend, kan deze controle ook mislukken als een adres wordt gecontroleerd dat leeg is in blok n, maar waarop een contract is geïmplementeerd in een bepaald blok groter dan n..

Waarschuwing: Deze kwestie is genuanceerd. Als het uw doel is om te voorkomen dat andere contracten uw contract kunnen bellen, is de extcodesize-check waarschijnlijk voldoende. Een alternatieve benadering is om de waarde van (tx.origin == msg.sender) te controleren, maar dit ook heeft nadelen.

Er kunnen andere situaties zijn waarin de extcodesize-controle uw doel dient. Ze hier allemaal beschrijven, valt buiten het bereik. Begrijp het onderliggende gedrag van de EVM en gebruik uw oordeel.

Is uw Blockchain-code veilig??

Boek een 1-daagse steekproef bij onze beveiligingsexperts. Boek de jouwe vandaag DiligenceBeveiligingSlimme contractenSoliditeitNieuwsbriefSchrijf je in voor onze nieuwsbrief voor het laatste Ethereum-nieuws, bedrijfsoplossingen, bronnen voor ontwikkelaars en meer.E-mailadresExclusieve inhoudHoe u een succesvol blockchain-product bouwtWebinar

Hoe u een succesvol blockchain-product bouwt

Hoe u een Ethereum-knooppunt instelt en uitvoertWebinar

Hoe u een Ethereum-knooppunt instelt en uitvoert

Hoe u uw eigen Ethereum-API kunt bouwenWebinar

Hoe u uw eigen Ethereum-API kunt bouwen

Hoe u een sociaal token maaktWebinar

Hoe u een sociaal token maakt

Beveiligingshulpmiddelen gebruiken bij slimme contractontwikkelingWebinar

Beveiligingshulpmiddelen gebruiken bij slimme contractontwikkeling

De toekomst van digitale activa en defiWebinar

De toekomst van financiën: digitale activa en deFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
Adblock
detector
map