Skip to main content
ClaudeWave
Skill55 repo starsupdated 4mo ago

dos-attack

>

Install in Claude Code
Copy
git clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/dos-attack && cp -r /tmp/dos-attack/packages/opencode/skills/dos-attack ~/.claude/skills/dos-attack
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Denial of Service (DoS) Attack Patterns

**OWASP SC10:2025** - DoS attacks prevent legitimate users from accessing protocol functionality, potentially locking funds permanently.

**2025-2026 Statistics**: DoS vulnerabilities caused protocol freezes affecting $180M+ in locked funds, with gas griefing attacks increasing 45% YoY.

---

## Why DoS Happens (Root Causes)

### Root Cause 1: Unbounded Iteration

Loops without gas limits become unusable as data grows.

```solidity
// VULNERABLE: Unbounded loop
function distributeRewards() external {
    for (uint256 i = 0; i < holders.length; i++) {  // @audit holders can grow unbounded
        token.transfer(holders[i], rewards[holders[i]]);
    }
}
```

**Attacker's view**: "I'll create thousands of tiny positions. Eventually, no one can call this function."

### Root Cause 2: External Call Dependency

Function success depends on external call that attacker can make fail.

```solidity
// VULNERABLE: Relies on external transfer success
function withdrawAll() external {
    for (uint256 i = 0; i < users.length; i++) {
        payable(users[i]).transfer(balances[users[i]]);  // @audit One revert blocks all
    }
}
```

**Attacker's view**: "If I'm in the array and my receive() reverts, nobody gets paid."

### Root Cause 3: Storage Slot Exhaustion

Unlimited storage growth makes operations cost-prohibitive.

```solidity
// VULNERABLE: Unlimited storage growth
mapping(address => uint256[]) public userDeposits;

function deposit() external payable {
    userDeposits[msg.sender].push(msg.value);  // @audit Array grows forever
}

function getTotalDeposits(address user) external view returns (uint256) {
    uint256 total;
    for (uint256 i = 0; i < userDeposits[user].length; i++) {  // @audit View can run out of gas
        total += userDeposits[user][i];
    }
    return total;
}
```

### Root Cause 4: Block Gas Limit Exploitation

Transaction exceeds block gas limit, making it impossible to execute.

```solidity
// VULNERABLE: Can exceed block gas limit
function processAllPending() external {
    while (pendingQueue.length > 0) {
        _processSingle(pendingQueue[0]);
        pendingQueue.pop();
    }
}
```

---

## The Liveness Analysis Map (Core Artifact)

For each critical function, document:

```
Function: withdrawAll()
├── External Calls: N calls to user addresses
├── Loop Bound: users.length (unbounded)
├── Gas Estimate: O(n) where n = users count
├── Failure Mode: Single revert blocks all withdrawals
├── Recovery: None - funds permanently locked
└── Risk: CRITICAL
```

| Function | Dependency | Bound | Failure Impact | Recovery |
|----------|------------|-------|----------------|----------|
| distributeRewards | N transfers | Unbounded | Protocol freeze | None |
| processQueue | Queue size | Bounded (100) | Temporary delay | Retry |
| batchLiquidate | M liquidations | User-controlled | Partial failure | Continue |

---

## Detection Patterns

### Pattern 1: Unbounded Loop DoS

**Root Cause**: Unbounded Iteration

```solidity
// VULNERABLE: Loop over dynamic array
function processAll() external {
    for (uint256 i = 0; i < items.length; i++) {
        _process(items[i]);
    }
}
```

**Attack Flow**:
1. Attacker adds many small items to array
2. Array grows to thousands of entries
3. Gas cost exceeds block limit
4. Function becomes uncallable
5. Funds/operations permanently stuck

**Search Queries**:
```
Grep("for.*\\.length|while.*\\.length", glob="**/*.sol")
Grep("for.*i\\+\\+|for.*i < ", glob="**/*.sol")
```

**Mitigation**:
```solidity
// SECURE: Pagination pattern
function processRange(uint256 start, uint256 end) external {
    require(end <= items.length && end - start <= MAX_BATCH);
    for (uint256 i = start; i < end; i++) {
        _process(items[i]);
    }
}
```

### Pattern 2: External Call Failure DoS

**Root Cause**: External Call Dependency

```solidity
// VULNERABLE: One failure blocks all
function refundAll() external {
    for (uint256 i = 0; i < refundees.length; i++) {
        (bool success,) = refundees[i].call{value: amounts[i]}("");
        require(success, "Refund failed");  // @audit Blocks on any failure
    }
}
```

**Attack Flow**:
1. Attacker enters system with contract that reverts on receive
2. Refund function iterates to attacker's address
3. Attacker's receive() reverts
4. Entire function reverts
5. All refunds blocked

**Search Queries**:
```
Grep("\\.call\\{value.*require\\(success", glob="**/*.sol")
Grep("\\.transfer\\(|send\\(", glob="**/*.sol")
```

**Mitigation**:
```solidity
// SECURE: Pull pattern
mapping(address => uint256) public pendingRefunds;

function withdraw() external {
    uint256 amount = pendingRefunds[msg.sender];
    pendingRefunds[msg.sender] = 0;
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}
```

### Pattern 3: Gas Griefing

**Root Cause**: Unchecked Gas Forwarding

```solidity
// VULNERABLE: Forwards all gas to untrusted call
function executeCallback(address target, bytes calldata data) external {
    (bool success,) = target.call(data);  // @audit Attacker can consume all gas
    require(success);
}
```

**Attack Flow**:
1. Attacker creates contract with expensive fallback
2. Callback executes, consuming all forwarded gas
3. Parent transaction fails or behaves unexpectedly
4. Griefing attack succeeds

**Search Queries**:
```
Grep("\\.call\\(|\\.delegatecall\\(", glob="**/*.sol")
Grep("gasleft\\(\\)", glob="**/*.sol")
```

**Mitigation**:
```solidity
// SECURE: Limit gas forwarded
(bool success,) = target.call{gas: 50000}(data);
// OR use try/catch
try ICallback(target).callback{gas: 50000}(data) {} catch {}
```

### Pattern 4: Block Stuffing

**Risk**: Attacker fills blocks to prevent time-sensitive operations

```solidity
// VULNERABLE: Time-sensitive operation
function claimAuction() external {
    require(block.timestamp >= auctionEnd, "Auction ongoing");
    require(!claimed, "Already claimed");
    claimed = true;
    // Transfer winning bid