CVE-2023-28443 (TypeScript, published 2023-03-24T00:15Z)
Description: value: Directus is a real-time API and App dashboard for managing SQL database content. Prior to version 9.23.3, the `directus_refresh_token` is not redacted properly from the log outputs and can be used to impersonate users without their permission. This issue is patched in version 9.23.3.
Severity: nan (CVSS v3 base score: 5.5)
Weakness: CWE-284 \u2014 Improper Access Control
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
Repository: https://github.com/directus/directus
Files changed:
api/src/logger.ts
api/src/utils/redact-header-cookies.test.ts
api/src/utils/redact-header-cookies.ts
Commit message: Merge pull request from GHSA-8vg2-wf3q-mwv7

* fix(api): redact header cookie

This is a quick PoC for a fix. I am not sure if it's the best answer and have not added tests yet. If we feel good about it, I can add tests and open a PR. Please let me know how you'd like to proceed!

* cleaner

* rework to handle multiple inputs and add unit tests

* Added same redacting logic for teh response set-cookie

---------

Co-authored-by: Brainslug <tim@brainslug.nl>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>

Vulnerable code:
import type { Request, RequestHandler } from 'express';
import pino, { LoggerOptions } from 'pino';

Fixed code:
import pino, { LoggerOptions, SerializedResponse } from 'pino';
import type { Request, RequestHandler } from 'express';
import { redactHeaderCookie } from './utils/redact-header-cookies';
			if (output.headers?.cookie) {
				output.headers.cookie = redactHeaderCookie(output.headers.cookie, [
					'access_token',
					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
				]);
			}
		res(response: SerializedResponse) {
			if (response.headers?.['set-cookie']) {
				response.headers['set-cookie'] = redactHeaderCookie(response.headers['set-cookie'], [
					'access_token',
					`${env.REFRESH_TOKEN_COOKIE_NAME}`,
				]);
			}
			return response;
		},
import { describe, expect, test } from 'vitest';
import env from '../env';
import { redactHeaderCookie } from './redact-header-cookies';

describe('redactHeaderCookie', () => {
	describe('Given auth cookies', () => {
		test('When it finds a refresh_token, it should redact the value', () => {
			const tokenKey = env.REFRESH_TOKEN_COOKIE_NAME;
			const cookieHeader = `${tokenKey}=shh;`;
			const cookieNames = [`${tokenKey}`];

			const redactedCookie = redactHeaderCookie(cookieHeader, cookieNames);
			expect(redactedCookie).toBe(`${tokenKey}=--redacted--;`);
		});
		test('When it finds an access_token, it should redact the value', () => {
			const tokenKey = 'access_token';
			const cookieHeader = `${tokenKey}=secret;`;
			const cookieNames = [`${tokenKey}`];

			const redactedCookie = redactHeaderCookie(cookieHeader, cookieNames);
			expect(redactedCookie).toBe(`${tokenKey}=--redacted--;`);
		});
		test('When it finds both an access_token and refresh_token, it should redact both values', () => {
			const cookieHeader = `access_token=secret; ${env.REFRESH_TOKEN_COOKIE_NAME}=shhhhhhh; randomCookie=Erdtree;`;
			const cookieNames = ['access_token', `${env.REFRESH_TOKEN_COOKIE_NAME}`];

			const redactedCookie = redactHeaderCookie(cookieHeader, cookieNames);
			expect(redactedCookie).toBe(
				`access_token=--redacted--; ${env.REFRESH_TOKEN_COOKIE_NAME}=--redacted--; randomCookie=Erdtree;`
			);
		});
	});
	describe('Given negligible cookies', () => {
		test('It should return the orignal value', () => {
			const originalCookie = `Crown=Swords; Hail=Sithis;`;
			const cookieNames = [env.REFRESH_TOKEN_COOKIE_NAME, 'access_token'];

			const redactedCookie = redactHeaderCookie(originalCookie, cookieNames);
			expect(redactedCookie).toBe(originalCookie);
		});
	});
});
export function redactHeaderCookie(cookieHeader: string, cookieNames: string[]) {
	for (const cookieName of cookieNames) {
		const re = new RegExp(`(${cookieName}=)([^;]+)`);
		cookieHeader = cookieHeader.replace(re, `$1--redacted--`);
	}
	return cookieHeader;
}