JSON in webhook signature missmatch

I have…

  • [ ] Checked the logs and uploaded a log file and provided a link because I found something suspicious there. Please do not post the log file in the topic because very often something important is missing.

I’m submitting a…

  • [x] Regression (a behavior that stopped working in a new release)
  • [ ] Bug report
  • [ ] Performance issue
  • [ ] Documentation issue or request

Current behavior

Starting from yesterday, something has changed in webhook contents. Most noticeable thing is that the body JSON is coming in a formatted way and contain newlines. Because of this and maybe other changes, the request signature validation is broken.

Expected behavior

The JSON body signature should match the body.

Minimal reproduction of the problem

Environment

  • [ ] Self hosted with docker
  • [ ] Self hosted with IIS
  • [ ] Self hosted with other version
  • [x] Cloud version

Version: [VERSION]

Browser:

  • [ ] Chrome (desktop)
  • [ ] Chrome (Android)
  • [ ] Chrome (iOS)
  • [ ] Firefox
  • [ ] Safari (desktop)
  • [ ] Safari (iOS)
  • [ ] IE
  • [ ] Edge

Others:

Yes, there was a deployment, where the serializer was replaced, but I don’t see how this can affect the signature.

It should not really matter how the body is formatted, when the signature is calculated. This is how the signature is calculated: https://github.com/Squidex/squidex/blob/master/backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs#L44

It is based on the same JSON string that is also send as a request.

Yes, we are doing the same logic for constructing the signature. Nothing has changed on our side there for more than 3 years.
Here is how we were always doing it in Nodejs:

let signature = crypto.createHash('sha256').update(JSON.stringify(req.body) + process.env.SQUIDEX_SECRET).digest('base64');

In fact, when trying to calculate the hash manually in a separate script it also does not match with the one coming with the request.

Is it possible that after generating the signature the actual response body is somehow altered before being sent?

Yes, but this is the problem.

You are

  1. Deserializing from string to object
  2. Serializing again to a string
  3. Then you calculate the hash.

The problem is, that you are very dependent that the serializer works exactly the same all the time on the byte level.

You should

  1. Take the response string and calculate the hash
  2. Then return 400 for mismatch
  3. Then deserialize the string.

Well maybe, but since yesterday the (de)serialization has worked the same way each time for 3 years :slight_smile: Something has definitely changed…

Yes, It is partly my fault, I was not thinking so far when I changed it, but your implementation is also not very robust :wink:

Actually we tried to provide the JSON manually as it is seen in the Rule logs with the same formatting, so that there is no request body parser applied to it. Also tried with raw data, and still the string is not matching the signature. My guess is that webhook sends slightly different data in the body than it uses during signature calculation.
Turning off the validation right now, as no solution found.

Have you changed the order or are you still doing things in the same order? I will see, whether I can cover it in my integration tests, perhaps I will find an issue.

1 Like

It works fine in the tests for me.

The tests do the following:

  1. Create a webhook to push to a webhook tester.
  2. Trigger the webhook
  3. Wait for the request to arrive
  4. Check the signature

It uses the following method: https://github.com/Squidex/squidex-samples/blob/3e4b2e87829e03f4565a037952d293299f5f79bc/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/WebhookUtils.cs

@mappr Sebastian is right, it would be more stable to use the response string directly. You can do so by using req.rawBody.toString() if you are using NodeJS. Usually, the bodyParser turns it into a buffer, therefore you need to use the toString() method. This makes the hash calculation working again.

1 Like

You can also add a custom header to webhooks. You could just add a secret string there.