Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Unrestricted Graduation Despite Failing Cutoff Score and Score Manipulation Below Minimum Cutoff Threshold

Summary

A vulnerability in the LevelOne smart contract allows students to graduate and upgrade to LevelTwo even when their final score falls below the established cutoff score. This bypasses critical academic requirements and compromises the integrity of the educational system.

The LevelOne contract's score management system allows teachers to reduce student scores below the minimum cutoff threshold set by the principal, violating the intended business logic and potentially leading to unfair student evaluations.

Vulnerability Details for Unrestricted Graduation Despite Failing Cutoff Score

The contract fails to properly enforce the cutoff score requirement during the graduation process. While the contract tracks student scores and allows setting a cutoff score, the graduateAndUpgrade function does not validate that students meet the minimum score requirement before allowing graduation.

POC for Unrestricted Graduation Despite Failing Cutoff Score

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test, console2} from "forge-std/Test.sol";
import {DeployLevelOne} from "../script/DeployLevelOne.s.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {LevelTwo} from "../src/LevelTwo.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract CutoffScoreVulnerabilityTest is Test {
LevelOne levelOneProxy;
IERC20 usdc;
address proxyAddress;
address principal;
address alice; // teacher
address bob; // teacher
address clara; // student
address david; // student
uint256 constant STARTING_BALANCE = 10000e18; // Increased from 1000e18 to 10000e18
uint256 schoolFees;
function setUp() public {
// Deploy contracts
DeployLevelOne deployer = new DeployLevelOne();
proxyAddress = deployer.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
// Get USDC instance and fees
usdc = deployer.getUSDC();
principal = deployer.principal();
schoolFees = deployer.getSchoolFees(); // This is 5000e18
// Setup accounts
alice = makeAddr("alice");
bob = makeAddr("bob");
clara = makeAddr("clara");
david = makeAddr("david");
// Fund accounts with enough USDC to cover school fees
deal(address(usdc), clara, STARTING_BALANCE);
deal(address(usdc), david, STARTING_BALANCE);
}
function test_CutoffScoreVulnerabilities() public {
// 1. Add teacher first
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
vm.stopPrank();
// 2. Enroll student before starting session
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
// 3. Start session with unreasonable cutoff
vm.startPrank(principal);
levelOneProxy.startSession(1000);
assertEq(levelOneProxy.cutOffScore(), 1000);
vm.stopPrank();
// 4. Give bad reviews to drop score
vm.startPrank(alice);
for (uint i = 0; i < 5; i++) {
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
}
vm.stopPrank();
// 5. Verify final score
uint256 finalScore = levelOneProxy.studentScore(clara);
assertEq(finalScore, 50);
// 6. Graduate despite not meeting cutoff
vm.warp(block.timestamp + 4 weeks);
// Deploy LevelTwo and attempt graduation
LevelTwo levelTwo = new LevelTwo();
address levelTwoImplementation = address(levelTwo);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementation, data);
}
}

The POC demonstrates the vulnerability through the following steps:

  1. A teacher (Alice) is added to the system by the principal

  2. A student (Clara) enrolls by paying school fees

  3. The principal sets an unreasonably high cutoff score of 1000

  4. The teacher gives multiple negative reviews, dropping the student's score to 50

  5. Despite having a score (50) well below the cutoff (1000), the student can still graduate and upgrade to LevelTwo

Key vulnerable code paths:

// No score validation in graduation process
levelOneProxy.graduateAndUpgrade(levelTwoImplementation, data);

Test Output for Unrestricted Graduation Despite Failing Cutoff Score

