The Replayed Webhook — Python Bug Hunt

Inspired by the webhook-replay attacks every payment integration guide warns about: capture one signed delivery, resend it later (or pre-date it into the…

  • Language: Python
  • Layer: Backend
  • Difficulty: Medium
  • Concepts: Security, Time
  • Modelled on: Stripe webhooks
  • Visible tests: a fresh event is accepted once; the same event id is a replay; old deliveries are stale
  • Reward: 50 XP for a complete fix

Briefing

Inspired by the webhook-replay attacks every payment integration guide warns about: capture one signed delivery, resend it later (or pre-date it into the future), and trigger the fulfilment twice.

webhook.py validates deliveries. Its replay window only looks one way, and its dedupe records the event before checking it.

Bug report

BUG-HOOK-2X · Priority: Critical (security) · Reported by: pentest

accept(event_id, timestamp, now, seen) rules (300s tolerance):

  • reject if the event id was already accepted -> "replay"
  • reject if |now - timestamp| > 300 -> "stale"
  • otherwise record the id in seen and return "ok"

Observed: the same event id is accepted twice, and a timestamp 2 hours in the FUTURE sails through.

Logs

[hook] evt_9f2 accepted
[hook] evt_9f2 accepted   <- replay!

The code as shipped

src/hooks/webhook.py (editable)

# Validates an incoming webhook delivery.

TOLERANCE_SECONDS = 300

def accept(event_id, timestamp, now, seen):
    seen.add(event_id)
    if now - timestamp > TOLERANCE_SECONDS:
        return "stale"
    if event_id in seen:
        return "ok"
    return "ok"

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Python bug hunts.