diff --git a/x-pack/plugins/security_solution/common/ecs/dll/index.ts b/x-pack/plugins/security_solution/common/ecs/dll/index.ts new file mode 100644 index 0000000000000..0634d29c691cf --- /dev/null +++ b/x-pack/plugins/security_solution/common/ecs/dll/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CodeSignature } from '../file'; +import { ProcessPe } from '../process'; + +export interface DllEcs { + path?: string; + code_signature?: CodeSignature; + pe?: ProcessPe; +} diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts index 610a2fd1f6e9e..fbeb323157367 100644 --- a/x-pack/plugins/security_solution/common/ecs/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/index.ts @@ -9,6 +9,7 @@ import { AgentEcs } from './agent'; import { AuditdEcs } from './auditd'; import { DestinationEcs } from './destination'; import { DnsEcs } from './dns'; +import { DllEcs } from './dll'; import { EndgameEcs } from './endgame'; import { EventEcs } from './event'; import { FileEcs } from './file'; @@ -68,4 +69,5 @@ export interface Ecs { // eslint-disable-next-line @typescript-eslint/naming-convention Memory_protection?: MemoryProtection; Target?: Target; + dll?: DllEcs; } diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts index 0eb2400466e64..2a58c6d5b47d0 100644 --- a/x-pack/plugins/security_solution/common/ecs/process/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts @@ -5,14 +5,16 @@ * 2.0. */ -import { Ext } from '../file'; +import { CodeSignature, Ext } from '../file'; export interface ProcessEcs { Ext?: Ext; + command_line?: string[]; entity_id?: string[]; exit_code?: number[]; hash?: ProcessHashData; parent?: ProcessParentData; + code_signature?: CodeSignature; pid?: number[]; name?: string[]; ppid?: number[]; @@ -32,6 +34,7 @@ export interface ProcessHashData { export interface ProcessParentData { name?: string[]; pid?: number[]; + executable?: string[]; } export interface Thread { @@ -39,3 +42,9 @@ export interface Thread { start?: string[]; Ext?: Ext; } +export interface ProcessPe { + original_file_name?: string; + company?: string; + description?: string; + file_version?: string; +} diff --git a/x-pack/plugins/security_solution/common/ecs/registry/index.ts b/x-pack/plugins/security_solution/common/ecs/registry/index.ts index c756fb139199e..6ca6afc10098c 100644 --- a/x-pack/plugins/security_solution/common/ecs/registry/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/registry/index.ts @@ -10,4 +10,9 @@ export interface RegistryEcs { key?: string[]; path?: string[]; value?: string[]; + data?: RegistryEcsData; +} + +export interface RegistryEcsData { + strings?: string[]; } diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index 255ab8f0a598c..b6d9e5f0f3695 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -392,6 +392,7 @@ enum AlertTypes { MALWARE = 'MALWARE', MEMORY_SIGNATURE = 'MEMORY_SIGNATURE', MEMORY_SHELLCODE = 'MEMORY_SHELLCODE', + BEHAVIOR = 'BEHAVIOR', } const alertsDefaultDataStream = { @@ -778,11 +779,117 @@ export class EndpointDocGenerator extends BaseDataGenerator { alertsDataStream, alertType, }); + case AlertTypes.BEHAVIOR: + return this.generateBehaviorAlert({ + ts, + entityID, + parentEntityID, + ancestry, + alertsDataStream, + }); default: return assertNever(alertType); } } + /** + * Creates a memory alert from the simulated host represented by this EndpointDocGenerator + * @param ts - Timestamp to put in the event + * @param entityID - entityID of the originating process + * @param parentEntityID - optional entityID of the parent process, if it exists + * @param ancestry - an array of ancestors for the generated alert + * @param alertsDataStream the values to populate the data_stream fields when generating alert documents + */ + public generateBehaviorAlert({ + ts = new Date().getTime(), + entityID = this.randomString(10), + parentEntityID, + ancestry = [], + alertsDataStream = alertsDefaultDataStream, + }: { + ts?: number; + entityID?: string; + parentEntityID?: string; + ancestry?: string[]; + alertsDataStream?: DataStream; + } = {}): AlertEvent { + const processName = this.randomProcessName(); + const newAlert: AlertEvent = { + ...this.commonInfo, + data_stream: alertsDataStream, + '@timestamp': ts, + ecs: { + version: '1.6.0', + }, + rule: { + id: this.randomUUID(), + }, + event: { + action: 'rule_detection', + kind: 'alert', + category: 'behavior', + code: 'behavior', + id: this.seededUUIDv4(), + dataset: 'endpoint.diagnostic.collection', + module: 'endpoint', + type: 'info', + sequence: this.sequence++, + }, + file: { + name: 'fake_behavior.exe', + path: 'C:/fake_behavior.exe', + }, + destination: { + port: 443, + ip: this.randomIP(), + }, + source: { + port: 59406, + ip: this.randomIP(), + }, + network: { + transport: 'tcp', + type: 'ipv4', + direction: 'outgoing', + }, + registry: { + path: + 'HKEY_USERS\\S-1-5-21-2460036010-3910878774-3458087990-1001\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\chrome', + value: processName, + data: { + strings: `C:/fake_behavior/${processName}`, + }, + }, + process: { + pid: 2, + name: processName, + entity_id: entityID, + executable: `C:/fake_behavior/${processName}`, + parent: parentEntityID + ? { + entity_id: parentEntityID, + pid: 1, + } + : undefined, + Ext: { + ancestry, + code_signature: [ + { + trusted: false, + subject_name: 'bad signer', + }, + ], + user: 'SYSTEM', + token: { + integrity_level_name: 'high', + elevation_level: 'full', + }, + }, + }, + dll: this.getAlertsDefaultDll(), + }; + return newAlert; + } /** * Returns the default DLLs used in alerts */ diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index d5a8caac1dffe..5f92965c0e6ed 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -301,6 +301,21 @@ export type AlertEvent = Partial<{ feature: ECSField; self_injection: ECSField; }>; + destination: Partial<{ + port: ECSField; + ip: ECSField; + }>; + source: Partial<{ + port: ECSField; + ip: ECSField; + }>; + registry: Partial<{ + path: ECSField; + value: ECSField; + data: Partial<{ + strings: ECSField; + }>; + }>; Target: Partial<{ process: Partial<{ thread: Partial<{ @@ -359,6 +374,9 @@ export type AlertEvent = Partial<{ }>; }>; }>; + rule: Partial<{ + id: ECSField; + }>; file: Partial<{ owner: ECSField; name: ECSField; @@ -677,6 +695,8 @@ export type SafeEndpointEvent = Partial<{ }>; }>; network: Partial<{ + transport: ECSField; + type: ECSField; direction: ECSField; forwarded_ip: ECSField; }>; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json index c37be60545ab2..12ee0273f078a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json @@ -56,6 +56,7 @@ "file.mode", "file.name", "file.owner", + "file.path", "file.pe.company", "file.pe.description", "file.pe.file_version", @@ -76,6 +77,7 @@ "host.os.platform", "host.os.version", "host.type", + "process.command_line", "process.Ext.services", "process.Ext.user", "process.Ext.code_signature", @@ -85,6 +87,7 @@ "process.hash.sha256", "process.hash.sha512", "process.name", + "process.parent.executable", "process.parent.hash.md5", "process.parent.hash.sha1", "process.parent.hash.sha256", @@ -97,11 +100,24 @@ "process.pe.product", "process.pgid", "rule.uuid", + "rule.id", + "source.ip", + "source.port", + "destination.ip", + "destination.port", + "registry.path", + "registry.value", + "registry.data.strings", "user.domain", "user.email", "user.hash", "user.id", "Ransomware.feature", "Memory_protection.feature", - "Memory_protection.self_injection" + "Memory_protection.self_injection", + "dll.path", + "dll.code_signature.subject_name", + "dll.pe.original_file_name", + "dns.question.name", + "dns.question.type" ] 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 83006f09a14be..9696604ddf222 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 @@ -1255,4 +1255,346 @@ describe('Exception helpers', () => { ]); }); }); + describe('behavior protection exception items', () => { + test('it should return pre-populated behavior protection items', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + rule: { + id: '123', + }, + process: { + command_line: 'command_line', + executable: 'some file path', + parent: { + executable: 'parent file path', + }, + code_signature: { + subject_name: 'subject-name', + trusted: 'true', + }, + }, + event: { + code: 'behavior', + }, + file: { + path: 'fake-file-path', + name: 'fake-file-name', + }, + source: { + ip: '0.0.0.0', + }, + destination: { + ip: '0.0.0.0', + }, + registry: { + path: 'registry-path', + value: 'registry-value', + data: { + strings: 'registry-strings', + }, + }, + dll: { + path: 'dll-path', + code_signature: { + subject_name: 'dll-code-signature-subject-name', + trusted: 'false', + }, + pe: { + original_file_name: 'dll-pe-original-file-name', + }, + }, + dns: { + question: { + name: 'dns-question-name', + type: 'dns-question-type', + }, + }, + user: { + id: '0987', + }, + }); + + expect(defaultItems[0].entries).toEqual([ + { + id: '123', + field: 'rule.id', + operator: 'included' as const, + type: 'match' as const, + value: '123', + }, + { + id: '123', + field: 'process.executable.caseless', + operator: 'included' as const, + type: 'match' as const, + value: 'some file path', + }, + { + id: '123', + field: 'process.command_line', + operator: 'included' as const, + type: 'match' as const, + value: 'command_line', + }, + { + id: '123', + field: 'process.parent.executable', + operator: 'included' as const, + type: 'match' as const, + value: 'parent file path', + }, + { + id: '123', + field: 'process.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: 'subject-name', + }, + { + id: '123', + field: 'file.path', + operator: 'included' as const, + type: 'match' as const, + value: 'fake-file-path', + }, + { + id: '123', + field: 'file.name', + operator: 'included' as const, + type: 'match' as const, + value: 'fake-file-name', + }, + { + id: '123', + field: 'source.ip', + operator: 'included' as const, + type: 'match' as const, + value: '0.0.0.0', + }, + { + id: '123', + field: 'destination.ip', + operator: 'included' as const, + type: 'match' as const, + value: '0.0.0.0', + }, + { + id: '123', + field: 'registry.path', + operator: 'included' as const, + type: 'match' as const, + value: 'registry-path', + }, + { + id: '123', + field: 'registry.value', + operator: 'included' as const, + type: 'match' as const, + value: 'registry-value', + }, + { + id: '123', + field: 'registry.data.strings', + operator: 'included' as const, + type: 'match' as const, + value: 'registry-strings', + }, + { + id: '123', + field: 'dll.path', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-path', + }, + { + id: '123', + field: 'dll.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-code-signature-subject-name', + }, + { + id: '123', + field: 'dll.pe.original_file_name', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-pe-original-file-name', + }, + { + id: '123', + field: 'dns.question.name', + operator: 'included' as const, + type: 'match' as const, + value: 'dns-question-name', + }, + { + id: '123', + field: 'dns.question.type', + operator: 'included' as const, + type: 'match' as const, + value: 'dns-question-type', + }, + { + id: '123', + field: 'user.id', + operator: 'included' as const, + type: 'match' as const, + value: '0987', + }, + ]); + }); + test('it should return pre-populated behavior protection fields and skip empty', () => { + const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', { + _id: '123', + rule: { + id: '123', + }, + process: { + // command_line: 'command_line', intentionally left commented + executable: 'some file path', + parent: { + executable: 'parent file path', + }, + code_signature: { + subject_name: 'subject-name', + trusted: 'true', + }, + }, + event: { + code: 'behavior', + }, + file: { + // path: 'fake-file-path', intentionally left commented + name: 'fake-file-name', + }, + source: { + ip: '0.0.0.0', + }, + destination: { + ip: '0.0.0.0', + }, + // intentionally left commented + // registry: { + // path: 'registry-path', + // value: 'registry-value', + // data: { + // strings: 'registry-strings', + // }, + // }, + dll: { + path: 'dll-path', + code_signature: { + subject_name: 'dll-code-signature-subject-name', + trusted: 'false', + }, + pe: { + original_file_name: 'dll-pe-original-file-name', + }, + }, + dns: { + question: { + name: 'dns-question-name', + type: 'dns-question-type', + }, + }, + user: { + id: '0987', + }, + }); + + expect(defaultItems[0].entries).toEqual([ + { + id: '123', + field: 'rule.id', + operator: 'included' as const, + type: 'match' as const, + value: '123', + }, + { + id: '123', + field: 'process.executable.caseless', + operator: 'included' as const, + type: 'match' as const, + value: 'some file path', + }, + { + id: '123', + field: 'process.parent.executable', + operator: 'included' as const, + type: 'match' as const, + value: 'parent file path', + }, + { + id: '123', + field: 'process.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: 'subject-name', + }, + { + id: '123', + field: 'file.name', + operator: 'included' as const, + type: 'match' as const, + value: 'fake-file-name', + }, + { + id: '123', + field: 'source.ip', + operator: 'included' as const, + type: 'match' as const, + value: '0.0.0.0', + }, + { + id: '123', + field: 'destination.ip', + operator: 'included' as const, + type: 'match' as const, + value: '0.0.0.0', + }, + { + id: '123', + field: 'dll.path', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-path', + }, + { + id: '123', + field: 'dll.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-code-signature-subject-name', + }, + { + id: '123', + field: 'dll.pe.original_file_name', + operator: 'included' as const, + type: 'match' as const, + value: 'dll-pe-original-file-name', + }, + { + id: '123', + field: 'dns.question.name', + operator: 'included' as const, + type: 'match' as const, + value: 'dns-question-name', + }, + { + id: '123', + field: 'dns.question.type', + operator: 'included' as const, + type: 'match' as const, + value: 'dns-question-type', + }, + { + id: '123', + field: 'user.id', + operator: 'included' as const, + type: 'match' as const, + value: '0987', + }, + ]); + }); + }); }); 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 62250a0933ffb..3d219b90a2fc8 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 @@ -655,6 +655,136 @@ export const getPrepopulatedMemoryShellcodeException = ({ }; }; +export const getPrepopulatedBehaviorException = ({ + listId, + ruleName, + eventCode, + listNamespace = 'agnostic', + alertEcsData, +}: { + listId: string; + listNamespace?: NamespaceType; + ruleName: string; + eventCode: string; + alertEcsData: Flattened; +}): ExceptionsBuilderExceptionItem => { + const { process } = alertEcsData; + const entries = filterEmptyExceptionEntries([ + { + field: 'rule.id', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.rule?.id ?? '', + }, + { + field: 'process.executable.caseless', + operator: 'included' as const, + type: 'match' as const, + value: process?.executable ?? '', + }, + { + field: 'process.command_line', + operator: 'included' as const, + type: 'match' as const, + value: process?.command_line ?? '', + }, + { + field: 'process.parent.executable', + operator: 'included' as const, + type: 'match' as const, + value: process?.parent?.executable ?? '', + }, + { + field: 'process.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: process?.code_signature?.subject_name ?? '', + }, + { + field: 'file.path', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.file?.path ?? '', + }, + { + field: 'file.name', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.file?.name ?? '', + }, + { + field: 'source.ip', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.source?.ip ?? '', + }, + { + field: 'destination.ip', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.destination?.ip ?? '', + }, + { + field: 'registry.path', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.registry?.path ?? '', + }, + { + field: 'registry.value', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.registry?.value ?? '', + }, + { + field: 'registry.data.strings', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.registry?.data?.strings ?? '', + }, + { + field: 'dll.path', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.dll?.path ?? '', + }, + { + field: 'dll.code_signature.subject_name', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.dll?.code_signature?.subject_name ?? '', + }, + { + field: 'dll.pe.original_file_name', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.dll?.pe?.original_file_name ?? '', + }, + { + field: 'dns.question.name', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.dns?.question?.name ?? '', + }, + { + field: 'dns.question.type', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.dns?.question?.type ?? '', + }, + { + field: 'user.id', + operator: 'included' as const, + type: 'match' as const, + value: alertEcsData.user?.id ?? '', + }, + ]); + 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 */ @@ -697,6 +827,15 @@ export const defaultEndpointExceptionItems = ( const eventCode = alertEvent?.code ?? ''; switch (eventCode) { + case 'behavior': + return [ + getPrepopulatedBehaviorException({ + listId, + ruleName, + eventCode, + alertEcsData, + }), + ]; case 'memory_signature': return [ getPrepopulatedMemorySignatureException({