Use timestamp for Replay Attack Prevention

How to protect against replay attacks with a simple backend configuration.

The Main Idea

DFP includes the timestamp field in the decrypted payload. It is the time when the result was generated.

Check timestamp on your backend. If a response is more than a few seconds old compared with the current UTC time, do not use it. This protects you from a replay attack: a situation where someone tries to resend an old DFP response as a fresh one to abuse your service.

How a Replay Attack Can Happen

All DFP responses pass through the user’s browser. First, DFP returns a response to the browser, then the browser script sends this response to your backend. The browser is the weak point here: an attacker can try to replace a fresh DFP response with an old one.

For example, an attacker wants to collect data from your website automatically: track prices, product availability, or available appointment slots.

You allow regular users to open these pages, but you have already configured blocking for requests with a high bot_score to protect against scraping.

The attacker does the following:

  1. Opens the website in a regular browser from a normal IP address.

  2. Receives a genuine DFP response for a clean environment: a normal browser, not a data center, no automation, and a low bot_score.

  3. Saves the full DFP response.

  4. Starts automation: a script that sends a large number of requests.

  5. On every request, substitutes the saved DFP response for the fresh response that would be received from the automated environment.

  6. If the backend does not check timestamp, it may accept the old response as the current check result and decide that the request came from a regular user.

  7. As a result, the requests are not blocked, and the attacker continues to collect data from the website in bulk.

DFP includes timestamp in the payload, so you can check the response freshness and avoid accepting the same result repeatedly.

How to Protect Your Service

Check timestamp after decrypting the payload.

Your backend must perform this check. Do not use time from the browser because it can be spoofed. Rely only on timestamp.

Your backend:

  1. receives the DFP response from the browser;

  2. decrypts the payload;

  3. reads timestamp;

  4. compares timestamp with the current UTC time;

  5. continues processing if the response is fresh, or rejects it if it is too old.

Example policy:

const MAX_DFP_RESPONSE_AGE_MS = 5_000;

const responseTimestamp = Date.parse(payload.timestamp);
const responseAge = Date.now() - responseTimestamp;

if (
  !Number.isFinite(responseTimestamp) ||
  responseAge < 0 ||
  responseAge > MAX_DFP_RESPONSE_AGE_MS
) {
  throw new Error('DFP response is expired');
}

acceptDfpResult(payload);

You choose the allowed timestamp freshness window according to your business scenario.