Beginner FriendlyFoundryNFT
100 EXP
View results
Submission Details
Severity: high
Invalid

`Staking` Contract Vulnerabilities: Reentrancy Risk and Unauthorized Withdrawals Endangering `LoveToken` Assets

Description

The Staking contract contains three vulnerabilities: reentrancy, unchecked external calls, and also potential unauthorized withdrawals. These vulnerabilities allow attacker to manipulate the contract's state and drains LoveToken. Specifically, the withdraw function is susceptible to reentrancy attacks due to improper state. Additionally, the contract lacks proper checks and error handling for external calls, posing risks of unexpected behavior. Unauthorized users may exploit weaknesses to withdraw LoveToken.

Vulnerability Details

  1. Reentrancy Issue:
    This vulnerability arises from making an external call to loveToken.transfer(msg.sender, amount); without first updating the state variables. An attacker could exploit this vulnerability to repeatedly call the withdraw function and execute additional malicious code each time.

  2. Unchecked External Call Issue:
    The external call to loveToken.transfer(msg.sender, amount); is made without proper checks and error handling, which could result in unexpected behavior if the call fails or is manipulated by an attacker.

  3. Potential Unprotected LoveToken Withdrawal Issue:
    The contract does not adequately validate or restrict withdrawals, potentially allowing unauthorized users to withdraw LoveToken from the contract without proper authorization.

Vulnerable Code
/// @notice Decrease the userStakes variable and transfer LoveToken to the user withdrawing.
function withdraw(uint256 amount) public {
// No require needed because of overflow protection
userStakes[msg.sender] -= amount;
@> loveToken.transfer(msg.sender, amount); // Unchecked external call
emit Withdrew(msg.sender, amount);
}

Impact

These vulnerabilities pose significant risk to the security and integrity of the Staking contract. They could lead to loss of LoveToken, manipulation of contract states, and unauthorized access to users LoveToken.

Proof of Concept

The attacker could deploy a malicious contract with a fallback function that repeatedly calls the withdraw function before the state is updated, allowing attacker to drain the contract of funds.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
import "./Staking.sol";
contract MaliciousContract {
Staking public stakingContract;
uint256 public amountToWithdraw = 1 ether;
constructor(Staking _stakingContract) {
stakingContract = _stakingContract;
}
// Function to initiate the reentrancy attack
function initiateAttack() external payable {
// Deposit some amount to the Staking contract
stakingContract.deposit{value: msg.value}(msg.value);
// Call the withdraw function repeatedly to drain the Staking contract
for (uint256 i = 0; i < 10; i++) {
stakingContract.withdraw(amountToWithdraw);
}
}
// Fallback function to receive funds
receive() external payable {}
}

In this malicious contract:

  • The initiateAttack function is used to deposit LoveToken into the Staking contract and then repeatedly call the withdraw function in a loop to drain the contract of funds.

  • The attacker can specify the amount they want to withdraw each time by setting the amountToWithdraw variable.

  • The contract also includes a fallback function to receive any LoveToken sent to it.

The attacker would deploy this malicious contract and specify the address of the deployed Staking contract when deploying the MaliciousContract. Then they call the initiateAttack function with a sufficient amount of LoveToken to trigger the reentrancy attack.

Tools Used

Manual review.

Recommended Mitigation Steps

  1. Use the "Checks-Effects-Interactions" Pattern:
    Ensure that all state changes are made before any external calls are executed. This prevents reentrancy attacks by ensuring that no LoveToken are sent out of the contract until all internal state changes have been completed.

  2. Use the require statement:
    The contract should handle the return value of the transfer function and implement appropriate error handling in case the transfer fails. This can be done by using the require statement to revert the transaction if the transfer fails.

function withdraw(uint256 amount) public {
// No require needed because of overflow protection
userStakes[msg.sender] -= amount;
- loveToken.transfer(msg.sender, amount);
+ require(loveToken.transfer(msg.sender, amount), "Transfer failed");
emit Withdrew(msg.sender, amount);
}
  1. Use the nonReentrant Modifier:
    Implement a nonReentrant modifier to prevent reentrancy attacks. This modifier can be applied to sensitive functions to ensure that they cannot be called recursively.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;
//imports
contract Staking {
//codes...
modifier nonReentrant() {
require(!isReentrant, "Reentrant call detected");
isReentrant = true;
_;
isReentrant = false;
}
// codes...
/// @notice Decrease the userStakes variable and transfer LoveToken to the user withdrawing.
function withdraw(uint256 amount) public nonReentrant {
require(amount > 0, "Amount must be greater than 0");
// Check
require(userStakes[msg.sender] >= amount, "Insufficient balance");
userStakes[msg.sender] -= amount;
emit Withdrew(msg.sender, amount);
// Interaction
loveToken.transfer(msg.sender, amount);
}
}

With these changes:

  • The nonReentrant modifier is added to prevent reentrancy risks by ensuring that withdraw function cannot be called recursively.

  • The withdraw function is updated to use the "Checks-Effects-Interactions" pattern, where state changes are made before any external calls.

  • Separate checks are added to ensure that the amount being withdrawn is valid and that the user has sufficient balance.

  • The nonReentrant modifier should be applied to prevent reentrancy attacks and they cannot be called recursively.

Updates

Lead Judging Commences

0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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