HIGH replay attackgrape

Replay Attack in Grape

How Replay Attack Manifests in Grape

In Grape, a Replay Attack exploits the absence of request uniqueness guarantees, typically within authentication or state-changing operations. A valid, captured request (e.g., a fund transfer or password change) is resent verbatim to achieve the same effect again. This is distinct from parameter tampering; the attacker reuses legitimate, signed credentials.

Grape's common authentication patterns create specific vulnerable surfaces:

  • Stateless Token Auth Without Nonces: Many Grape APIs use JWT or API keys passed in headers (Authorization: Bearer <token>). If the token validation logic (often in a before block or helper) only verifies signature and expiry but does not check a request-specific nonce or timestamp, the token can be reused until it expires.
  • Missing Request Timestamp Validation: A Grape endpoint might accept a timestamp parameter but fail to validate it is recent (e.g., within 5 minutes). An attacker can capture a legitimate request and replay it hours later if the timestamp is ignored or not checked against a server-side window.
  • Idempotency Key Misuse: While idempotency keys prevent duplicate processing, Grape apps sometimes implement them incorrectly. If the key is client-controlled and stored without server-side expiration or scope binding (e.g., per user), an attacker can replay the same key with different payloads or reuse an old key after the intended operation succeeded.

Vulnerable Grape Code Pattern:

module API
  class Transactions < Grape::API
    before { authenticate! }

    post '/transfer' do
      # VULNERABLE: No nonce, timestamp, or idempotency check
      from = params[:from_account]
      to = params[:to_account]
      amount = params[:amount]
      Bank.transfer(from, to, amount)
      { status: 'success' }
    end
  end
end

Here, any valid authenticated request to /transfer can be replayed indefinitely. An attacker intercepting a request with a valid session token can resend it to repeatedly drain an account.

Grape-Specific Detection

Manual Detection: Review Grape's authentication helpers and before filters. Look for:

  • Absence of a nonce generation/validation step. A secure pattern would generate a cryptographically random nonce per request, store it server-side (e.g., Redis) with a short TTL, and reject duplicates.
  • Missing or incorrect timestamp validation. Check if the code parses a X-Request-Timestamp header and rejects requests older than, say, 300 seconds.
  • Idempotency key implementation that only checks existence without validating the key's association with the specific user/action and without expiring old keys.

Test manually by capturing a legitimate request (e.g., with Burp Suite or OWASP ZAP) and resending it after a delay or from a different session. If the server processes it again identically, the endpoint is vulnerable.

Automated Detection with middleBrick: middleBrick's Authentication and Input Validation checks actively probe for replay vulnerabilities in Grape APIs. During a scan, it:

  • Extracts the authentication mechanism (e.g., Bearer token, API key) from the OpenAPI spec or runtime responses.
  • Issues a valid authenticated request and records the response, including any anti-replay headers (e.g., Idempotency-Key, Nonce) or timestamp fields.
  • Attempts to replay the identical request (same headers, body, method) multiple times. If subsequent replays return 200 OK or similar success codes instead of 409 Conflict (for idempotency) or 401/403 (for expired nonce), it flags a replay risk.
  • Analyzes the OpenAPI spec for Grape endpoints that lack security scheme requirements or have no parameters for timestamps/nonces.

The scan report will highlight vulnerable endpoints under the Authentication category, often with a severity of High, and provide the specific request that was successfully replayed. This aligns with OWASP API Top 10: API2:2023 — Broken Authentication.

Grape-Specific Remediation

Remediation requires implementing a server-side, per-request uniqueness check. In Grape, this is typically done via a before filter or a custom helper that integrates with a fast store like Redis.

Pattern 1: Nonce-Based Replay Protection

Generate a nonce for each authenticated request and store it with a short TTL. Reject any request presenting a previously seen nonce.

require 'securerandom'
require 'redis'

