HIGH replay attackgrapehmac signatures

Replay Attack in Grape with Hmac Signatures

Replay Attack in Grape with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A replay attack occurs when an attacker intercepts a valid request and re-sends it to the server to produce an unintended effect. In Grape APIs that use HMAC signatures for request authentication, the vulnerability arises when the server validates the signature but does not enforce strict request uniqueness. HMAC signatures are computed over selected parts of the request—typically the HTTP method, path, timestamp, and body—so the same inputs will produce the same signature. If a server accepts a request with a valid HMAC but does not ensure freshness or uniqueness, an attacker can capture a legitimate signed request and replay it at a later time, potentially gaining access or causing repeated actions.

Consider a Grape endpoint that accepts a POST with an X-API-Timestamp and an X-API-Signature. The signature is generated with a shared secret over the concatenation of method, request path, timestamp, and body. If the server only checks that the signature matches and that the timestamp is not too old (for example, within five minutes), it might still accept a valid, previously seen request if the timestamp falls within the allowed window. Without a nonce or a server-side record of recently used timestamp+signature pairs, the server cannot distinguish a legitimate fresh request from a replayed one. This is especially risky for operations that change state, such as creating a resource or charging a payment, because the replayed request will be processed as if it were a new, legitimate action.

The risk is compounded when the timestamp granularity is coarse or when clocks between client and server are not tightly synchronized. An attacker can slightly adjust timestamps within the allowed skew window and still produce a valid HMAC if the server does not enforce strict one-time use. Additionally, if the request body is large and only partially covered by the signature, or if the signature is computed over a subset of headers, an attacker might modify non-covered parameters between replays. Because HMAC signatures provide integrity and authenticity but not replay protection by themselves, the server must explicitly incorporate mechanisms such as single-use timestamps or cryptographic nonces to prevent this class of attack.

Hmac Signatures-Specific Remediation in Grape — concrete code fixes

To mitigate replay attacks in Grape when using HMAC signatures, you should ensure that each request includes a unique, one-time component and that the server tracks or validates that component within a short window. A common approach is to use a timestamp combined with a nonce or to maintain a short-lived cache of recently seen signature components. Below are concrete code examples showing how to implement server-side verification that rejects replays.

First, a helper to maintain a short-term set of used nonce values. In a production environment, replace the in-memory Set with a distributed cache with TTL to support multi-instance deployments.

require 'set'
require 'time'

module NonceStore
  @used_nonces = Set.new
  @mutex = Mutex.new
  TTL = 300 # seconds, slightly larger than the request timestamp window

  def self.add(nonce)
    @mutex.synchronize do
      @used_nonces.add(nonce)
    end
  end

  def self.include?(nonce)
    @mutex.synchronize do
      @used_nonces.include?(nonce)
    end
  end

  def self.purge_expired(threshold_time)
    @mutex.synchronize do
      @used_nonces.select! { |n| n >= threshold_time }
    end
  end
end

Next, configure your Grape endpoint to require and validate the timestamp and nonce. The example below shows a before hook that extracts X-API-Timestamp, X-API-Nonce, and X-API-Signature, then verifies the signature and ensures the nonce has not been used recently.

class App < Grape::API
  format :json
  helpers do
    def verify_hmac_signature!
      timestamp = request.headers['X-API-Timestamp']
      nonce = request.headers['X-API-Nonce']
      signature = request.headers['X-API-Signature']
      return error!('Missing authentication headers', 401) if timestamp.nil? || nonce.nil? || signature.nil?

      # Reject stale requests to limit replay window
      request_time = Time.parse(timestamp)
      allowed_skew = 60 # seconds
      now = Time.now.utc
      if (now - request_time).abs > allowed_skew
        error!('Request timestamp out of allowed skew', 401)
      end

      # Reject replayed nonces
      if NonceStore.include?(nonce)
        error!('Replay detected: nonce already used', 401)
      end

      # Compute expected signature (example uses a subset of headers and body)
      message = "#{request.request_method}\n#{request.path}\n#{timestamp}\n#{nonce}\n#{request.body.read}"
      expected_signature = OpenSSL::HMAC.hexdigest('sha256', ENV['HMAC_SECRET'], message)
      request.body.rewind

      unless Rack::Utils.secure_compare(signature, expected_signature)
        error!('Invalid signature', 401)
      end

      NonceStore.add(nonce)
    end
  end

  before { verify_hmac_signature! }

  resource :orders do
    desc 'Create an order',
         headers: [
           { key: 'X-API-Timestamp', type: String, desc: 'ISO8601 timestamp' },
           { key: 'X-API-Nonce', type: String, desc: 'Unique nonce' },
           { key: 'X-API-Signature', type: String, desc: 'HMAC signature' }
         ],
         http_codes: [
           { code: 201, message: 'Order created' },
           { code: 401, message: 'Unauthorized' }
         ]
    post do
      # Your order creation logic here
      { order_id: SecureRandom.uuid }
    end
  end
end

In this setup, the server rejects requests with timestamps outside an allowed skew and rejects any nonce that has already been seen within the TTL. The nonce can be a random value provided by the client (included in the signature base) or a server-generated value returned in a prior step. By combining HMAC signature verification with nonce or replay tracking, you effectively prevent an attacker from reusing captured requests even if they possess a valid signature.

Frequently Asked Questions

Why does HMAC alone not prevent replay attacks in Grape APIs?
HMAC ensures integrity and authenticity of a request but does not guarantee freshness or uniqueness. Without a nonce or timestamp tracking, an attacker can capture a valid signed request and replay it, and the server will accept it as long as the signature and timestamp window checks pass.
What additional measures complement HMAC to stop replay attacks in Grape?
Include a one-time nonce or a strictly validated timestamp with limited skew, and maintain a short-lived server-side cache of recently used nonces. Also ensure the signature covers all immutable request components (method, path, timestamp, nonce, and body) and use secure comparison to prevent timing attacks.