我正在facebook信使平台上开发一个聊天机器人。我浏览了Facebook的文档,找不到如何保护我的webhook
免受随机呼叫的影响。
例如,如果用户可以用我的机器人购买东西,那么知道某人userId的攻击者可以通过拨打我的webhook来开始下未经授权的订单。
关于如何保护这一点,我有几个想法。
- 将我的API白名单仅限于来自Facebook的呼叫
- 创造一些东西比如具有回发调用的CSRF令牌
有什么想法吗?
Facebook当然已经实现了一种机制,通过该机制,您可以检查对回调URL的请求是否真实(其他一切都只是他们的疏忽)-请参阅https://developers.facebook.com/docs/graph-api/webhooks/getting-started#validate-有效载荷
我们使用SHA256签名对所有事件通知有效载荷进行签名,并将签名包含在请求的
X-Hub-Signature-256
标头中,以sha256=
开头。您不必验证有效负载,但您应该验证。验证有效载荷:
- 使用有效负载和应用程序的应用程序机密生成SHA256签名
- 将您的签名与
X-Hub-Signature-256
标头中的签名(sha256=
之后的所有内容)进行比较。如果签名匹配,则有效载荷是真实的请注意,我们使用有效负载的转义unicode版本生成签名,该版本使用小写十六进制数字。如果你只是根据解码的字节进行计算,你最终会得到一个不同的签名。例如,字符串
äöå
应该转义为u00e4u00f6u00e5
。
除了CBroe的答案外,下面的片段还将签名验证实现表示为NestJS保护。
// 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) {
// ...
}