Skip to content

Unrestricted Withdrawals

id: LS15C
title: Unrestricted Withdrawals
baseSeverity: C
category: access-control
language: solidity
blockchain: [ethereum, polygon, bsc, arbitrum, optimism]
impact: Unauthorized access to funds or assets
status: draft
complexity: low
attack_vector: external
mitigation_difficulty: easy
versions: [">=0.4.0", "<=0.8.25"]
cwe: CWE-862
swc: SWC-105

๐Ÿ“ Description

  • Unrestricted Withdrawals occur when a smart contract allows any user to call a withdrawal function that transfers funds or tokens, without properly verifying ownership, role, or deposit history. These flaws commonly arise from:
  • Missing require() conditions (e.g., msg.sender == owner)
  • Misused public or external visibility on privileged functions
  • Incorrect state tracking of user balances
  • Global withdrawals callable by anyone regardless of deposits
  • This leads to complete loss of protocol assets, enabling malicious actors to withdraw user funds or protocol reserves without authorization.

๐Ÿšจ Vulnerable Code

contract Vault {
    function withdraw() public {
        payable(msg.sender).transfer(address(this).balance); // โŒ no ownership or deposit check
    }
}

๐Ÿงช Exploit Scenario

Step-by-step exploit process:

  1. A poorly coded staking or vault contract contains a withdraw() function without access checks.
  2. An attacker notices the missing verification (e.g., balances[msg.sender], onlyOwner).
  3. They call withdraw() directly.
  4. The contract blindly transfers its entire balance to msg.sender.
  5. All user deposits or reserves are stolen.

Assumptions:

  • The contract holds ETH or ERC20 tokens.
  • There is no require() verifying user deposit, stake, role, or identity.
  • The withdraw() function is callable by the public.
  • There are no per-user balance mappings or they are unused in withdrawal logic.

โœ… Fixed Code

mapping(address => uint256) public balances;

function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");
    balances[msg.sender] = 0;
    payable(msg.sender).transfer(amount);
}

๐Ÿงญ Contextual Severity

- context: "DeFi vault or fundraising contract with open withdrawals"
  severity: C
  reasoning: "Allows anyone to drain protocol funds immediately."
- context: "Owner-only function missing modifier"
  severity: H
  reasoning: "High severity if deployed and callable on mainnet."
- context: "Function unused or unreachable due to constructor logic"
  severity: L
  reasoning: "Lower severity if dead code or access unreachable."

๐Ÿ›ก๏ธ Prevention

Primary Defenses

  • Always enforce access control via require() statements tied to msg.sender.
  • Track user deposits and match withdrawal limits accordingly.
  • Apply onlyOwner or onlyRole modifiers to admin-level withdrawals.

Additional Safeguards

  • Integrate nonReentrant guards when transferring value.
  • Use pullPayment pattern or withdraw queues to reduce abuse risk.
  • Simulate misbehavior during testnet and fuzz scenarios.

Detection Methods

  • Slither: unprotected-function, missing-zero-reset, unchecked-send
  • Manual review for functions transferring ETH, ERC20, or NFTs
  • Fuzz testing with unauthorized users to simulate withdrawal attempts

๐Ÿ•ฐ๏ธ Historical Exploits

  • Name: dForce Lendf.Me Hack
  • Date: 2020-04
  • Loss: ~$25,000,000
  • Post-mortem: Link to post-mortem
  • Name: Eminence (EMN) Exploit
  • Date: 2020-09
  • Loss: ~$15,000,000
  • Post-mortem: Link to post-mortem
  • Name: Meerkat Finance Rug Pull
  • Date: 2021-03
  • Loss: ~$31,000,000
  • Post-mortem: Link to post-mortem

๐Ÿ“š Further Reading


โœ… Vulnerability Report

id: LS15C
title: Unrestricted Withdrawals
severity: C
score:
impact: 5       
exploitability: 4 
reachability: 4  
complexity: 1    
detectability: 4 
finalScore: 4.3

๐Ÿ“„ Justifications & Analysis

  • Impact: Immediate and irreversible asset loss.
  • Exploitability: Requires only a public call to the flawed withdrawal function.
  • Reachability: Occurs in many staking/farming contracts or miswritten vaults.
  • Complexity: Very lowโ€”no technical sophistication required.
  • Detectability: Easily found with tools like Slither or basic manual audit.