From 97f8c0813c0014e82427e5d07c06698d19d09726 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 8 Jul 2022 21:10:51 +0200 Subject: [PATCH] fix: Use crypto.timingSafeEqual, closes #3740 --- server/routes/api/cron.ts | 8 +++++++- server/routes/api/hooks.ts | 33 ++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/server/routes/api/cron.ts b/server/routes/api/cron.ts index 340f5e72d..1d4a67b40 100644 --- a/server/routes/api/cron.ts +++ b/server/routes/api/cron.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import { Context } from "koa"; import Router from "koa-router"; import env from "@server/env"; @@ -13,7 +14,12 @@ const router = new Router(); const cronHandler = async (ctx: Context) => { const { token, limit = 500 } = ctx.body as { token?: string; limit: number }; - if (env.UTILS_SECRET !== token) { + if ( + !crypto.timingSafeEqual( + Buffer.from(env.UTILS_SECRET), + Buffer.from(String(token)) + ) + ) { throw AuthenticationError("Invalid secret token"); } diff --git a/server/routes/api/hooks.ts b/server/routes/api/hooks.ts index 0f3e79399..28622a034 100644 --- a/server/routes/api/hooks.ts +++ b/server/routes/api/hooks.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import Router from "koa-router"; import { escapeRegExp } from "lodash"; import env from "@server/env"; @@ -19,6 +20,23 @@ import { assertPresent } from "@server/validation"; const router = new Router(); +function verifySlackToken(token: string) { + if (!env.SLACK_VERIFICATION_TOKEN) { + throw AuthenticationError( + "SLACK_VERIFICATION_TOKEN is not present in environment" + ); + } + + if ( + !crypto.timingSafeEqual( + Buffer.from(env.SLACK_VERIFICATION_TOKEN), + Buffer.from(token) + ) + ) { + throw AuthenticationError("Invalid token"); + } +} + // triggered by a user posting a getoutline.com link in Slack router.post("hooks.unfurl", async (ctx) => { const { challenge, token, event } = ctx.body; @@ -26,9 +44,8 @@ router.post("hooks.unfurl", async (ctx) => { return (ctx.body = ctx.body.challenge); } - if (token !== env.SLACK_VERIFICATION_TOKEN) { - throw AuthenticationError("Invalid token"); - } + assertPresent(token, "token is required"); + verifySlackToken(token); const user = await User.findOne({ include: [ @@ -88,10 +105,7 @@ router.post("hooks.interactive", async (ctx) => { assertPresent(token, "token is required"); assertPresent(callback_id, "callback_id is required"); - - if (token !== env.SLACK_VERIFICATION_TOKEN) { - throw AuthenticationError("Invalid verification token"); - } + verifySlackToken(token); // we find the document based on the users teamId to ensure access const document = await Document.scope("withCollection").findByPk( @@ -125,10 +139,7 @@ router.post("hooks.slack", async (ctx) => { assertPresent(token, "token is required"); assertPresent(team_id, "team_id is required"); assertPresent(user_id, "user_id is required"); - - if (token !== env.SLACK_VERIFICATION_TOKEN) { - throw AuthenticationError("Invalid verification token"); - } + verifySlackToken(token); // Handle "help" command or no input if (text.trim() === "help" || !text.trim()) {