Ran 1 test for test/CutoffScoreVulnerabilityTest.t.sol:CutoffScoreVulnerabilityTest
[PASS] test_CutoffScoreVulnerabilities() (gas: 914394)
Traces:
[934294] CutoffScoreVulnerabilityTest::test_CutoffScoreVulnerabilities()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ ├─ [73258] LevelOne::addTeacher(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [delegatecall]
│ │ ├─ emit TeacherAdded(: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [152722] ERC1967Proxy::fallback()
│ ├─ [152251] LevelOne::enroll() [delegatecall]
│ │ ├─ [31619] MockUSDC::transferFrom(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50532] ERC1967Proxy::fallback(1000)
│ ├─ [50058] LevelOne::startSession(1000) [delegatecall]
│ │ ├─ emit SchoolInSession(startTime: 1, endTime: 2419201 [2.419e6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [968] ERC1967Proxy::fallback() [staticcall]
│ ├─ [494] LevelOne::cutOffScore() [delegatecall]
│ │ └─ ← [Return] 1000
│ └─ ← [Return] 1000
├─ [0] VM::assertEq(1000, 1000) [staticcall]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [0] VM::warp(604801 [6.048e5])
│ └─ ← [Return]
├─ [29010] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [28533] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 90)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(1209601 [1.209e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 80)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(1814401 [1.814e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 70)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 60)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(3024001 [3.024e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 50)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 50
│ └─ ← [Return] 50
├─ [0] VM::assertEq(50, 50) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(5443201 [5.443e6])
│ └─ ← [Return]
├─ [445078] → new LevelTwo@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 2223 bytes of code
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [57124] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca)
│ ├─ [56635] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca) [delegatecall]
│ │ ├─ [25750] MockUSDC::transfer(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], 1750000000000000000000 [1.75e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 1750000000000000000000 [1.75e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [25750] MockUSDC::transfer(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], 250000000000000000000 [2.5e20])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], value: 250000000000000000000 [2.5e20])
│ │ │ └─ ← [Return] true
│ │ └─ ← [Stop]
│ └─ ← [Return]
└─ ← [Stop]

Vulnerability Details for Score Manipulation Below Minimum Cutoff Threshold

The contract's score management system contains a critical flaw where negative reviews can continue to decrease a student's score even after it reaches the minimum cutoff score set by the principal. This vulnerability is demonstrated in the following sequence:

  1. The principal sets a minimum cutoff score of 50

  2. A student starts with an initial score of 100

  3. Through consecutive negative weekly reviews, the score can be reduced to:

    • Week 1: 100 -> 90

    • Week 2: 90 -> 80

    • Week 3: 80 -> 70

    • Week 4: 70 -> 60

    • Week 5: 60 -> 50

    • Week 6: 50 -> 40 (Below minimum threshold)

The contract lacks proper validation to prevent scores from falling below the established minimum cutoff value.

POC for Score Manipulation Below Minimum Cutoff Threshold

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test, console2} from "forge-std/Test.sol";
import {DeployLevelOne} from "../script/DeployLevelOne.s.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ScoreManipulationTest is Test {
LevelOne levelOneProxy;
IERC20 usdc;
address proxyAddress;
address principal;
address alice; // teacher
address clara; // student
uint256 constant STARTING_BALANCE = 10000e18;
uint256 schoolFees;
function setUp() public {
// Deploy contracts
DeployLevelOne deployer = new DeployLevelOne();
proxyAddress = deployer.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
// Get USDC instance and fees
usdc = deployer.getUSDC();
principal = deployer.principal();
schoolFees = deployer.getSchoolFees();
// Setup accounts
alice = makeAddr("alice");
clara = makeAddr("clara");
// Fund student account
deal(address(usdc), clara, STARTING_BALANCE);
}
function test_ScoreManipulationVulnerability() public {
// 1. Principal adds teacher
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
vm.stopPrank();
// 2. Student enrolls
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
// 3. Principal starts session with cutoff score of 50
vm.prank(principal);
levelOneProxy.startSession(50);
// 4. Teacher gives bad reviews over multiple weeks
vm.startPrank(alice);
// Initial score is 100
assertEq(levelOneProxy.studentScore(clara), 100);
// Week 1: 100 -> 90
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 90);
// Week 2: 90 -> 80
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 80);
// Week 3: 80 -> 70
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 70);
// Week 4: 70 -> 60
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 60);
// Week 5: 60 -> 50
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 50);
// Critical: Score can go below minimum cutoff of 50
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
// Score is now 40, below the cutoff of 50
assertEq(levelOneProxy.studentScore(clara), 40);
vm.stopPrank();
}
}

Test Output for Score Manipulation Below Minimum Cutoff Threshold

Ran 1 test for test/ScoreManipulationTest.t.sol:ScoreManipulationTest
[PASS] test_ScoreManipulationVulnerability() (gas: 398533)
Traces:
[418433] ScoreManipulationTest::test_ScoreManipulationVulnerability()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ ├─ [73258] LevelOne::addTeacher(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [delegatecall]
│ │ ├─ emit TeacherAdded(: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [152722] ERC1967Proxy::fallback()
│ ├─ [152251] LevelOne::enroll() [delegatecall]
│ │ ├─ [31619] MockUSDC::transferFrom(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50532] ERC1967Proxy::fallback(50)
│ ├─ [50058] LevelOne::startSession(50) [delegatecall]
│ │ ├─ emit SchoolInSession(startTime: 1, endTime: 2419201 [2.419e6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 100
│ └─ ← [Return] 100
├─ [0] VM::assertEq(100, 100) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(604801 [6.048e5])
│ └─ ← [Return]
├─ [29010] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [28533] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 90)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 90
│ └─ ← [Return] 90
├─ [0] VM::assertEq(90, 90) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(1209601 [1.209e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 80)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 80
│ └─ ← [Return] 80
├─ [0] VM::assertEq(80, 80) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(1814401 [1.814e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 70)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 70
│ └─ ← [Return] 70
├─ [0] VM::assertEq(70, 70) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 60)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 60
│ └─ ← [Return] 60
├─ [0] VM::assertEq(60, 60) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(3024001 [3.024e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 50)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 50
│ └─ ← [Return] 50
├─ [0] VM::assertEq(50, 50) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(3628801 [3.628e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 40)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 40
│ └─ ← [Return] 40
├─ [0] VM::assertEq(40, 40) [staticcall]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]

Impact

  • Students can graduate without meeting minimum academic requirements

  • Compromises educational standards and certification integrity

  • Potential reputational damage to the institution

  • Undermines the purpose of having a cutoff score system

  • Students can be unfairly evaluated with scores below the minimum threshold

  • Violation of the school's grading policy

  • Potential financial implications if scholarship decisions or continued enrollment are based on scores

  • Loss of system integrity and trust

Tools Used

  • Foundry Framework

  • Forge testing suite

  • Manual review

Recommendations

  1. Add score validation in the graduateAndUpgrade function:

function graduateAndUpgrade(address implementation, bytes memory data) external {
require(studentScore[msg.sender] >= cutOffScore, "Score below cutoff");
// existing graduation logic
}

Implement a score floor check in the giveReview function:

function giveReview(address student, bool positive) public {
uint256 currentScore = studentScore[student];
uint256 newScore = positive ? currentScore + 10 : currentScore - 10;
// Ensure score doesn't fall below minimum cutoff
if (!positive && newScore < minimumCutoffScore) {
newScore = minimumCutoffScore;
}
studentScore[student] = newScore;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

cut-off criteria not applied

All students are graduated when the graduation function is called as the cut-off criteria is not applied.

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

cut-off criteria not applied

All students are graduated when the graduation function is called as the cut-off criteria is not applied.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.