Hawk High

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

[H-02] Principal Can Illegally Add Themselves as Teacher to Double-Dip Salary Payments

Summary

The addTeacher() function fails to prevent the principal from adding themselves as a teacher, allowing them to:

  1. Illegitimately claim a share of the teachers' 35% bursary allocation

  2. Still receive their normal 5% principal payment

Vulnerability Details

Location

  • File: levelOne.sol

  • Functions:

    • addTeacher()

    • graduateAndUpgrade()

      function addTeacher(address _teacher) public onlyPrincipal notYetInSession {
      if (_teacher == address(0)) {
      revert HH__ZeroAddress();
      }
      if (isTeacher[_teacher]) {
      revert HH__TeacherExists();
      }
      if (isStudent[_teacher]) {
      revert HH__NotAllowed();
      }
      listOfTeachers.push(_teacher);
      isTeacher[_teacher] = true;
      emit TeacherAdded(_teacher);
      }
      function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
      if (_levelTwo == address(0)) {
      revert HH__ZeroAddress();
      }
      uint256 totalTeachers = listOfTeachers.length;
      @> uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
      @> uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
      _authorizeUpgrade(_levelTwo);
      for (uint256 n = 0; n < totalTeachers; n++) {
      usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
      }
      usdc.safeTransfer(principal, principalPay);
      }

Issue

The access control has two critical flaws:

  1. No check preventing principal == _teacher

  2. Principal receives both:

    • Normal 5% principal payment

    • Illegitimate teacher payment (shared from 35% pool)

Proof of Concept

Test Case:

function test_principal_can_steal_teacher_share() public {
// 1. Student enrolls (funds bursary)
vm.prank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
// 2. Principal adds themselves as teacher
vm.startPrank(principal);
levelOneProxy.addTeacher(alice); // Legit teacher
levelOneProxy.addTeacher(principal); // Illegal self-addition
// 3. Upgrade triggers payments
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
vm.stopPrank();
// Verify theft:
assertEq(usdc.balanceOf(alice), 1_750e18);
assertEq(usdc.balanceOf(principal), 2_000e18); // 250e18 (5%) + 1,750e18 (stolen)
}

Test Result:

Ran 1 test for test/LevelOneAndGraduateTest.t.sol:LevelOneAndGraduateTest
[PASS] test_principal_can_steal_teacher_share() (gas: 929760)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 30.41ms (3.73ms CPU time)

Impact

  • Direct fund theft from the teacher pool

  • Violates documented payment structure (5% principal + 35% teachers)

  • Creates governance trust issues

Tools Used

  • Foundry (forge test)

  • Manual analysis of payment flows

Recommendations

  • Add principal exclusion check in addTeacher():

function addTeacher(address _teacher) public onlyPrincipal notYetInSession {
require(_teacher != principal, "Principal cannot be teacher");
// ... existing checks ...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
27 days ago
yeahchibyke Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

principal can become teacher

Principal can add themselves as teacher and share in teacher pay upon graduation

yeahchibyke Lead Judge 16 days ago
Submission Judgement Published
Validated
Assigned finding tags:

principal can become teacher

Principal can add themselves as teacher and share in teacher pay upon graduation

Support

FAQs

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