From 323e2f3c534a3f052d665a41afa4c1f88d77dbdb Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 2 Aug 2021 11:55:47 +0200 Subject: [PATCH] Filter out empty values for exceptions (#106685) --- .../components/exceptions/helpers.test.tsx | 153 +++++++++++- .../common/components/exceptions/helpers.tsx | 220 ++++++++++-------- 2 files changed, 274 insertions(+), 99 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx index 5347ee875181b0..83006f09a14be0 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx @@ -823,7 +823,8 @@ describe('Exception helpers', () => { }, ]); }); - + }); + describe('ransomware protection exception items', () => { test('it should return pre-populated ransomware items for event code `ransomware`', () => { const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { _id: '123', @@ -938,7 +939,9 @@ describe('Exception helpers', () => { }, ]); }); + }); + describe('memory protection exception items', () => { test('it should return pre-populated memory signature items for event code `memory_signature`', () => { const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { _id: '123', @@ -990,6 +993,44 @@ describe('Exception helpers', () => { ]); }); + test('it should return pre-populated memory signature items for event code `memory_signature` and skip Empty', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + process: { + name: '', // name is empty + // executable: '', left intentionally commented + hash: { + sha256: 'some hash', + }, + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + Memory_protection: { + feature: 'signature', + }, + event: { + code: 'memory_signature', + }, + }); + + // should not contain name or executable + expect(defaultItems[0].entries).toEqual([ + { + field: 'Memory_protection.feature', + operator: 'included', + type: 'match', + value: 'signature', + id: '123', + }, + { + field: 'process.hash.sha256', + operator: 'included', + type: 'match', + value: 'some hash', + id: '123', + }, + ]); + }); + test('it should return pre-populated memory shellcode items for event code `malicious_thread`', () => { const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { _id: '123', @@ -1085,7 +1126,115 @@ describe('Exception helpers', () => { value: '4000', id: '123', }, - { field: 'region_size', operator: 'included', type: 'match', value: '4000', id: '123' }, + { + field: 'region_size', + operator: 'included', + type: 'match', + value: '4000', + id: '123', + }, + { + field: 'region_protection', + operator: 'included', + type: 'match', + value: 'RWX', + id: '123', + }, + { + field: 'memory_pe.imphash', + operator: 'included', + type: 'match', + value: 'a hash', + id: '123', + }, + ], + id: '123', + }, + ]); + }); + + test('it should return pre-populated memory shellcode items for event code `malicious_thread` and skip empty', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + process: { + name: '', // name is empty + // executable: '', left intentionally commented + Ext: { + token: { + integrity_level_name: 'high', + }, + }, + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + Memory_protection: { + feature: 'shellcode_thread', + self_injection: true, + }, + event: { + code: 'malicious_thread', + }, + Target: { + process: { + thread: { + Ext: { + start_address_allocation_offset: 0, + start_address_bytes_disasm_hash: 'a disam hash', + start_address_details: { + // allocation_type: '', left intentionally commented + allocation_size: 4000, + region_size: 4000, + region_protection: 'RWX', + memory_pe: { + imphash: 'a hash', + }, + }, + }, + }, + }, + }, + }); + + // no name, no exceutable, no allocation_type + expect(defaultItems[0].entries).toEqual([ + { + field: 'Memory_protection.feature', + operator: 'included', + type: 'match', + value: 'shellcode_thread', + id: '123', + }, + { + field: 'Memory_protection.self_injection', + operator: 'included', + type: 'match', + value: 'true', + id: '123', + }, + { + field: 'process.Ext.token.integrity_level_name', + operator: 'included', + type: 'match', + value: 'high', + id: '123', + }, + { + field: 'Target.process.thread.Ext.start_address_details', + type: 'nested', + entries: [ + { + field: 'allocation_size', + operator: 'included', + type: 'match', + value: '4000', + id: '123', + }, + { + field: 'region_size', + operator: 'included', + type: 'match', + value: '4000', + id: '123', + }, { field: 'region_protection', operator: 'included', diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 613d2955454619..62250a0933ffb9 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -343,6 +343,29 @@ export const getCodeSignatureValue = ( } }; +// helper type to filter empty-valued exception entries +interface ExceptionEntry { + value?: string; + entries?: ExceptionEntry[]; +} + +/** + * Takes an array of Entries and filter out the ones with empty values. + * It will also filter out empty values for nested entries. + */ +function filterEmptyExceptionEntries(entries: T[]): T[] { + const finalEntries: T[] = []; + for (const entry of entries) { + if (entry.entries !== undefined) { + entry.entries = entry.entries.filter((el) => el.value !== undefined && el.value.length > 0); + finalEntries.push(entry); + } else if (entry.value !== undefined && entry.value.length > 0) { + finalEntries.push(entry); + } + } + return finalEntries; +} + /** * Returns the default values from the alert data to autofill new endpoint exceptions */ @@ -510,34 +533,35 @@ export const getPrepopulatedMemorySignatureException = ({ alertEcsData: Flattened; }): ExceptionsBuilderExceptionItem => { const { process } = alertEcsData; + const entries = filterEmptyExceptionEntries([ + { + field: 'Memory_protection.feature', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.Memory_protection?.feature ?? '', + }, + { + field: 'process.executable.caseless', + operator: 'included' as const, + type: 'match' as const, + value: process?.executable ?? '', + }, + { + field: 'process.name.caseless', + operator: 'included' as const, + type: 'match' as const, + value: process?.name ?? '', + }, + { + field: 'process.hash.sha256', + operator: 'included' as const, + type: 'match' as const, + value: process?.hash?.sha256 ?? '', + }, + ]); return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), - entries: addIdToEntries([ - { - field: 'Memory_protection.feature', - operator: 'included', - type: 'match', - value: alertEcsData.Memory_protection?.feature ?? '', - }, - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: process?.executable ?? '', - }, - { - field: 'process.name.caseless', - operator: 'included', - type: 'match', - value: process?.name ?? '', - }, - { - field: 'process.hash.sha256', - operator: 'included', - type: 'match', - value: process?.hash?.sha256 ?? '', - }, - ]), + entries: addIdToEntries(entries), }; }; export const getPrepopulatedMemoryShellcodeException = ({ @@ -554,81 +578,83 @@ export const getPrepopulatedMemoryShellcodeException = ({ alertEcsData: Flattened; }): ExceptionsBuilderExceptionItem => { const { process, Target } = alertEcsData; + const entries = filterEmptyExceptionEntries([ + { + field: 'Memory_protection.feature', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.Memory_protection?.feature ?? '', + }, + { + field: 'Memory_protection.self_injection', + operator: 'included' as const, + type: 'match' as const, + value: String(alertEcsData.Memory_protection?.self_injection) ?? '', + }, + { + field: 'process.executable.caseless', + operator: 'included' as const, + type: 'match' as const, + value: process?.executable ?? '', + }, + { + field: 'process.name.caseless', + operator: 'included' as const, + type: 'match' as const, + value: process?.name ?? '', + }, + { + field: 'process.Ext.token.integrity_level_name', + operator: 'included' as const, + type: 'match' as const, + value: process?.Ext?.token?.integrity_level_name ?? '', + }, + { + field: 'Target.process.thread.Ext.start_address_details', + type: 'nested' as const, + entries: [ + { + field: 'allocation_type', + operator: 'included' as const, + type: 'match' as const, + value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '', + }, + { + field: 'allocation_size', + operator: 'included' as const, + type: 'match' as const, + value: String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '', + }, + { + field: 'region_size', + operator: 'included' as const, + type: 'match' as const, + value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '', + }, + { + field: 'region_protection', + operator: 'included' as const, + type: 'match' as const, + value: + String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '', + }, + { + field: 'memory_pe.imphash', + operator: 'included' as const, + type: 'match' as const, + value: + String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '', + }, + ], + }, + ]); + return { ...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }), - entries: addIdToEntries([ - { - field: 'Memory_protection.feature', - operator: 'included', - type: 'match', - value: alertEcsData.Memory_protection?.feature ?? '', - }, - { - field: 'Memory_protection.self_injection', - operator: 'included', - type: 'match', - value: String(alertEcsData.Memory_protection?.self_injection) ?? '', - }, - { - field: 'process.executable.caseless', - operator: 'included', - type: 'match', - value: process?.executable ?? '', - }, - { - field: 'process.name.caseless', - operator: 'included', - type: 'match', - value: process?.name ?? '', - }, - { - field: 'process.Ext.token.integrity_level_name', - operator: 'included', - type: 'match', - value: process?.Ext?.token?.integrity_level_name ?? '', - }, - { - field: 'Target.process.thread.Ext.start_address_details', - type: 'nested', - entries: [ - { - field: 'allocation_type', - operator: 'included', - type: 'match', - value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '', - }, - { - field: 'allocation_size', - operator: 'included', - type: 'match', - value: - String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '', - }, - { - field: 'region_size', - operator: 'included', - type: 'match', - value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '', - }, - { - field: 'region_protection', - operator: 'included', - type: 'match', - value: - String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '', - }, - { - field: 'memory_pe.imphash', - operator: 'included', - type: 'match', - value: - String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '', - }, - ], - }, - ]), + entries: addIdToEntries(entries), }; }; + /** * Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping */