GitHub Webhook密钥从未验证

8
我正在使用GitHub的Webhook将事件引导到我的应用程序(GitHub Hubot的实例),并且它受到sha1密码的保护。
我是使用以下代码验证传入Webhook的哈希值:
crypto    = require('crypto')
signature = "sha1=" + crypto.createHmac('sha1', process.env.HUBOT_GITHUB_SECRET).update( new Buffer request.body ).digest('hex')
unless request.headers['x-hub-signature'] is signature
  response.send "Signature not valid"
  return

发送到 Webhook 的 X-Hub-Signature 标头如下所示:

X-Hub-Signature: sha1=1cffc5d4c77a3f696ecd9c19dbc2575d22ffebd4

按照 GitHub 的文档,我正确地传递了密钥和数据,但哈希值总是不同。

这是 GitHub 的文档。 https://developer.github.com/v3/repos/hooks/#example

我可能误解了以下部分:

secret: 一个可选字符串,作为 X-Hub-Signature 标头与 HTTP 请求一起传递。该标头的值被计算为 body 的 HMAC 十六进制摘要,使用密钥作为密钥。

有人能看出我的问题在哪里吗?

3个回答

9

似乎不能使用缓冲区,但可以使用JSON.stringify()函数。以下是我的可行代码:

var
  hmac,
  calculatedSignature,
  payload = req.body;

hmac = crypto.createHmac('sha1', config.github.secret);
hmac.update(JSON.stringify(payload));
calculatedSignature = 'sha1=' + hmac.digest('hex');

if (req.headers['x-hub-signature'] === calculatedSignature) {
  console.log('all good');
} else {
  console.log('not good');
}

2
非常重要的一点是,GitHub hookshot 必须 将其 Content-Type 设置为 application/json。您可以在 Webhook 配置页面上找到这些设置。https://github.com/MY_ORG/MY_REPO/settings/hooks/HOOK_ID#delivery-response - Michael Johansen
像 @MrClean 所说,使用 application/json! - Mattis

5

除了Patrick的答案,我建议使用Express和它的body-parser。下面是完整的示例。这适用于Express 4.x,Node 8.x(截至撰写本文时为最新版本)。

请替换YOUR_WEBHOOK_SECRET_HERE并在authorizationSuccessful函数中进行一些操作。

// Imports
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');

const app = express();
// The GitHub webhook MUST be configured to be sent as "application/json"
app.use(bodyParser.json());

// Verification function to check if it is actually GitHub who is POSTing here
const verifyGitHub = (req) => {
  if (!req.headers['user-agent'].includes('GitHub-Hookshot')) {
    return false;
  }
  // Compare their hmac signature to our hmac signature
  // (hmac = hash-based message authentication code)
  const theirSignature = req.headers['x-hub-signature'];
  const payload = JSON.stringify(req.body);
  const secret = 'YOUR_WEBHOOK_SECRET_HERE'; // TODO: Replace me
  const ourSignature = `sha1=${crypto.createHmac('sha1', secret).update(payload).digest('hex')}`;
  return crypto.timingSafeEqual(Buffer.from(theirSignature), Buffer.from(ourSignature));
};

const notAuthorized = (req, res) => {
  console.log('Someone who is NOT GitHub is calling, redirect them');
  res.redirect(301, '/'); // Redirect to domain root
};

const authorizationSuccessful = () => {
  console.log('GitHub is calling, do something here');
  // TODO: Do something here
};

app.post('*', (req, res) => {
  if (verifyGitHub(req)) {
    // GitHub calling
    authorizationSuccessful();
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Thanks GitHub <3');
  } else {
    // Someone else calling
    notAuthorized(req, res);
  }
});

app.all('*', notAuthorized); // Only webhook requests allowed at this address

app.listen(3000);

console.log('Webhook service running at http://localhost:3000');

注意:修复了 req.body 被重复字符串化的 bug。 - Michael Johansen

5

补充Patrick的答案。比较HMAC摘要或密钥值时,最好使用crypto.timingSafeEqual。以下是方法:

const blob = JSON.stringify(req.body);  
const hmac = crypto.createHmac('sha1', process.env.GITHUB_WEBHOOK_SECRET);
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`;

const theirSignature = req.get('X-Hub-Signature');

const bufferA = Buffer.from(ourSignature, 'utf8');
const bufferB = Buffer.from(theirSignature, 'utf8');

const safe = crypto.timingSafeEqual(bufferA, bufferB);

if (safe) {
  console.log('Valid signature');
} else {
  console.log('Invalid signature');
}

想要了解安全比较(如timingEqual)和简单的===检查之间的区别,请参阅此线程here

crypto.timingSafeEqual在Node.js v6.6.0中添加。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接