GitHub Webhook秘密永远不会验证

luk*_*ans 8 git cryptography github node.js github-api

我正在使用GitHub webhook将事件传递给我的应用程序(GitHub的Hubot的一个实例),并使用sha1秘密进行保护.

我正在使用以下代码验证传入webhooks上的哈希值

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
Run Code Online (Sandbox Code Playgroud)

在webhook中传递的X-Hub-Signature标头看起来像这样

X-Hub-Signature:sha1 = 1cffc5d4c77a3f696ecd9c19dbc2575d22ffebd4

我按照GitHub的文档准确传递密钥和数据,但哈希总是不同.

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

这是我最有可能误解的部分

secret:作为X-Hub-Signature标头与HTTP请求一起传递的可选字符串.使用秘密作为密钥,将此标头的值计算为正文的HMAC十六进制摘要.

谁能看到我哪里出错了?

Pat*_*ick 9

似乎无法使用Buffer,但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');
}
Run Code Online (Sandbox Code Playgroud)

  • 非常重要的是要提到GitHub hookshot*必须*将它的Content-Type设置为`application/json`.您可以在webhook配置页面上找到这些设置.https://github.com/MY_ORG/MY_REPO/settings/hooks/HOOK_ID#delivery-response (2认同)

Sau*_*rya 5

添加帕特里克的答案。最好使用crypto.timingSafeEqual来比较 HMAC 摘要或秘密值。就是这样:

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');
}
Run Code Online (Sandbox Code Playgroud)

要了解有关 TimingEqual 等安全比较和简单 === 之间的区别的更多信息,请查看此处的此线程。

Node.js v6.6.0 中添加了crypto.timingSafeEqual


Mic*_*sen 5

另外,我建议将 Express 与其主体解析器一起使用,以补充帕特里克的答案。下面是完整的示例。这适用于 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');
Run Code Online (Sandbox Code Playgroud)