Webhooks timing analysis attack prevention

I am implementing a square webhook for my application and as I am reading the docs I see the warning on this page.

" Important
A malicious agent can compromise your notification endpoint by using a timing analysis attack to determine the key you’re using to decrypt and compare webhook signatures. You should use a constant-time crypto library to prevent such attacks by masking the actual time taken to decrypt and compare signatures."

I was wondering if I need to implement this functionality to validate webhook events, given the warning on the docs. But I was confused, as the square node.js SDK provides the function “WebhooksHelper.isValidWebhookEventSignature()”. And after looking at the code for the function on github here I don’t see a constant time comparison with the crypto package. I guess my question is the given code enough for security reasons, do I need to do something to my signature before passing into this function or do I need to make a new function similar to isValidWebhookEventSignature that is secure. If someone could clear this warning in the docs that would be great.

The warning in the Square documentation is advising developers to use constant-time comparison when verifying webhook signatures to prevent timing attacks. Timing attacks can occur when an attacker measures the time it takes for your server to respond to signature verification attempts. Differences in response times can potentially be used to infer information about the secret key.

The WebhooksHelper.isValidWebhookEventSignature() function provided in the Square Node.js SDK is designed to validate the signature of incoming webhook events to ensure they are coming from Square. :slightly_smiling_face:

Ahh I see, so do I just validate the signature using a crypto function myself, making sure to use constant time comparisons. Then use the WebhooksHelper.isValidWebhookEventSignature() function to validate it is coming from square. Or am I still missing something. Thanks for the quick response

Yes, your on the right track. To ensure maximum security against timing attacks, you could perform the validation in two steps:

  1. Constant-Time Comparison: First, use a constant-time comparison function to compare the signature sent by Square with the one you generate using your webhook signing secret. In Node.js, you can use the crypto.timingSafeEqual() function to do this. You’ll need to make sure that both signatures are of the same length before performing the comparison to avoid errors.
  2. Square SDK Validation: After you’ve confirmed that the signatures match using a constant-time comparison, you can then use the WebhooksHelper.isValidWebhookEventSignature() function from the Square SDK to perform the additional validation steps that the function provides.

Here’s a simplified example of how you might implement the constant-time comparison:

const crypto = require('crypto');

function isValidSignature(signature, webhookSigningSecret, body) {
  const expectedSignature = crypto.createHmac('sha1', webhookSigningSecret)
                                  .update(body)
                                  .digest('base64');

  const signatureBuffer = Buffer.from(signature, 'base64');
  const expectedSignatureBuffer = Buffer.from(expectedSignature, 'base64');

  if (signatureBuffer.length !== expectedSignatureBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(signatureBuffer, expectedSignatureBuffer);
}

In this example, signature is the signature from the webhook header, webhookSigningSecret is your webhook signing secret, and body is the raw body of the webhook request. Make sure to adjust this example to match the actual signature format and hashing algorithm used by Square webhooks.

Once you’ve confirmed the signatures match using this method, you can proceed with the rest of your webhook processing logic, knowing that the request has been validated both for the correct signature and against timing attacks. :slightly_smiling_face:

Wow, thanks for the help, that definitely clears things up. Going to add something similar like this now, appreciate it!

I’m confused by this. Are we saying that a webhook POST signature should be verified twice? If even one of the signature verifications is not fixed-time, that would negate the use of the fixed time crypto verification. Might it be more effective to add a random pause for a small number of milliseconds, or for Square to fix the helper verification function/s to be fixed time?

Either way it should be noted this would be rather difficult to exploit as it should take something like thousands of hits to explore a sufficiently wide keyspace, I’d think? (Not a crypto expert, so feel free to correct me!). A high volume application should detect a large number of hits in a short period; a low volume application should simply report on failed verifications, which should normally be extremely rare.

Another mitigation would be to return a 403 http status code, or similar, and arrange for your webserver to block requests receiving a lot of 403 errors. Additionally, limiting the IP addresses allowed to post to your webhook would make this attack near impossible. Obviously if you limit the IPs able to post to your webhook, the 403 status code and IP blocking aren’t going to be useful and could be risky (as they could result in a legitimate webhook source IP being blocked).

This stuff gets complicated and is always difficult to do right. The key in security mitigation is defence-in-depth - that is, do you have several mechanisms that detect exploit attempts? It’s easy enough to bypass one layer, harder to bypass two, and generally almost impossible to bypass 3 layers. The layers here would be:

  1. non-obvious webhook file name; ensure directory listings can’t be remotely sourced by creating an index.html file/disabling using webserver config. Remember the webhook name isn’t public.
  2. verifying Square signature on incoming POSTs
  3. 403 status code - makes it easy to track failures in logfiles if needed
  4. restricting webhook access to known Square IPs
  5. reporting/limiting verification failures (if not restricting by IP)
  6. solid checks on all data received by webhook - probably best not to even access incoming POST data unless verification is successful

Overall, I’d question putting too much engineering effort into this. If something important gets done as a result of webhook hits, you might use an API call to verify stuff - for instance, Get payment to verify the transaction exists, was successful, and was for the amount given, and send sme sort of alert if any of those aspects fails to validate.