From 276644eb3ca8923985e9f86ac09bab3a0a2bfad5 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Fri, 17 May 2024 20:15:24 +0200 Subject: [PATCH 01/10] Add sanitization process to SVG files --- .../controllers/wazuh-utils/wazuh-utils.ts | 280 ++++++++++++------ plugins/main/server/lib/sanitizer.ts | 10 + plugins/main/server/plugin.ts | 133 ++++++--- plugins/main/server/start/index.ts | 3 +- .../start/sanitize-uploaded-files/index.ts | 7 + .../sanitize-svg.test.ts | 1 + .../sanitize-uploaded-files/sanitize-svg.ts | 94 ++++++ 7 files changed, 406 insertions(+), 122 deletions(-) create mode 100644 plugins/main/server/lib/sanitizer.ts create mode 100644 plugins/main/server/start/sanitize-uploaded-files/index.ts create mode 100644 plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.test.ts create mode 100644 plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.ts diff --git a/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts b/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts index dfaba3b656..be5a566ee9 100644 --- a/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts +++ b/plugins/main/server/controllers/wazuh-utils/wazuh-utils.ts @@ -16,9 +16,17 @@ import { getConfiguration } from '../../lib/get-configuration'; import { read } from 'read-last-lines'; import { UpdateConfigurationFile } from '../../lib/update-configuration'; import jwtDecode from 'jwt-decode'; -import { WAZUH_ROLE_ADMINISTRATOR_ID, WAZUH_DATA_LOGS_RAW_PATH, PLUGIN_SETTINGS } from '../../../common/constants'; +import { + WAZUH_ROLE_ADMINISTRATOR_ID, + WAZUH_DATA_LOGS_RAW_PATH, + PLUGIN_SETTINGS, +} from '../../../common/constants'; import { ManageHosts } from '../../lib/manage-hosts'; -import { OpenSearchDashboardsRequest, RequestHandlerContext, OpenSearchDashboardsResponseFactory } from 'src/core/server'; +import { + OpenSearchDashboardsRequest, + RequestHandlerContext, + OpenSearchDashboardsResponseFactory, +} from 'src/core/server'; import { getCookieValueByName } from '../../lib/cookie'; import fs from 'fs'; import path from 'path'; @@ -26,6 +34,8 @@ import { createDirectoryIfNotExists } from '../../lib/filesystem'; import glob from 'glob'; import { getFileExtensionFromBuffer } from '../../../common/services/file-extension'; +import { sanitizeSVG } from '../../lib/sanitizer'; + const updateConfigurationFile = new UpdateConfigurationFile(); // TODO: these controllers have no logs. We should include them. @@ -45,7 +55,11 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Object} Configuration File or ErrorResponse */ - getConfigurationFile(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + getConfigurationFile( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { const configFile = getConfiguration(); @@ -53,8 +67,8 @@ export class WazuhUtilsCtrl { body: { statusCode: 200, error: 0, - data: configFile || {} - } + data: configFile || {}, + }, }); } catch (error) { return ErrorResponse(error.message || error, 3019, 500, response); @@ -68,35 +82,74 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Object} Configuration File or ErrorResponse */ - updateConfigurationFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) => { - - let requiresRunningHealthCheck: boolean = false, - requiresReloadingBrowserTab: boolean = false, - requiresRestartingPluginPlatform: boolean = false; - - // Plugin settings configurables in the configuration file. - const pluginSettingsConfigurableFile = Object.keys(request.body) - .filter(pluginSettingKey => PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile) - .reduce((accum, pluginSettingKey: string) => ({ ...accum, [pluginSettingKey]: request.body[pluginSettingKey] }), {}); + updateConfigurationFile = + this.routeDecoratorProtectedAdministratorRoleValidToken( + async ( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) => { + let requiresRunningHealthCheck: boolean = false, + requiresReloadingBrowserTab: boolean = false, + requiresRestartingPluginPlatform: boolean = false; - if (Object.keys(pluginSettingsConfigurableFile).length) { - // Update the configuration file. - await updateConfigurationFile.updateConfiguration(pluginSettingsConfigurableFile); + // Plugin settings configurables in the configuration file. + const pluginSettingsConfigurableFile = Object.keys(request.body) + .filter( + pluginSettingKey => + PLUGIN_SETTINGS[pluginSettingKey].isConfigurableFromFile, + ) + .reduce( + (accum, pluginSettingKey: string) => ({ + ...accum, + [pluginSettingKey]: request.body[pluginSettingKey], + }), + {}, + ); - requiresRunningHealthCheck = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRunningHealthCheck)) || requiresRunningHealthCheck; - requiresReloadingBrowserTab = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresReloadingBrowserTab)) || requiresReloadingBrowserTab; - requiresRestartingPluginPlatform = Object.keys(pluginSettingsConfigurableFile).some((pluginSettingKey: string) => Boolean(PLUGIN_SETTINGS[pluginSettingKey].requiresRestartingPluginPlatform)) || requiresRestartingPluginPlatform; - }; + if (Object.keys(pluginSettingsConfigurableFile).length) { + // Update the configuration file. + await updateConfigurationFile.updateConfiguration( + pluginSettingsConfigurableFile, + ); - return response.ok({ - body: { - data: { requiresRunningHealthCheck, requiresReloadingBrowserTab, requiresRestartingPluginPlatform, updatedConfiguration: pluginSettingsConfigurableFile } + requiresRunningHealthCheck = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey].requiresRunningHealthCheck, + ), + ) || requiresRunningHealthCheck; + requiresReloadingBrowserTab = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey].requiresReloadingBrowserTab, + ), + ) || requiresReloadingBrowserTab; + requiresRestartingPluginPlatform = + Object.keys(pluginSettingsConfigurableFile).some( + (pluginSettingKey: string) => + Boolean( + PLUGIN_SETTINGS[pluginSettingKey] + .requiresRestartingPluginPlatform, + ), + ) || requiresRestartingPluginPlatform; } - }); - }, - 3021 - ) + + return response.ok({ + body: { + data: { + requiresRunningHealthCheck, + requiresReloadingBrowserTab, + requiresRestartingPluginPlatform, + updatedConfiguration: pluginSettingsConfigurableFile, + }, + }, + }); + }, + 3021, + ); /** * Returns Wazuh app logs @@ -105,22 +158,23 @@ export class WazuhUtilsCtrl { * @param {Object} response * @returns {Array} app logs or ErrorResponse */ - async getAppLogs(context: RequestHandlerContext, request: OpenSearchDashboardsRequest, response: OpenSearchDashboardsResponseFactory) { + async getAppLogs( + context: RequestHandlerContext, + request: OpenSearchDashboardsRequest, + response: OpenSearchDashboardsResponseFactory, + ) { try { - const lastLogs = await read( - WAZUH_DATA_LOGS_RAW_PATH, - 50 - ); + const lastLogs = await read(WAZUH_DATA_LOGS_RAW_PATH, 50); const spliterLog = lastLogs.split('\n'); return spliterLog && Array.isArray(spliterLog) ? response.ok({ - body: { - error: 0, - lastLogs: spliterLog.filter( - item => typeof item === 'string' && item.length - ) - } - }) + body: { + error: 0, + lastLogs: spliterLog.filter( + item => typeof item === 'string' && item.length, + ), + }, + }) : response.ok({ error: 0, lastLogs: [] }); } catch (error) { return ErrorResponse(error.message || error, 3036, 500, response); @@ -135,25 +189,44 @@ export class WazuhUtilsCtrl { * @returns {Object} Configuration File or ErrorResponse */ uploadFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory, + ) => { const { key } = request.params; - const { file: bufferFile } = request.body; + let { file: bufferFile } = request.body; const pluginSetting = PLUGIN_SETTINGS[key]; // Check file extension const fileExtension = getFileExtensionFromBuffer(bufferFile); // Check if the extension is valid for the setting. - if (!pluginSetting.options.file.extensions.includes(`.${fileExtension}`)) { + if ( + !pluginSetting.options.file.extensions.includes(`.${fileExtension}`) + ) { return response.badRequest({ - body: `File extension is not valid for setting [${key}] setting. Allowed file extensions: ${pluginSetting.options.file.extensions.join(', ')}` + body: `File extension is not valid for setting [${key}] setting. Allowed file extensions: ${pluginSetting.options.file.extensions.join( + ', ', + )}`, }); - }; + } + + // Sanitize SVG content to prevent prevents XSS attacks + if (fileExtension === 'svg') { + const svgString = bufferFile.toString(); + const cleanSVG = sanitizeSVG(svgString); + bufferFile = Buffer.from(cleanSVG); + } const fileNamePath = `${key}.${fileExtension}`; // Create target directory - const targetDirectory = path.join(__dirname, '../../..', pluginSetting.options.file.store.relativePathFileSystem); + const targetDirectory = path.join( + __dirname, + '../../..', + pluginSetting.options.file.store.relativePathFileSystem, + ); createDirectoryIfNotExists(targetDirectory); // Get the files related to the setting and remove them const files = glob.sync(path.join(targetDirectory, `${key}.*`)); @@ -163,24 +236,33 @@ export class WazuhUtilsCtrl { fs.writeFileSync(path.join(targetDirectory, fileNamePath), bufferFile); // Update the setting in the configuration cache - const pluginSettingValue = pluginSetting.options.file.store.resolveStaticURL(fileNamePath); - await updateConfigurationFile.updateConfiguration({ [key]: pluginSettingValue }); + const pluginSettingValue = + pluginSetting.options.file.store.resolveStaticURL(fileNamePath); + await updateConfigurationFile.updateConfiguration({ + [key]: pluginSettingValue, + }); return response.ok({ body: { data: { - requiresRunningHealthCheck: Boolean(pluginSetting.requiresRunningHealthCheck), - requiresReloadingBrowserTab: Boolean(pluginSetting.requiresReloadingBrowserTab), - requiresRestartingPluginPlatform: Boolean(pluginSetting.requiresRestartingPluginPlatform), + requiresRunningHealthCheck: Boolean( + pluginSetting.requiresRunningHealthCheck, + ), + requiresReloadingBrowserTab: Boolean( + pluginSetting.requiresReloadingBrowserTab, + ), + requiresRestartingPluginPlatform: Boolean( + pluginSetting.requiresRestartingPluginPlatform, + ), updatedConfiguration: { - [key]: pluginSettingValue - } - } - } + [key]: pluginSettingValue, + }, + }, + }, }); }, - 3022 - ) + 3022, + ); /** * Delete a file @@ -190,64 +272,96 @@ export class WazuhUtilsCtrl { * @returns {Object} Configuration File or ErrorResponse */ deleteFile = this.routeDecoratorProtectedAdministratorRoleValidToken( - async (context: RequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory) => { + async ( + context: RequestHandlerContext, + request: KibanaRequest, + response: KibanaResponseFactory, + ) => { const { key } = request.params; const pluginSetting = PLUGIN_SETTINGS[key]; // Get the files related to the setting and remove them - const targetDirectory = path.join(__dirname, '../../..', pluginSetting.options.file.store.relativePathFileSystem); + const targetDirectory = path.join( + __dirname, + '../../..', + pluginSetting.options.file.store.relativePathFileSystem, + ); const files = glob.sync(path.join(targetDirectory, `${key}.*`)); files.forEach(fs.unlinkSync); // Update the setting in the configuration cache const pluginSettingValue = pluginSetting.defaultValue; - await updateConfigurationFile.updateConfiguration({ [key]: pluginSettingValue }); + await updateConfigurationFile.updateConfiguration({ + [key]: pluginSettingValue, + }); return response.ok({ body: { - message: 'All files were removed and the configuration file was updated.', + message: + 'All files were removed and the configuration file was updated.', data: { - requiresRunningHealthCheck: Boolean(pluginSetting.requiresRunningHealthCheck), - requiresReloadingBrowserTab: Boolean(pluginSetting.requiresReloadingBrowserTab), - requiresRestartingPluginPlatform: Boolean(pluginSetting.requiresRestartingPluginPlatform), + requiresRunningHealthCheck: Boolean( + pluginSetting.requiresRunningHealthCheck, + ), + requiresReloadingBrowserTab: Boolean( + pluginSetting.requiresReloadingBrowserTab, + ), + requiresRestartingPluginPlatform: Boolean( + pluginSetting.requiresRestartingPluginPlatform, + ), updatedConfiguration: { - [key]: pluginSettingValue - } - } - } + [key]: pluginSettingValue, + }, + }, + }, }); }, - 3023 - ) + 3023, + ); - private routeDecoratorProtectedAdministratorRoleValidToken(routeHandler, errorCode: number) { + private routeDecoratorProtectedAdministratorRoleValidToken( + routeHandler, + errorCode: number, + ) { return async (context, request, response) => { try { // Check if user has administrator role in token const token = getCookieValueByName(request.headers.cookie, 'wz-token'); if (!token) { return ErrorResponse('No token provided', 401, 401, response); - }; + } const decodedToken = jwtDecode(token); if (!decodedToken) { return ErrorResponse('No permissions in token', 401, 401, response); - }; - if (!decodedToken.rbac_roles || !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID)) { + } + if ( + !decodedToken.rbac_roles || + !decodedToken.rbac_roles.includes(WAZUH_ROLE_ADMINISTRATOR_ID) + ) { return ErrorResponse('No administrator role', 401, 401, response); - }; + } // Check the provided token is valid - const apiHostID = getCookieValueByName(request.headers.cookie, 'wz-api'); + const apiHostID = getCookieValueByName( + request.headers.cookie, + 'wz-api', + ); if (!apiHostID) { return ErrorResponse('No API id provided', 401, 401, response); - }; - const responseTokenIsWorking = await context.wazuh.api.client.asCurrentUser.request('GET', '/', {}, { apiHostID }); + } + const responseTokenIsWorking = + await context.wazuh.api.client.asCurrentUser.request( + 'GET', + '/', + {}, + { apiHostID }, + ); if (responseTokenIsWorking.status !== 200) { return ErrorResponse('Token is not valid', 401, 401, response); - }; - return await routeHandler(context, request, response) + } + return await routeHandler(context, request, response); } catch (error) { return ErrorResponse(error.message || error, errorCode, 500, response); } - } + }; } } diff --git a/plugins/main/server/lib/sanitizer.ts b/plugins/main/server/lib/sanitizer.ts new file mode 100644 index 0000000000..2de6bcc909 --- /dev/null +++ b/plugins/main/server/lib/sanitizer.ts @@ -0,0 +1,10 @@ +import createDOMPurify from 'dompurify'; +import { JSDOM } from 'jsdom'; + +function sanitizeCSV(svgString: string) { + const window = new JSDOM('').window; + const DOMPurify = createDOMPurify(window); + const cleanSVG = DOMPurify.sanitize(svgString); + return cleanSVG; +} +export { sanitizeCSV }; diff --git a/plugins/main/server/plugin.ts b/plugins/main/server/plugin.ts index 788d281ea2..b754085f69 100644 --- a/plugins/main/server/plugin.ts +++ b/plugins/main/server/plugin.ts @@ -29,9 +29,16 @@ import { import { WazuhPluginSetup, WazuhPluginStart, PluginSetup } from './types'; import { SecurityObj, ISecurityFactory } from './lib/security-factory'; import { setupRoutes } from './routes'; -import { jobInitializeRun, jobMonitoringRun, jobSchedulerRun, jobQueueRun, jobMigrationTasksRun } from './start'; +import { + jobInitializeRun, + jobMonitoringRun, + jobSchedulerRun, + jobQueueRun, + jobMigrationTasksRun, + jobSanitizeUploadedFilesTasksRun, +} from './start'; import { getCookieValueByName } from './lib/cookie'; -import * as ApiInterceptor from './lib/api-interceptor'; +import * as ApiInterceptor from './lib/api-interceptor'; import { schema, TypeOf } from '@osd/config-schema'; import type { Observable } from 'rxjs'; import { first } from 'rxjs/operators'; @@ -39,21 +46,31 @@ import { first } from 'rxjs/operators'; declare module 'opensearch_dashboards/server' { interface RequestHandlerContext { wazuh: { - logger: Logger, - plugins: PluginSetup, - security: ISecurityFactory + logger: Logger; + plugins: PluginSetup; + security: ISecurityFactory; api: { client: { asInternalUser: { - authenticate: (apiHostID: string) => Promise - request: (method: string, path: string, data: any, options: {apiHostID: string, forceRefresh?:boolean}) => Promise - }, + authenticate: (apiHostID: string) => Promise; + request: ( + method: string, + path: string, + data: any, + options: { apiHostID: string; forceRefresh?: boolean }, + ) => Promise; + }; asCurrentUser: { - authenticate: (apiHostID: string) => Promise - request: (method: string, path: string, data: any, options: {apiHostID: string, forceRefresh?:boolean}) => Promise - } - } - } + authenticate: (apiHostID: string) => Promise; + request: ( + method: string, + path: string, + data: any, + options: { apiHostID: string; forceRefresh?: boolean }, + ) => Promise; + }; + }; + }; }; } } @@ -82,15 +99,35 @@ export class WazuhPlugin implements Plugin { api: { client: { asInternalUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID), - request: async (method, path, data, options) => await ApiInterceptor.requestAsInternalUser(method, path, data, options), + authenticate: async apiHostID => + await ApiInterceptor.authenticate(apiHostID), + request: async (method, path, data, options) => + await ApiInterceptor.requestAsInternalUser( + method, + path, + data, + options, + ), }, asCurrentUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID, (await wazuhSecurity.getCurrentUser(request, context)).authContext), - request: async (method, path, data, options) => await ApiInterceptor.requestAsCurrentUser(method, path, data, {...options, token: getCookieValueByName(request.headers.cookie, 'wz-token')}), - } - } - } + authenticate: async apiHostID => + await ApiInterceptor.authenticate( + apiHostID, + ( + await wazuhSecurity.getCurrentUser(request, context) + ).authContext, + ), + request: async (method, path, data, options) => + await ApiInterceptor.requestAsCurrentUser(method, path, data, { + ...options, + token: getCookieValueByName( + request.headers.cookie, + 'wz-token', + ), + }), + }, + }, + }, }; }); @@ -110,18 +147,28 @@ export class WazuhPlugin implements Plugin { } public async start(core: CoreStart) { - const globalConfiguration: SharedGlobalConfig = await this.initializerContext.config.legacy.globalConfig$.pipe(first()).toPromise(); + const globalConfiguration: SharedGlobalConfig = + await this.initializerContext.config.legacy.globalConfig$ + .pipe(first()) + .toPromise(); const wazuhApiClient = { client: { asInternalUser: { - authenticate: async (apiHostID) => await ApiInterceptor.authenticate(apiHostID), - request: async (method, path, data, options) => await ApiInterceptor.requestAsInternalUser(method, path, data, options), - } - } + authenticate: async apiHostID => + await ApiInterceptor.authenticate(apiHostID), + request: async (method, path, data, options) => + await ApiInterceptor.requestAsInternalUser( + method, + path, + data, + options, + ), + }, + }, }; const contextServer = { - config: globalConfiguration + config: globalConfiguration, }; // Initialize @@ -129,19 +176,29 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('initialize'), - api: wazuhApiClient + api: wazuhApiClient, }, - server: contextServer + server: contextServer, + }); + + // Sanitize uploaded files tasks + jobSanitizeUploadedFilesTasksRun({ + core, + wazuh: { + logger: this.logger.get('sanitize-uploaded-files-task'), + api: wazuhApiClient, + }, + server: contextServer, }); // Migration tasks jobMigrationTasksRun({ - core, + core, wazuh: { logger: this.logger.get('migration-task'), - api: wazuhApiClient + api: wazuhApiClient, }, - server: contextServer + server: contextServer, }); // Monitoring @@ -149,9 +206,9 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('monitoring'), - api: wazuhApiClient + api: wazuhApiClient, }, - server: contextServer + server: contextServer, }); // Scheduler @@ -159,9 +216,9 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('cron-scheduler'), - api: wazuhApiClient + api: wazuhApiClient, }, - server: contextServer + server: contextServer, }); // Queue @@ -169,12 +226,12 @@ export class WazuhPlugin implements Plugin { core, wazuh: { logger: this.logger.get('queue'), - api: wazuhApiClient + api: wazuhApiClient, }, - server: contextServer + server: contextServer, }); return {}; } - public stop() { } + public stop() {} } diff --git a/plugins/main/server/start/index.ts b/plugins/main/server/start/index.ts index 39dd0a51a3..9c68b84340 100644 --- a/plugins/main/server/start/index.ts +++ b/plugins/main/server/start/index.ts @@ -3,4 +3,5 @@ export * from './initialize'; export * from './monitoring'; export * from './queue'; export * from './tryCatchForIndexPermissionError'; -export * from './migration-tasks'; \ No newline at end of file +export * from './migration-tasks'; +export * from './sanitize-uploaded-files'; diff --git a/plugins/main/server/start/sanitize-uploaded-files/index.ts b/plugins/main/server/start/sanitize-uploaded-files/index.ts new file mode 100644 index 0000000000..22e0300212 --- /dev/null +++ b/plugins/main/server/start/sanitize-uploaded-files/index.ts @@ -0,0 +1,7 @@ +import sanitizeUploadedSVG from './sanitize-svg'; + +export function jobSanitizeUploadedFilesTasksRun(context) { + const sanitizeTasks = [sanitizeUploadedSVG]; + + sanitizeTasks.forEach(task => task(context)); +} diff --git a/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.test.ts b/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.test.ts new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.test.ts @@ -0,0 +1 @@ +// TODO diff --git a/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.ts b/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.ts new file mode 100644 index 0000000000..1c419e5b93 --- /dev/null +++ b/plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.ts @@ -0,0 +1,94 @@ +import fs from 'fs'; +import glob from 'glob'; +import path from 'path'; +import { PLUGIN_SETTINGS, TPluginSetting } from '../../../common/constants'; +import { log } from '../../lib/logger'; +import { sanitizeSVG } from '../../lib/sanitizer'; +import { getConfiguration } from '../../lib/get-configuration'; + +/** + * This task checks for custom SVG files uploaded by the user and sanitizes them. + * The goal is to sanitize files uploaded in previous versions. + * @param context + * @returns + */ +export default function sanitizeUploadedSVG(context) { + // Create a wrapper function that logs to plugin files and platform logging system + const createLog = (level: string) => message => { + log('sanitize:sanitizeUploadedSVG', message, level); + context.wazuh.logger[level](`sanitize:sanitizeUploadedSVG: ${message}`); + }; + + // Create the logger + const logger = { + info: createLog('info'), + warn: createLog('warn'), + error: createLog('error'), + debug: createLog('debug'), + }; + + try { + logger.debug('Task sanitize SVG started'); + + const configuration = getConfiguration(); + const logosSettingKeys = [ + 'customization.logo.sidebar', + 'customization.logo.app', + 'customization.logo.healthcheck', + ]; + + // Check each of the possible custom settings uploaded files look for SVG to sanitize + logosSettingKeys.forEach(logoKey => { + const logoSetting: TPluginSetting | undefined = PLUGIN_SETTINGS[logoKey]; + const customLogoPath = configuration[logoKey]; + if (!logoSetting || !customLogoPath) { + return; + } + + const targetDirectory = path.join( + __dirname, + '../../..', + logoSetting.options.file.store.relativePathFileSystem, + ); + + // If the setting folder doesn't exist abort the task + if (!fs.existsSync(targetDirectory)) { + return; + } + + // Get the files related to the setting and remove them + const files = glob.sync(path.join(targetDirectory, `${logoKey}.*`)); + + // If there are no files saved abort the task + if (!files?.length) { + return; + } + + // Check file extension + const fileName = path.basename(files[0]); + const fileExtension = path.extname(fileName); + if (fileExtension.toLocaleLowerCase() !== '.svg') { + return; + } + + // Read the file contents + const fileFullPath = path.join(targetDirectory, fileName); + const originalFileBuffer = fs.readFileSync(fileFullPath); + + // Sanitize the file contents + const svgString = originalFileBuffer.toString(); + const cleanSVG = sanitizeSVG(svgString); + const cleanFileBuffer = Buffer.from(cleanSVG); + + // Delete the original file + fs.unlinkSync(fileFullPath); + + // Save the clean file in the target directory + fs.writeFileSync(fileFullPath, cleanFileBuffer); + }); + + logger.debug('Task finished'); + } catch (error) { + logger.error(`Error: ${error.message}`); + } +} From e46401a61ab49679c3de1179134bdd4da9d19e12 Mon Sep 17 00:00:00 2001 From: Federico Rodriguez Date: Sun, 19 May 2024 17:11:02 +0200 Subject: [PATCH 02/10] Add log and test to sanitizer --- plugins/main/server/lib/sanitizer/index.ts | 3 ++ .../server/lib/sanitizer/sanitizer.test.ts | 47 +++++++++++++++++++ .../server/lib/{ => sanitizer}/sanitizer.ts | 4 +- .../sanitize-svg.test.ts | 1 - .../sanitize-uploaded-files/sanitize-svg.ts | 35 +++++++++----- 5 files changed, 74 insertions(+), 16 deletions(-) create mode 100644 plugins/main/server/lib/sanitizer/index.ts create mode 100644 plugins/main/server/lib/sanitizer/sanitizer.test.ts rename plugins/main/server/lib/{ => sanitizer}/sanitizer.ts (77%) delete mode 100644 plugins/main/server/start/sanitize-uploaded-files/sanitize-svg.test.ts diff --git a/plugins/main/server/lib/sanitizer/index.ts b/plugins/main/server/lib/sanitizer/index.ts new file mode 100644 index 0000000000..b641100eb6 --- /dev/null +++ b/plugins/main/server/lib/sanitizer/index.ts @@ -0,0 +1,3 @@ +import { sanitizeSVG } from './sanitizer'; + +export { sanitizeSVG }; diff --git a/plugins/main/server/lib/sanitizer/sanitizer.test.ts b/plugins/main/server/lib/sanitizer/sanitizer.test.ts new file mode 100644 index 0000000000..0c19883660 --- /dev/null +++ b/plugins/main/server/lib/sanitizer/sanitizer.test.ts @@ -0,0 +1,47 @@ +import { sanitizeSVG } from './sanitizer'; + +const mockedFilesContents = [ + { + original: ` + + + + + +`, + clean: ` + + + + + +`, + }, + { original: '', clean: '' }, + { original: '', clean: '' }, + { + original: '

abc