验证Webhook签名的重要性
#javascript #node #opensourcesecurity #codesecurity

Webhooks是一种用于接近实时的信息,例如,用于发送和接收信息(例如事件通知)的回调集成技术。可以通过应用程序事件触发Webhooks,并通过HTTP将数据传输到另一个应用程序或第三方API。您可以配置Webhook URL并将外部参与者连接以自定义,扩展或修改工作流程。

Webhooks可能会或可能不会签署。但是,出于安全目的,最好的做法是包括可验证的签名,以便听众可以确认请求来自预期的Webhook源。

以下是今天如何使用Webhooks的一些示例:

  • 开发人员将代码推向存储库时,基于GIT的版本控制平台上的通知。
  • 通知用户在消息平台上回复消息线程时。
  • 付款方式被授权用于零售购买时确认付款服务的通知。

根据Verizon报告,供应链攻击占2022年所有系统入侵事件的62% 。如果利用了漏洞,它们可能会损害涉及的合作伙伴并破坏整个软件供应链。

要应对这些威胁并维持供应链安全,供应链供应商执行Webhook签名。例如,使用CircleCi,您可以使用webhook签名来验证秘密,并确保只有Circleci且不恶意演员称呼您的Webhook。 Travis CI在其Webhook验证过程中还提供了signature HTTP header

在本演练中,我们将在Node.js中实现一个github webhook,该Webhook检测到用户将代码推向存储库时。

先决条件

要跟随,您将需要执行以下操作:

  • 下载并安装Node.js and npm
  • 安装ngrok,一种反向代理工具,可以打开从公共URL到本地node.js应用程序的安全隧道。
  • 安装Postman,一个平台,允许您创建HTTP请求并轻松检查结果。

使用node.js验证webhook签名

在您的本地计算机中创建一个新文件夹和一个名为webhook.js的新文件,其中包含以下代码。切记用本地存储库路径替换repo常数的值:

const http = require('http');
const exec = require('child_process').exec;

const repo = "C:\\Users\\your-user-name\\Documents\\GitHub\\webhook-test";

http
  .createServer((req, res) => {
    req.on('data', chunk => {

      const body = JSON.parse(chunk);

      console.log(body);

      const isMain = body?.ref === 'refs/heads/main';

      if (isMain) {
        try {
            console.log('Push event detected. Pulling repository updates...');
            exec(`cd ${repo} && git fetch && git pull`);
            console.log('Git repository pulled successfully.');
        } catch (error) {
          console.log(error);
        }
      }
      res.end();
    });
  })
  .listen(8080);

现在,在您的终端中执行以下行:

node webhook.js

这是我们尝试用webhook.js文件实现的目标:

  1. repo常数存储了包含我们要观看的存储库的文件夹的路径。
  2. createServer函数在我们的计算机端口8080中启动了HTTP服务器。Webhook应用现在正在运行并准备在http://localhost:8080端点接收请求。
  3. 一旦处理传入请求,车身常数将存储大部分请求。向我们的端点的请求意味着,我们在http://localhost:8080上收到了Github Webhook到本地有效载荷URL的通知,告知我们一些用户已将代码推向我们重新观看的存储库。
  4. 我们要确保将推动推向主要分支。为了验证这一点,我们将请求JSON数据解析到身体对象中,并使用isMain常数指示参考是否匹配主分支。
  5. 如果接受请求,我们将执行一个命令以更改为本地存储库。最后,我们执行git fetchgit pull命令对其进行更新。

与Postman模拟恶意请求

我们设计了node.js应用程序以与github合作。但是,在使用GitHub之前,让我们与Postman模拟恶意请求,以显示我们的应用程序有多脆弱。这将帮助您了解签名的重要性。

打开Postman应用程序并将新的HTTP POST请求配置为我们本地有效载荷URL http://localhost:8080

正文选项卡中,添加以下代码:

{
    "ref": "refs/heads/main",
    "comment": "hi, I'm a malicious request..."
}


点击发送。打开您的端子。


如您所见,Node.js应用程序正在接受GitHub以外的其他来源的请求,这很危险。我们需要在代码中实现Webhook签名,以便我们可以验证请求者是否合法。

配置NGrok

在这种情况下,我们在本地运行Node.js应用程序。但是,GitHub无法在计算机上访问http://localhost:8080端点来发送Webhook请求。我们可以通过在公共服务器上发布node.js应用来解决。但是,开发计算机上的运行和调试Node.js比将其部署到Web服务器或云更容易,因此我们将使用ngrok。 Ngrok是一种反向代理工具,可将安全隧道从随机的公共URL打开到您的本地Webhook端点。

打开机器上的终端窗口并执行以下NGrok命令以公开本地端口8080:

ngrok http 8080

ngrok将通过带有随机公共URL的安全隧道来确认它已经暴露了您的本地8080端口:

Forwarding https://<>.sa.ngrok.io -> http://localhost:8080

接下来,我们将使用上面的公共Ngrok子域来配置GitHub Webhook。

