Skip to content

Commit

Permalink
[Beats Management] Prevent timing attacks when checking auth tokens (#…
Browse files Browse the repository at this point in the history
…19363)

* Using crypto.timingSafeEqual() for comparing auth tokens

* Prevent subtler timing attack in token comparison function

* Introduce random delay after we try to find token in ES to mitigate timing attack

* Remove random delay
  • Loading branch information
ycombinator authored and mattapperson committed Aug 14, 2018
1 parent 32beb20 commit 41ff71b
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 2 deletions.
21 changes: 21 additions & 0 deletions x-pack/plugins/beats/server/lib/crypto/are_tokens_equal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { timingSafeEqual } from 'crypto';

const RANDOM_TOKEN_1 = 'b48c4bda384a40cb91c6eb9b8849e77f';
const RANDOM_TOKEN_2 = '80a3819e3cd64f4399f1d4886be7a08b';

export function areTokensEqual(token1, token2) {
if ((typeof token1 !== 'string') || (typeof token2 !== 'string') || (token1.length !== token2.length)) {
// This prevents a more subtle timing attack where we know already the tokens aren't going to
// match but still we don't return fast. Instead we compare two pre-generated random tokens using
// the same comparison algorithm that we would use to compare two equal-length tokens.
return timingSafeEqual(Buffer.from(RANDOM_TOKEN_1, 'utf8'), Buffer.from(RANDOM_TOKEN_2, 'utf8'));
}

return timingSafeEqual(Buffer.from(token1, 'utf8'), Buffer.from(token2, 'utf8'));
}
7 changes: 7 additions & 0 deletions x-pack/plugins/beats/server/lib/crypto/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { areTokensEqual } from './are_tokens_equal';
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function registerEnrollBeatRoute(server) {
try {
const enrollmentToken = request.headers['kbn-beats-enrollment-token'];
const { token, expires_on: expiresOn } = await getEnrollmentToken(callWithInternalUser, enrollmentToken);
if (!token || token !== enrollmentToken) {
if (!token) {
return reply({ message: 'Invalid enrollment token' }).code(400);
}
if (moment(expiresOn).isBefore(moment())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { get } from 'lodash';
import { INDEX_NAMES } from '../../../common/constants';
import { callWithInternalUserFactory } from '../../lib/client';
import { wrapEsError } from '../../lib/error_wrappers';
import { areTokensEqual } from '../../lib/crypto';

async function getBeat(callWithInternalUser, beatId) {
const params = {
Expand Down Expand Up @@ -74,7 +75,7 @@ export function registerUpdateBeatRoute(server) {
return reply({ message: 'Beat not found' }).code(404);
}

const isAccessTokenValid = beat.access_token === request.headers['kbn-beats-access-token'];
const isAccessTokenValid = areTokensEqual(beat.access_token, request.headers['kbn-beats-access-token']);
if (!isAccessTokenValid) {
return reply({ message: 'Invalid access token' }).code(401);
}
Expand Down

0 comments on commit 41ff71b

Please sign in to comment.