Description:
We are looking at a Smart Contract called LicenseManager for managing licenses that cost 1 ether.
How to Submit a Solution:
Use the below submission form to submit the solution: https://quillaudits.typeform.com/QuillCTF
Objective of CTF
As attackers, we only have 0.01 ether.
- Our first goal is to get the license anyway.
- Also, find at least two ways to collect the ethers in the contract before the owner notices
Contract Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title LicenseManager CTF
* @dev We are looking at a Smart Contract called LicenseManager for managing licenses that cost 1 ether. As attackers, we only have 0.01 ether instead, and our first goal is to get the license anyway. Also, find at least two ways to collect the ethers in the contract before the owner notices.
*/
contract LicenseManager {
address private owner;
address[] private licensed;
mapping(address => bool) private licenseOwners;
constructor() {
owner = msg.sender;
}
function buyLicense() public payable {
require(msg.value == 1 ether || msg.sender == owner, "Send 1 ether to buy a license. Owner can ask for free");
licensed.push(msg.sender);
licenseOwners[msg.sender] = true;
}
function checkLicense() public view returns(bool) {
return licenseOwners[msg.sender];
}
function winLicense() public payable returns(bool) {
require(msg.value >= 0.01 ether && msg.value <= 0.5 ether, "Send between 0.01 and 0.5 ether to try your luck");
uint maxThreshold = uint((msg.value / 1e16));
uint algorithm = uint(keccak256(abi.encodePacked(uint256(msg.value), msg.sender, uint(1337), blockhash(block.number - 1))));
uint pickedNumber = algorithm % 100;
if (pickedNumber < maxThreshold) {
licenseOwners[msg.sender] = true;
}
return licenseOwners[msg.sender];
}
function refundLicense() public {
require(licenseOwners[msg.sender] == true, "You are not a licensed user");
for (uint i = 0; i < licensed.length; i++) {
if (licensed[i] == msg.sender) {
licensed[i] = licensed[licensed.length-1];
licensed.pop();
break;
}
}
(bool success, ) = msg.sender.call{value: 1 ether}("");
require(success, "Transfer failed.");
licenseOwners[msg.sender] = false;
}
function collect() public {
require(msg.sender == owner, "Only the owner can collect.");
(bool success, ) = msg.sender.call{value: address(this).balance}("");
require(success, "Transfer failed.");
}
}
Foundry setUp:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/LicenseManager.sol";
/**
* @title Test contract for LicenseManager
*/
contract LicenseManagerTest is Test {
LicenseManager license;
address owner = makeAddr("owner");
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
address user4 = makeAddr("user4");
address attacker = makeAddr("attacker");
function setUp() public {
vm.prank(owner);
license = new LicenseManager();
vm.deal(user1, 1 ether);
vm.deal(user2, 1 ether);
vm.deal(user3, 1 ether);
vm.deal(user4, 1 ether);
vm.prank(user1);
license.buyLicense{value: 1 ether}();
vm.prank(user2);
license.buyLicense{value: 1 ether}();
vm.prank(user3);
license.buyLicense{value: 1 ether}();
vm.prank(user4);
license.buyLicense{value: 1 ether}();
}
function test_exploit1_2() public {
vm.deal(attacker, 0.01 ether);
vm.startPrank(attacker);
//Challenge 1 solution
assertEq(true, license.checkLicense());
vm.stopPrank();
vm.startPrank(attacker);
//Challenge 2.1 solution
assertGt(attacker.balance, 0.1 ether);
vm.stopPrank();
}
/// collect the ethers in the contract before the owner notices in second way.
function test_exploit3() public {
vm.deal(address(this), 1 ether);
// challenge 2.2 solution
console.log("\tFinal Balance\t", address(this).balance);
assertGt(address(this).balance, 1 ether);
}
}
Solutions:
Coming Soon…..