Filter out empty values for exceptions (#106685)
This commit is contained in:
parent
ced9aecab5
commit
ee04f6dc95
2 changed files with 274 additions and 99 deletions
|
@ -823,7 +823,8 @@ describe('Exception helpers', () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
describe('ransomware protection exception items', () => {
|
||||||
test('it should return pre-populated ransomware items for event code `ransomware`', () => {
|
test('it should return pre-populated ransomware items for event code `ransomware`', () => {
|
||||||
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
||||||
_id: '123',
|
_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`', () => {
|
test('it should return pre-populated memory signature items for event code `memory_signature`', () => {
|
||||||
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
||||||
_id: '123',
|
_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`', () => {
|
test('it should return pre-populated memory shellcode items for event code `malicious_thread`', () => {
|
||||||
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
|
||||||
_id: '123',
|
_id: '123',
|
||||||
|
@ -1085,7 +1126,115 @@ describe('Exception helpers', () => {
|
||||||
value: '4000',
|
value: '4000',
|
||||||
id: '123',
|
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',
|
field: 'region_protection',
|
||||||
operator: 'included',
|
operator: 'included',
|
||||||
|
|
|
@ -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<T extends ExceptionEntry>(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
|
* Returns the default values from the alert data to autofill new endpoint exceptions
|
||||||
*/
|
*/
|
||||||
|
@ -510,34 +533,35 @@ export const getPrepopulatedMemorySignatureException = ({
|
||||||
alertEcsData: Flattened<Ecs>;
|
alertEcsData: Flattened<Ecs>;
|
||||||
}): ExceptionsBuilderExceptionItem => {
|
}): ExceptionsBuilderExceptionItem => {
|
||||||
const { process } = alertEcsData;
|
const { process } = alertEcsData;
|
||||||
return {
|
const entries = filterEmptyExceptionEntries([
|
||||||
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
|
|
||||||
entries: addIdToEntries([
|
|
||||||
{
|
{
|
||||||
field: 'Memory_protection.feature',
|
field: 'Memory_protection.feature',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: alertEcsData.Memory_protection?.feature ?? '',
|
value: alertEcsData.Memory_protection?.feature ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.executable.caseless',
|
field: 'process.executable.caseless',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.executable ?? '',
|
value: process?.executable ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.name.caseless',
|
field: 'process.name.caseless',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.name ?? '',
|
value: process?.name ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.hash.sha256',
|
field: 'process.hash.sha256',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.hash?.sha256 ?? '',
|
value: process?.hash?.sha256 ?? '',
|
||||||
},
|
},
|
||||||
]),
|
]);
|
||||||
|
return {
|
||||||
|
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
|
||||||
|
entries: addIdToEntries(entries),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export const getPrepopulatedMemoryShellcodeException = ({
|
export const getPrepopulatedMemoryShellcodeException = ({
|
||||||
|
@ -554,81 +578,83 @@ export const getPrepopulatedMemoryShellcodeException = ({
|
||||||
alertEcsData: Flattened<Ecs>;
|
alertEcsData: Flattened<Ecs>;
|
||||||
}): ExceptionsBuilderExceptionItem => {
|
}): ExceptionsBuilderExceptionItem => {
|
||||||
const { process, Target } = alertEcsData;
|
const { process, Target } = alertEcsData;
|
||||||
return {
|
const entries = filterEmptyExceptionEntries([
|
||||||
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
|
|
||||||
entries: addIdToEntries([
|
|
||||||
{
|
{
|
||||||
field: 'Memory_protection.feature',
|
field: 'Memory_protection.feature',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: alertEcsData.Memory_protection?.feature ?? '',
|
value: alertEcsData.Memory_protection?.feature ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'Memory_protection.self_injection',
|
field: 'Memory_protection.self_injection',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: String(alertEcsData.Memory_protection?.self_injection) ?? '',
|
value: String(alertEcsData.Memory_protection?.self_injection) ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.executable.caseless',
|
field: 'process.executable.caseless',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.executable ?? '',
|
value: process?.executable ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.name.caseless',
|
field: 'process.name.caseless',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.name ?? '',
|
value: process?.name ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'process.Ext.token.integrity_level_name',
|
field: 'process.Ext.token.integrity_level_name',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: process?.Ext?.token?.integrity_level_name ?? '',
|
value: process?.Ext?.token?.integrity_level_name ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'Target.process.thread.Ext.start_address_details',
|
field: 'Target.process.thread.Ext.start_address_details',
|
||||||
type: 'nested',
|
type: 'nested' as const,
|
||||||
entries: [
|
entries: [
|
||||||
{
|
{
|
||||||
field: 'allocation_type',
|
field: 'allocation_type',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '',
|
value: Target?.process?.thread?.Ext?.start_address_details?.allocation_type ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'allocation_size',
|
field: 'allocation_size',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value:
|
value: String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '',
|
||||||
String(Target?.process?.thread?.Ext?.start_address_details?.allocation_size) ?? '',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'region_size',
|
field: 'region_size',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '',
|
value: String(Target?.process?.thread?.Ext?.start_address_details?.region_size) ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'region_protection',
|
field: 'region_protection',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value:
|
value:
|
||||||
String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '',
|
String(Target?.process?.thread?.Ext?.start_address_details?.region_protection) ?? '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'memory_pe.imphash',
|
field: 'memory_pe.imphash',
|
||||||
operator: 'included',
|
operator: 'included' as const,
|
||||||
type: 'match',
|
type: 'match' as const,
|
||||||
value:
|
value:
|
||||||
String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '',
|
String(Target?.process?.thread?.Ext?.start_address_details?.memory_pe?.imphash) ?? '',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]),
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...getNewExceptionItem({ listId, namespaceType: listNamespace, ruleName }),
|
||||||
|
entries: addIdToEntries(entries),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping
|
* Determines whether or not any entries within the given exceptionItems contain values not in the specified ECS mapping
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in a new issue