Skip to content

Commit

Permalink
Add server side validation for uploaded file types (elastic#173960)
Browse files Browse the repository at this point in the history
## 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>
  • Loading branch information
2 people authored and CoenWarmer committed Feb 15, 2024
1 parent 0c17f33 commit 066cba2
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 3 deletions.
5 changes: 5 additions & 0 deletions x-pack/plugins/security/common/constants.ts
Expand Up @@ -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'];
Expand Up @@ -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,
Expand Down
Expand Up @@ -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) {
Expand Down
22 changes: 22 additions & 0 deletions x-pack/plugins/security/server/routes/user_profile/update.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down
Expand Up @@ -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);
Expand Down Expand Up @@ -160,7 +164,7 @@ export default ({ getService }: FtrProviderContext): void => {
req: {
initials: 'ES',
color: '#6092C0',
imageUrl: 'my-image',
imageUrl: IMAGE_URL_TEST,
},
headers: superUserHeaders,
});
Expand Down

0 comments on commit 066cba2

Please sign in to comment.