T09 · Insecure Skill Coding Practices
- Location
references/webhooks-events.md:26- Finding
Webhook Endpoint Processes Unauthenticated, Attacker-Controlled Events
- Content
View full analysis
{ // Return 200 immediately to acknowledge receipt res.status(200).send('OK'); // Process asynchronously processWebhookAsync(req.body).catch(console.error); }); ``` ### Technical Analysis This complete endpoint example passes `req.body` directly to `processWebhookAsync` without authenticating the webhook sender or verifying the payload signature. Because the endpoint is intended to be publicly accessible, any remote client capable of reaching it can submit fabricated events. Although a later section separately demonstrates signature verification, the initial example is independently usable and unsafe if copied as written. The documented event handlers perform security-relevant business operations, including suppressing email addresses, removing recipients from lists, and modifying delivery records. Processing events before authentication violates the trust boundary between an external webhook sender and internal application logic. The endpoint also returns HTTP 200 before establishing authenticity or durably queueing the event. Consequently, invalid requests receive successful acknowledgements, while failures during asynchronous processing cannot be communicated to the legitimate provider for retry. ### Attack Path 1. An application implements the endpoint shown in the documentation. 2. The endpoint is exposed publicly so the email provider can deliver events. 3. An attacker sends a crafted POST request to `/webhooks/resend`. 4. The request body impersonates an `email.bounced`, `email.complained`, or `email.delivered` event and identifies a targeted address or message. 5. The endpoint returns HTTP 200 without validating the sender. 6. `processWebhookAsync` processes the attacker-controlled ...[truncated 934 chars]- Remediation
View remediation
{ try { const event = verifyWebhook(req.rawBody, req.headers); validateEventSchema(event); await enqueueVerifiedEvent(event); return res.status(202).send(''); } catch { return res.status(400).send('Invalid webhook'); } } ); ``` ]]>
