Security Tip: Validate Your Webhooks!
[Tip#51] Just because your webhook endpoints aren't listed anywhere (are they?) doesn't mean someone won't find them, and send malicious payloads to your app! You need to validate your webhook payloads!
Webhooks are great!
You just create a route, wait for the provider to send through a useful payload, and then process it.
What could possibly go wrong?!
Ah, but how do you know the payload you've received has come from the actual webhook provider? Surely it must be real, right? I mean, it’s not as if you’ve got your webhook endpoints in your user documentation... right? So how is a hacker going to find your endpoints?
They are safe, right...?
Answer me this, who has webhooks that look like these:
/webhooks/<vendor>
/callbacks/<vendor>
/events/<vendor>
/api/webhooks/<vendor>
/api/callbacks/<vendor>
/api/events/<vendor>
/<vendor>/webhooks
/spark/webhook
...
Yeah, I thought so...
I’m sure you get the point: webhook endpoints are very predictable and easy to find. This makes them incredibly easy to spoof, since it’s trivial for an attacker to learn the expected format of a webhook payload, forge their own, and send it to your app to be processed.
This is why webhook providers (usually) include some method of validating the payload. This is typically done through something like a HMAC signature with a pre-shared key. The provider builds the payload, generates a predictable signature (hash) using HMAC, and sends both the payload and signature through to you. Your app can then generate it’s own HMAC from the received payload, and verify the generated signature matches the provided one. If they don’t match, the payload has been tampered with/forged, and can be ignored.
The best place to start with validating your webhooks is to check the dev/API documentation for your provider. They should have a section on Webhook Security that outlines the process they require. Some may include it automatically in their SDKs too, which makes the job easier for you.
Here are a couple I recently came across:
- Stripe: https://stripe.com/docs/webhooks/signatures
- Paddle: https://developer.paddle.com/webhook-reference/ZG9jOjI1MzUzOTg2-verifying-webhooks
- Mailgun: https://www.mailgun.com/blog/email/your-guide-to-webhooks/#chapter-5
- Twilio: https://www.twilio.com/docs/usage/webhooks/webhooks-security
- Cronofy: https://docs.cronofy.com/developers/push-notifications/authentication/
- Xero: https://developer.xero.com/documentation/guides/webhooks/configuring-your-server#intent-to-receive
If you can’t find their webhook security instructions, ask their support for help, and considering finding a new provider if they don't have anything.
If your provider doesn’t have a way to validate their webhooks…
- They may provide IPs you can allow-list. While this is a relatively good security measure, IPs can still be spoofed with the right environment, so it shouldn’t be your only defence.
- You could also add a random URL parameter (like a random 32-character string) you can confirm is present when receiving the webhook. This will prevent someone from guessing your endpoint, which is better than nothing, but anyone logging the requests will be able to see it.
If you've made it this far, please share this Security Tip and Securing Laravel with your Laravel friends, colleagues, and enemies! The more folks we have learning about security, the more secure code will be written, and the safer the whole community and your users will be. Also, if you tag me I'll give it a retweet!
Find me on: Pinkary, Twitter, Mastodon, LinkedIn, & Threads.