module API
  class Base < Grape::API
    before :validate_nonce!

    helpers do
      def redis
        @redis ||= Redis.new(url: ENV['REDIS_URL'])
      end

      def validate_nonce!
        nonce = headers['X-Nonce']
        error!({ error: 'Missing nonce' }, 400) unless nonce

        # Use SET with NX (only set if not exists) and EX (expire in seconds)
        # Returns true if key was set (new nonce), false if already exists
        unless redis.set(nonce, '1', nx: true, ex: 300)
          error!({ error: 'Replay detected' }, 409)
        end
      end

      def authenticate!
        # ... existing token validation logic ...
      end
    end
  end
end

Pattern 2: Timestamp + Nonce (Defense in Depth)

Combine a short-lived timestamp window with a nonce to prevent both replay and clock skew attacks.

helpers do
  def validate_timestamp_and_nonce!
    timestamp = headers['X-Request-Timestamp'].to_i
    nonce = headers['X-Nonce']

    error!({ error: 'Missing timestamp or nonce' }, 400) if timestamp.zero? || nonce.nil?

    # Reject requests older than 5 minutes (300 seconds)
    current_time = Time.now.to_i
    if (current_time - timestamp).abs > 300
      error!({ error: 'Timestamp out of window' }, 400)
    end

    # Nonce check as above
    unless redis.set("nonce:#{nonce}", '1', nx: true, ex: 300)
      error!({ error: 'Replay detected' }, 409)
    end
  end
end

Pattern 3: Idempotency Key for State-Changing Operations

For POST/PUT/PATCH, require an Idempotency-Key header. Store the hash of the key + request body to detect exact replays, but also bind it to the user and operation type to allow the same key for different endpoints.

helpers do
  def validate_idempotency_key!
    key = headers['Idempotency-Key']
    error!({ error: 'Missing Idempotency-Key' }, 400) unless key

    # Create a composite key: user_id + HTTP method + path + idempotency_key
    composite_key = "idem:#{current_user.id}:#{request.request_method}:#{request.path}:#{key}"

    # Store the hash of the request body to ensure identical payloads
    payload_hash = Digest::SHA256.hexdigest(request.body.read)
    request.body.rewind # Reset stream for downstream use

    stored_hash = redis.get(composite_key)
    if stored_hash == payload_hash
      # Return the cached successful response (or a 200 with a specific header)
      # For simplicity, we return 409 if a duplicate is detected mid-processing
      error!({ error: 'Duplicate request' }, 409)
    else
      # Set the key with a long TTL (e.g., 24 hours) after successful processing
      # This is typically done after the business logic completes
      # redis.setex(composite_key, 86400, payload_hash)
    end
  end
end

class Transactions < Grape::API
  before :validate_idempotency_key!, only: [:post]
  # ...
end

Important: Always perform nonce/idempotency checks after authentication but before business logic. The nonce store must be fast (Redis) and have automatic expiration to avoid memory leaks.

After implementing these fixes, rescan the API with middleBrick. The Authentication and Input Validation category scores should improve, and the replay finding should disappear from the report.

Frequently Asked Questions

How does middleBrick detect replay vulnerabilities in a Grape API?
middleBrick performs active testing by first authenticating to your Grape API (using credentials or tokens from the OpenAPI spec). It then captures a successful authenticated request and automatically replays it multiple times. If the server processes the replayed requests as new, valid operations (returning 200/201), middleBrick flags a replay vulnerability. It also checks your OpenAPI spec for missing anti-replay parameters like nonces or timestamps. The finding appears in the Authentication category with a High severity and includes the specific endpoint and request that was replayed.
What's the difference between a replay attack and a brute force attack in a Grape API?
A replay attack reuses a single, captured valid request to achieve the same malicious effect repeatedly. It exploits the lack of request uniqueness checks (nonces, timestamps). A brute force attack systematically guesses credentials (passwords, tokens) by sending many different requests. In Grape, replay protection requires per-request tokens (nonces) and validation, while brute force protection requires rate limiting on authentication endpoints. middleBrick checks for both: replay via request replay testing, and brute force via its Rate Limiting check.