4

I am playing around with developing a chatbot on facebook messenger platform. I went through the Facebook document and couldn't find how to protect my webhook from random calls.

For example, if users can buy stuff with my bots, an attacker that knows someone's userId can start placing unauthorized orders by making calls to my webhook.

I have several ideas on how to protect this.

  1. Whitelist my API to only calls from Facebook.
  2. Create something like CSRF tokens with the postback calls.

Any ideas?

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
nate
  • 71
  • 5
  • Hi, how did you even access the Messenger apps setting page? It just opens up blank page for me when I click on the Messenger tab on an app settings page. – Anant Gupta Apr 14 '16 at 11:36
  • I don't remember how I did it exactly but i believe it shows you blank if you haven't setup the webhook and selected messages. Try following this [guide](https://developers.facebook.com/docs/messenger-platform/quickstart) – nate Apr 14 '16 at 13:02
  • I was following that only. In point #2:Setup Webhook, it says: "click on the "Messenger" product you just added". I'm not able to add the product in the first place. – Anant Gupta Apr 14 '16 at 13:37
  • Hey, I have the same problem. Although I created a webhook the Messenger page is still empty. I use the old dashboard and not the new one that is mentioned in the guide. – Oleksii Rudenko Apr 14 '16 at 20:09

2 Answers2

9

Facebook has of course already implemented a mechanism by which you can check if requests made to your callback URL are genuine (everything else would just be negligence on their part) – see https://developers.facebook.com/docs/graph-api/webhooks/getting-started#validate-payloads

We sign all Event Notification payloads with a SHA256 signature and include the signature in the request's X-Hub-Signature-256 header, preceded with sha256=. You don't have to validate the payload, but you should.

To validate the payload:

  1. Generate a SHA256 signature using the payload and your app's App Secret.
  2. Compare your signature to the signature in the X-Hub-Signature-256 header (everything after sha256=). If the signatures match, the payload is genuine.

Please note that we generate the signature using an escaped unicode version of the payload, with lowercase hex digits. If you just calculate against the decoded bytes, you will end up with a different signature. For example, the string äöå should be escaped to \u00e4\u00f6\u00e5.

Jarett Millard
  • 5,802
  • 4
  • 41
  • 48
CBroe
  • 91,630
  • 14
  • 92
  • 150
  • Cool! I will look into this. Thanks! – nate Apr 14 '16 at 12:56
  • @nate Did you find out a way to do this? I'm having trouble getting my signature to match what Facebook is sending. – Talon876 May 15 '16 at 20:49
  • @CBroe is the X-Hub-Signature not only sent with GET requests? Looking at Heroku and Parse's code (in the examples), it seems the POST requests are left unverified. How does this stop someone from posing to be a user, as nate asked? – forallepsilon May 17 '16 at 21:40
  • @forallepsilon: No, it should work the same way for POST requests. Updates are always send via POST, so it would make no sense to leave out the verification/signature in this case. – CBroe May 18 '16 at 07:38
  • @CBroe I figured this was the case but cannot seem to find the signature being sent with post requests. Can you give me a small code sample of how to verify it? Much appreciated. – forallepsilon May 19 '16 at 20:45
  • Well if you can't even "find" the signature, then what do you want to verify ....? Suggest you make a debug output of all the HTTP request headers first, and check what you really got in there. – CBroe May 19 '16 at 20:48
  • @CBroe As an example, https://github.com/fbsamples/graph-api-webhooks-samples/blob/master/heroku/index.js only verifies on GET requests. Should it not be done on POST as well? – forallepsilon May 19 '16 at 23:33
  • I don’t see any validation in that script at all. But anyway, yes, you should validate it on the incoming POST requests as well, if you want to be sure the data is genuine. – CBroe May 20 '16 at 07:13
0

In addition to CBroe's answer, the snippet below represents signature verification implementation as NestJS guard.

// src/common/guards/signature-verification.guard.ts
@Injectable()
export class SignatureVerificationGuard implements CanActivate {
  constructor(private readonly configService: ConfigService) {}

  canActivate(context: ExecutionContext): boolean {
    const {
      rawBody,
      headers: { 'x-hub-signature': signature },
    } = context.switchToHttp().getRequest();
    const { sha1 } = parse(signature);
    if (!sha1) return false;

    const appSecret = this.configService.get('MESSENGER_APP_SECRET');
    const digest = createHmac('sha1', appSecret).update(rawBody).digest('hex');
    const hashBufferFromBody = Buffer.from(`sha1=${digest}`, 'utf-8');
    const bufferFromSignature = Buffer.from(signature, 'utf-8');

    if (hashBufferFromBody.length !== bufferFromSignature.length)
      return false;

    return timingSafeEqual(hashBufferFromBody, bufferFromSignature);
  }
}
// src/modules/webhook/webhook.controller.ts
@UseGuards(SignatureVerificationGuard)
@Post()
@HttpCode(HttpStatus.OK)
handleWebhook(@Body() data) {
  // ...
}
Željko Šević
  • 3,743
  • 2
  • 26
  • 23