配置github webhook

访问github的create a new repository页面,添加一个新存储库来配置webhook:


单击顶部栏上的设置。然后,在左列表上选择 Webhooks 操作,然后单击添加webhook


这将打开添加webhook 表单,我们将为开发人员将代码推向存储库的事件配置Webhook。


现在,填写表格如下:

  • 有效载荷URL 字段中,输入与本地node.js应用程序端点的NGrok地址。
  • 内容类型字段中,选择“ application/json”以表明这是github将发送到端点的内容类型。
  • Secret 字段中,键入“ Some-Webhook-Secret”。请记住,在生产中,您需要更强大的密码或短语来执行安全性。
  • 下,您想触发此Webhook ,选择只是推动事件。这将设置GitHub Webhook以进行存储库推动操作,您可以触发以测试验证。

注意:始终建议在Webhook配置中启用安全套接字层(SSL)验证。通过启用SSL验证,组织确保系统之间传输的数据是安全的,并且无法被未经授权的各方拦截。

填写表格后,单击添加Webhook

确保Webhook

我们需要添加所需的node.js加密模块来处理加密数据。在webhook.js文件的第一个块中添加此行:

const crypto = require('crypto');

下一

const secret = 'some-webhook-secret';

注意:永远不要为您的秘密进行硬编码。此方法仅用于演示目的。

req.on函数的开头,我们声明一个常数,以保留使用HMAC创建的计算出的SHA256签名的值。通过在共享秘密密钥上运行加密sha256哈希函数来获取HMAC-SHA256签名,并通过github请求传递的值(由chunk参数表示):

    req.on('data', chunk => {

      const hashAlgorithm = 'sha256'
      const signature = Buffer.from(req.headers['x-hub-signature-256'] || '', 'utf8')
      const hmac = crypto.createHmac(hashAlgorithm, secret)
      const digest = Buffer.from(hashAlgorithm + '=' + hmac.update(chunk).digest('hex'), 'utf8')

接下来,我们声明req.on事件内的isAllowed常数,以指示x hub-signature-256标头是否匹配我们计算的签名。使用crypto.timingSafeEqual函数,该功能比较两个变量而不揭示可以帮助攻击者猜测的一个值之一:

      const isAllowed = (signature.length === digest.length && crypto.timingSafeEqual(digest, signature));
      if (!isAllowed) {
        const msg = 'Webhook signature does not match the hash of the payload';
        console.log(msg);
        res.writeHead(401)
        res.end(msg)
        return;
      }

在上面的代码中,匹配意味着我们可以放心,请求是来自github而不是恶意演员。如果请求不来自GitHub,则返回HTTP 401未经授权的响应。

以下是此代码中使用的一些最佳实践,在开发网络钩时应实现这些方法:

  • 执行字符串操作计算Webhook签名时,请务必将有效载荷作为UTF-8处理,因为Webhook有效负载可能包含Unicode字符。此外,如果您对数据的出现期望,则可以实施验证方法,以确保收到的数据如预期。
  • 某些Webhook提供商(例如GitHub)可能在请求中包含x-hub-signature标头。 x-hub-signature标头是使用SHA-1生成的,SHA-1被认为是弱的哈希算法,但仅包括向后兼容。只要有可能,请使用使用强SHA-256算法生成的x-hub-signature-256标头。
  • 使用请求有效负载主体的秘密开始使用"sha256="启动哈希签名。
  • 而不是使用普通的平等运算符("==""==="),而是使用一种安全的比较方法来减少timing attacks的机会。所有现代语言和平台至少具有这些安全方法之一。在此node.js项目中,我们使用了crypto.timingSafeEqual函数。

您可以抓住完整的webhook.js code here

有关更多详细信息,请参阅有关固定Webhooks的GitHub documentation

测试安全的Webhook端点

由于修改了代码,因此应重新启动本地节点服务器。在您的控制台中,使用Ctrl+C(或Mac中的命令+C)停止节点服务器,然后再次执行node webhook.js命令。

然后,返回邮递员应用并重新发送请求。现在,Node.js应用程序以HTTP 401未经授权的代码响应:


打开github并添加一个readme.md文件,如果您已经尚未,或者如果有的话,请打开它。然后添加或修改文本如下:


最后,单击提交更改将更改推向存储库的主要分支:


现在,返回到您的node.js终端,确认存储库已成功获取和拉动:

结论

Webhooks允许在工具之间进行通信,并在事件发生时提供实时数据。您可以将它们集成到系统中以创建灵活而扩展的协作工作流程,这是将API与预定义参数一起使用时的可能性。

因为它们是按设计开放的,所以网络钩子容易受到安全威胁的影响,因此必须采取适当措施来防止供应链攻击。

Webhook签名是推荐的解决方案,因为Webhook提供商可以使用强大的哈希算法配置秘密并生成签名。签名是一种简单且安全的方法,可以确保网络钩之间的数据通信仅在经过验证和受信任的合作伙伴之间发生。