From 066cba2d0067d1421e676818bbbe6189043f2c61 Mon Sep 17 00:00:00 2001 From: Sid Date: Tue, 30 Jan 2024 10:29:14 +0100 Subject: [PATCH] Add server side validation for uploaded file types (#173960) ## Summary Closes https://github.com/elastic/security/issues/1839 ## Fixes - Introduced a MIME type check for the server endpoint for image upload. The check runs against the same allowed file types for the UI and returns an error if not matched. ### Tesing 1. Use the `POST kbn://internal/security/user_profile/_data` endpoint with a body as follows (substituting your own base64 image string): ``` { "avatar": { "imageUrl": "[image as base64 encoded string]" } } ``` 2. The beginning of the base64 string should look something like "data:image/png;base64,...", where 'png' is the image format. Verify that all supported image formats work and the API returns 200 for each. 3. In the base64 string, change the image format (e.g. 'png') to a non-supported format (e.g. 'gnp') and verify a 415 "Unsupported Media Type" error occurs. 4. In the base64 string, change the "data:image/png;base64" to "data:file/pdf;base64" and verify a 415 "Unsupported Media Type" error occurs. ## Release notes --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/security/common/constants.ts | 5 +++++ .../user_profile/user_profile.tsx | 3 ++- .../account_management/user_profile/utils.ts | 2 +- .../server/routes/user_profile/update.ts | 22 +++++++++++++++++++ .../common/internal/user_actions_get_users.ts | 6 ++++- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 48a020210596560..aab0c9d6c438b11 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -92,3 +92,8 @@ export const SESSION_EXTENSION_THROTTLE_MS = 60 * 1000; * Route to get session info and extend session expiration */ export const SESSION_ROUTE = '/internal/security/session'; + +/** + * Allowed image file types for uploading an image as avatar + */ +export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif']; diff --git a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx index 04ac8cc4fcfe9af..601e13c8bb16e10 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx +++ b/x-pack/plugins/security/public/account_management/user_profile/user_profile.tsx @@ -42,8 +42,9 @@ import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import type { DarkModeValue, UserProfileData } from '@kbn/user-profile-components'; import { UserAvatar, useUpdateUserProfile } from '@kbn/user-profile-components'; -import { createImageHandler, getRandomColor, IMAGE_FILE_TYPES, VALID_HEX_COLOR } from './utils'; +import { createImageHandler, getRandomColor, VALID_HEX_COLOR } from './utils'; import type { AuthenticatedUser } from '../../../common'; +import { IMAGE_FILE_TYPES } from '../../../common/constants'; import { canUserChangeDetails, canUserChangePassword, diff --git a/x-pack/plugins/security/public/account_management/user_profile/utils.ts b/x-pack/plugins/security/public/account_management/user_profile/utils.ts index fd1535028849305..2621ba0b65f1fd9 100644 --- a/x-pack/plugins/security/public/account_management/user_profile/utils.ts +++ b/x-pack/plugins/security/public/account_management/user_profile/utils.ts @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { IMAGE_FILE_TYPES } from '../../../common/constants'; -export const IMAGE_FILE_TYPES = ['image/svg+xml', 'image/jpeg', 'image/png', 'image/gif']; export const MAX_IMAGE_SIZE = 64; export function readFile(data: File) { diff --git a/x-pack/plugins/security/server/routes/user_profile/update.ts b/x-pack/plugins/security/server/routes/user_profile/update.ts index 205f6a6d68a4a35..9a550ada52adc18 100644 --- a/x-pack/plugins/security/server/routes/user_profile/update.ts +++ b/x-pack/plugins/security/server/routes/user_profile/update.ts @@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema'; import type { RouteDefinitionParams } from '..'; +import { IMAGE_FILE_TYPES } from '../../../common/constants'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { flattenObject } from '../../lib'; import { getPrintableSessionId } from '../../session_management'; @@ -47,7 +48,28 @@ export function defineUpdateUserProfileDataRoute({ } const currentUser = getAuthenticationService().getCurrentUser(request); + const userProfileData = request.body; + const imageDataUrl = userProfileData.avatar?.imageUrl; + if (imageDataUrl && typeof imageDataUrl === 'string') { + const matches = imageDataUrl.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + if (!matches || matches.length !== 3) { + return response.customError({ + body: 'Unsupported media type', + statusCode: 415, + }); + } + + const [, mimeType] = matches; + + if (!IMAGE_FILE_TYPES.includes(mimeType)) { + return response.customError({ + body: 'Unsupported media type', + statusCode: 415, + }); + } + } + const keysToUpdate = Object.keys(flattenObject(userProfileData)); if (currentUser?.elastic_cloud_user) { diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts index e49c48fbf2347e6..c1edd4280be85e6 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/internal/user_actions_get_users.ts @@ -43,6 +43,10 @@ export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const kibanaServer = getService('kibanaServer'); + // Use simple image data URL to match server side validation of image type + const IMAGE_URL_TEST = + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAIBJREFUeF7t14ERACAMAjG6/9A6yMcROEuOe9tb+J0A/AAnoAPCHTglSAEKUIACFAgngEEMYhCDGAwjYAxhEIMYxCAGMRhOAIMYxCAGMRhGwBrEIAYxiEEMYjCcAAYxiEEMYjCMgDWIQQxiEIMYxGA4AQxiEIMYxGAYAWsQg3UGPw2Yf8EFsz4JAAAAAElFTkSuQmCC'; + describe('user_actions_get_users', () => { afterEach(async () => { await deleteAllCaseItems(es); @@ -160,7 +164,7 @@ export default ({ getService }: FtrProviderContext): void => { req: { initials: 'ES', color: '#6092C0', - imageUrl: 'my-image', + imageUrl: IMAGE_URL_TEST, }, headers: superUserHeaders, });