【问题标题】:GitHub Webhook Secret Never ValidatesGitHub Webhook Secret 从不验证
【发布时间】:2014-09-10 14:17:29
【问题描述】:

我正在使用 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-签名:sha1=1cffc5d4c77a3f696ecd9c19dbc2575d22ffebd4

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

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

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

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

谁能看出我哪里出错了?

【问题讨论】:

    标签: node.js git github cryptography github-api


    【解决方案1】:

    似乎不适用于缓冲区,但 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');
    }
    

    【讨论】:

    • 非常重要的一点是,GitHub 钩子必须将其 Content-Type 设置为 application/json。您将在 webhook 配置页面上找到这些设置。 github.com/MY_ORG/MY_REPO/settings/hooks/…
    • 像@MrClean 状态,使用application/json!
    【解决方案2】:

    添加到Patrick's 答案。最好使用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');
    }
    

    要了解更多关于安全比较(如 timingEqual)和简单 === 之间的区别,请查看此线程 here

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

    【讨论】:

      【解决方案3】:

      除了 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 被字符串化两次的错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-31
      • 2023-02-09
      • 1970-01-01
      • 2019-04-14
      • 1970-01-01
      • 2014-05-25
      • 2015-11-14
      相关资源
      最近更新 更多