Filter out empty values for exceptions (#106685)

This commit is contained in:
Esteban Beltran 2021-08-02 11:55:47 +02:00 committed by GitHub
parent ced9aecab5
commit ee04f6dc95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 274 additions and 99 deletions

View file

@ -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',

View file

@ -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
*/
@ -510,34 +533,35 @@ export const getPrepopulatedMemorySignatureException = ({
alertEcsData: Flattened<Ecs>;
}): 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<Ecs>;
}): 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
*/