Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack of rate limiting allows malicious users to repeatedly interact with sensitive contract functions, leading to potential disruption or abuse of system resources in `PriorityPool.sol`

Summary

This report highlights a lack of rate limiting vulnerability in the staking pool contract. The absence of rate-limiting mechanisms allows malicious users to repeatedly interact with sensitive contract functions, leading to potential disruption or abuse of system resources.

Vulnerability Details

Rate limiting is crucial to prevent repeated, rapid interactions with smart contract functions that could strain resources or enable Denial of Service (DoS) attacks. In the staking pool contract, there is no limit on how frequently users can invoke critical functions such as depositQueuedTokens, performUpkeep, and withdraw. Without rate-limiting, an attacker can flood the contract with transactions, consuming gas, filling block space, and potentially causing the contract to enter a non-functional or unusable state for other users.

Affected functions:

  1. depositQueuedTokens(uint256 _queueDepositMin, uint256 _queueDepositMax, bytes[] calldata _data): Without rate limiting, a malicious actor could repeatedly call this function to deposit tokens in rapid succession, leading to a disruption in normal deposit operations.

  2. withdraw(address _account, uint256 _amount): Similarly, attackers could spam the withdraw function to repeatedly withdraw small amounts or invoke costly gas operations, congesting the network and contract.

  3. performUpkeep(bytes calldata _performData): This function could also be abused by triggering upkeep operations too frequently, potentially overloading the contract and consuming unnecessary gas.

Proof of Concept (PoC):

Scenario:
An attacker, Eve, discovers that the depositQueuedTokens and withdraw functions do not have any rate limiting. Eve writes a script that submits multiple transactions per second, filling up the blockchain's capacity, consuming contract gas, and preventing legitimate users from interacting with the contract effectively.

Steps for PoC:

  1. Deploy the Contract: The contract is deployed in a local Hardhat environment.

  2. Simulate an Attack: Eve spams the contract with thousands of deposit or withdrawal requests in quick succession.

  3. Result: Other users are unable to interact with the contract as Eve’s rapid transactions consume all available gas and block space. The contract becomes less responsive and fails to handle legitimate user requests.

Hardhat test case for lack of rate limiting:

This test simulates an attacker spamming the depositQueuedTokens function in rapid succession.

const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Lack of Rate Limiting PoC", function () {
let StakingPool, stakingPool, token, owner, eve;
before(async function () {
// Deploy token contract and mint tokens to Eve
const Token = await ethers.getContractFactory("ERC20Mock");
token = await Token.deploy("MockToken", "MTK", 18);
await token.deployed();
// Deploy staking pool contract
StakingPool = await ethers.getContractFactory("StakingPool");
stakingPool = await StakingPool.deploy();
await stakingPool.deployed();
[owner, eve] = await ethers.getSigners();
// Mint tokens to Eve
await token.mint(eve.address, ethers.utils.parseEther("1000000"));
// Approve staking pool to spend Eve's tokens
await token.connect(eve).approve(stakingPool.address, ethers.utils.parseEther("1000000"));
});
it("should demonstrate lack of rate limiting", async function () {
// Eve spams the contract with multiple rapid deposit transactions
for (let i = 0; i < 100; i++) {
await stakingPool.connect(eve).depositQueuedTokens(ethers.utils.parseEther("100"), ethers.utils.parseEther("100"), []);
}
// Check that a large number of transactions were processed
const totalQueued = await stakingPool.totalQueued();
expect(totalQueued).to.equal(ethers.utils.parseEther("10000")); // Eve deposited 100 tokens 100 times
});
});

Impact

  1. Attackers can flood the contract with a high volume of transactions, consuming block space and gas. This can prevent legitimate users from depositing or withdrawing tokens.

  2. The attacker could potentially drain contract resources or lock them up in pending transactions, degrading contract performance.

  3. Network congestion caused by repeated spam transactions can drive up gas prices, making it more expensive for other users to interact with the contract.

Tools Used

Manual review.

Recommendations

  • Implement a cooldown period for sensitive functions like depositQueuedTokens and withdraw. Users would need to wait a fixed time before calling these functions again.
    Example:

mapping(address => uint256) private lastDepositTime;
uint256 public depositCooldown = 1 minutes;
function depositQueuedTokens(...) external {
require(block.timestamp >= lastDepositTime[msg.sender] + depositCooldown, "Cooldown period not passed");
lastDepositTime[msg.sender] = block.timestamp;
_depositQueuedTokens(...);
}
  • Implement checks to ensure that transactions are processed at reasonable gas prices, reducing the incentive for attackers to spam the contract with high-priority transactions.

  • Limit the number of transactions a user can submit in a certain time frame or batch multiple deposit or withdrawal requests into a single transaction to reduce the frequency of function calls.

  • Users should only be able to interact with the contract in proportion to the amount they have staked. Higher stakers can call the functions more frequently, while smaller stakers are limited.

  • Implement logic to ensure that users cannot submit back-to-back transactions without a delay or a unique identifier to prevent rapid sequential execution.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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