From e69c7823c05b596fc7ede7d89473db2298a1e718 Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Thu, 8 May 2025 13:22:42 -0400
Subject: [PATCH] [Security Solution][Endpoint Exceptions] Fixes bug where
behavior alerts do not show nested code signatures with subject name and
trusted field (#212325)
## Summary
When navigating to the endpoint exceptions form from an alert, we
pre-populate certain exceptions fields based on the type of alert. There
was a bug for behavior alerts where we did not use the proper nested
`code_signature` field for windows and mac endpoints. Instead of showing
the nested `code_signature` field that has the `subject_name` and
`trusted` sub-fields, we only showed non-nested `code_signature subject
field. This PR also refactors the code to account for the following
behaviors that we want:
- [x] If `field.Ext.code_signature` is present, we want to use the
nested `code_signature` subject field with the `subject_name` and
`trusted` sub-fields for
- [x] If `field.Ext.code_signature` is not present, we will default to
the non-nested `field.code_signature.subject_name` and
`field.code_signature.trusted` field pair.
- [x] We will only show non-empty pre-populated values and also only
code signature values with the `trusted` field set to `true`
- [x] Pre-populated code signature fields are only present in windows
and mac OSes.
- [x] Behavior, ransomware and default alerts had the code_signature
adjustments
- [x] Previously the code duplicated a set of the pre-populated fields
PER code signature. Now, each pre-populated field is only shown once,
followed by all valid code_signatures.
- [x] Does not allow duplicate code signatures
# SCREENSHOTS
Behavior alert w/ nested `process.Ext.code_signature` and non-nested
`dll.code_signature` fields

Malware alert w/ nested `file.Ext.code_signature`
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine
(cherry picked from commit 76e256ccffd6fb59fd4212be68e5a1da9915a9fb)
---
.../kbn-securitysolution-ecs/src/dll/index.ts | 3 +-
.../src/file/index.ts | 4 +-
.../common/endpoint/generate_data.ts | 58 +-
.../rule_exceptions/utils/helpers.test.tsx | 939 +++++++++++++++---
.../rule_exceptions/utils/helpers.tsx | 349 ++++---
.../rule_exceptions/utils/types.ts | 2 +-
6 files changed, 1038 insertions(+), 317 deletions(-)
diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/dll/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/dll/index.ts
index 956ef7bc9ab99..e286dd65ba55d 100644
--- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/dll/index.ts
+++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/dll/index.ts
@@ -7,10 +7,11 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import type { CodeSignature } from '../file';
+import type { CodeSignature, Ext } from '../file';
import type { ProcessPe } from '../process';
export interface DllEcs {
+ Ext?: Ext;
path?: string;
code_signature?: CodeSignature;
pe?: ProcessPe;
diff --git a/src/platform/packages/shared/kbn-securitysolution-ecs/src/file/index.ts b/src/platform/packages/shared/kbn-securitysolution-ecs/src/file/index.ts
index daf4226679591..ba6a6542e18e8 100644
--- a/src/platform/packages/shared/kbn-securitysolution-ecs/src/file/index.ts
+++ b/src/platform/packages/shared/kbn-securitysolution-ecs/src/file/index.ts
@@ -14,7 +14,7 @@ interface Original {
export interface CodeSignature {
subject_name: string[];
- trusted: string[];
+ trusted: boolean;
}
export interface Token {
@@ -72,6 +72,8 @@ export interface FileEcs {
type?: string[];
+ code_signature?: CodeSignature;
+
device?: string[];
inode?: string[];
diff --git a/x-pack/solutions/security/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/solutions/security/plugins/security_solution/common/endpoint/generate_data.ts
index b4ee20f3d5ba2..486d48d465ed2 100644
--- a/x-pack/solutions/security/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/solutions/security/plugins/security_solution/common/endpoint/generate_data.ts
@@ -529,6 +529,10 @@ export class EndpointDocGenerator extends BaseDataGenerator {
trusted: false,
subject_name: 'bad signer',
},
+ {
+ trusted: true,
+ subject_name: 'a good signer',
+ },
],
malware_classification: {
identifier: 'endpointpe',
@@ -900,6 +904,10 @@ export class EndpointDocGenerator extends BaseDataGenerator {
trusted: false,
subject_name: 'bad signer',
},
+ {
+ trusted: true,
+ subject_name: 'good signer',
+ },
],
user: 'SYSTEM',
token: {
@@ -921,36 +929,34 @@ export class EndpointDocGenerator extends BaseDataGenerator {
* Returns the default DLLs used in alerts
*/
private getAlertsDefaultDll() {
- return [
- {
- pe: {
- architecture: 'x64',
- },
- code_signature: {
- subject_name: 'Cybereason Inc',
- trusted: true,
- },
+ return {
+ pe: {
+ architecture: 'x64',
+ },
+ code_signature: {
+ subject_name: 'Cybereason Inc',
+ trusted: true,
+ },
- hash: {
- md5: '1f2d082566b0fc5f2c238a5180db7451',
- sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
- sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
- },
+ hash: {
+ md5: '1f2d082566b0fc5f2c238a5180db7451',
+ sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d',
+ sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2',
+ },
- path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
- Ext: {
- compile_time: 1534424710,
- mapped_address: 5362483200,
- mapped_size: 0,
- malware_classification: {
- identifier: 'Whitelisted',
- score: 0,
- threshold: 0,
- version: '3.0.0',
- },
+ path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe',
+ Ext: {
+ compile_time: 1534424710,
+ mapped_address: 5362483200,
+ mapped_size: 0,
+ malware_classification: {
+ identifier: 'Whitelisted',
+ score: 0,
+ threshold: 0,
+ version: '3.0.0',
},
},
- ];
+ };
}
/**
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx
index d404cba0257a2..412c2043e43de 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx
@@ -22,6 +22,7 @@ import {
defaultEndpointExceptionItems,
getFileCodeSignature,
getProcessCodeSignature,
+ getDllCodeSignature,
retrieveAlertOsTypes,
getCodeSignatureValue,
buildRuleExceptionWithConditions,
@@ -249,13 +250,40 @@ describe('Exception helpers', () => {
});
describe('#getCodeSignatureValue', () => {
- test('it should return empty string if code_signature nested value are undefined', () => {
+ test('it should return undefined if code_signature nested value are undefined', () => {
// Using the unsafe casting because with our types this shouldn't be possible but there have been issues with old data having undefined values in these fields
const payload = [{ trusted: undefined, subject_name: undefined }] as unknown as Flattened<
CodeSignature[]
>;
- const result = getCodeSignatureValue(payload);
- expect(result).toEqual([{ trusted: '', subjectName: '' }]);
+ const result = getCodeSignatureValue(payload, 'field');
+ expect(result).toEqual([undefined]);
+ });
+
+ test('it should not return duplicate code signature entries', () => {
+ const payload = [
+ { subject_name: 'asdf', trusted: true },
+ { subject_name: 'asdf', trusted: true },
+ ];
+ expect(getCodeSignatureValue(payload, 'field')).toEqual([
+ {
+ field: 'field',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'asdf',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ]);
});
});
@@ -380,56 +408,26 @@ describe('Exception helpers', () => {
_id: 'test-id',
file: { path: 'some-file-path', hash: { sha256: 'some-hash' } },
};
- test('it returns prepopulated fields with empty values', () => {
+ test('it does not return prepopulated fields with empty values', () => {
const prepopulatedItem = getPrepopulatedEndpointException({
listId: 'some_id',
name: 'my rule',
- codeSignature: { subjectName: '', trusted: '' },
eventCode: '',
alertEcsData: { ...alertDataMock, file: { path: '', hash: { sha256: '' } } },
});
- expect(prepopulatedItem.entries).toEqual([
- {
- entries: [
- { id: '123', field: 'subject_name', operator: 'included', type: 'match', value: '' },
- { id: '123', field: 'trusted', operator: 'included', type: 'match', value: '' },
- ],
- field: 'file.Ext.code_signature',
- type: 'nested',
- id: '123',
- },
- { id: '123', field: 'file.path.caseless', operator: 'included', type: 'match', value: '' },
- { id: '123', field: 'file.hash.sha256', operator: 'included', type: 'match', value: '' },
- { id: '123', field: 'event.code', operator: 'included', type: 'match', value: '' },
- ]);
+ expect(prepopulatedItem.entries).toEqual([]);
});
test('it returns prepopulated items with actual values', () => {
const prepopulatedItem = getPrepopulatedEndpointException({
listId: 'some_id',
name: 'my rule',
- codeSignature: { subjectName: 'someSubjectName', trusted: 'false' },
eventCode: 'some-event-code',
alertEcsData: alertDataMock,
});
expect(prepopulatedItem.entries).toEqual([
- {
- entries: [
- {
- id: '123',
- field: 'subject_name',
- operator: 'included',
- type: 'match',
- value: 'someSubjectName',
- },
- { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' },
- ],
- field: 'file.Ext.code_signature',
- type: 'nested',
- id: '123',
- },
{
id: '123',
field: 'file.path.caseless',
@@ -463,13 +461,32 @@ describe('Exception helpers', () => {
Ext: {
code_signature: {
subject_name: 'some_subject',
- trusted: 'false',
+ trusted: true,
},
},
},
});
- expect(codeSignatures).toEqual([{ subjectName: 'some_subject', trusted: 'false' }]);
+ expect(codeSignatures).toEqual([
+ {
+ field: 'file.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ]);
});
test('it works when file.Ext.code_signature is nested type', () => {
@@ -478,36 +495,112 @@ describe('Exception helpers', () => {
file: {
Ext: {
code_signature: [
- { subject_name: 'some_subject', trusted: 'false' },
- { subject_name: 'some_subject_2', trusted: 'true' },
+ { subject_name: 'some_subject', trusted: true },
+ { subject_name: 'some_subject_2', trusted: true },
],
},
},
});
expect(codeSignatures).toEqual([
- { subjectName: 'some_subject', trusted: 'false' },
{
- subjectName: 'some_subject_2',
- trusted: 'true',
+ field: 'file.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ {
+ field: 'file.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject_2',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
},
]);
});
- test('it returns default when file.Ext.code_signatures values are empty', () => {
- const codeSignatures = getFileCodeSignature({
+ test('it returns an exception entry when file.Ext.code_signature is undefined but file.code_signature is defined', () => {
+ const codeSignature = getFileCodeSignature({
+ _id: '123',
+ file: {
+ code_signature: { subject_name: 'some_subject', trusted: true },
+ },
+ });
+
+ expect(codeSignature).toEqual([
+ {
+ field: 'file.code_signature.subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'file.code_signature.trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ]);
+ });
+
+ test('it does not return an exception entry when code signature "trusted: false"', () => {
+ const extCodeSignatures = getFileCodeSignature({
_id: '123',
file: {
Ext: {
- code_signature: { subject_name: '', trusted: '' },
+ code_signature: [
+ { subject_name: 'some_subject', trusted: false },
+ { subject_name: 'some_subject_2', trusted: false },
+ ],
},
},
});
+ const codeSignature = getFileCodeSignature({
+ _id: '123',
+ file: {
+ code_signature: { subject_name: 'some_subject', trusted: false },
+ },
+ });
+
+ expect(extCodeSignatures).toEqual([]);
+ expect(codeSignature).toEqual(undefined);
+ });
+
+ test('it returns undefined when file.Ext.code_signatures values are empty', () => {
+ const codeSignatures = getFileCodeSignature({
+ _id: '123',
+ file: {
+ Ext: {},
+ },
+ });
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(codeSignatures).toEqual(undefined);
});
- test('it returns default when file.Ext.code_signatures is empty array', () => {
+ test('it returns undefined when file.Ext.code_signatures is empty array', () => {
const codeSignatures = getFileCodeSignature({
_id: '123',
file: {
@@ -517,71 +610,166 @@ describe('Exception helpers', () => {
},
});
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(codeSignatures).toEqual(undefined);
});
- test('it returns default when file.Ext.code_signatures does not exist', () => {
+ test('it returns undefined when file.Ext.code_signatures does not exist', () => {
const codeSignatures = getFileCodeSignature({
_id: '123',
});
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(codeSignatures).toEqual(undefined);
});
});
describe('getProcessCodeSignature', () => {
- test('it works when file.Ext.code_signature is an object', () => {
+ test('it works when process.Ext.code_signature is an object', () => {
const codeSignatures = getProcessCodeSignature({
_id: '123',
process: {
Ext: {
code_signature: {
subject_name: 'some_subject',
- trusted: 'false',
+ trusted: true,
},
},
},
});
- expect(codeSignatures).toEqual([{ subjectName: 'some_subject', trusted: 'false' }]);
+ expect(codeSignatures).toEqual([
+ {
+ field: 'process.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ]);
});
- test('it works when file.Ext.code_signature is nested type', () => {
+ test('it works when process.Ext.code_signature is nested type', () => {
const codeSignatures = getProcessCodeSignature({
_id: '123',
process: {
Ext: {
code_signature: [
- { subject_name: 'some_subject', trusted: 'false' },
- { subject_name: 'some_subject_2', trusted: 'true' },
+ { subject_name: 'some_subject', trusted: true },
+ { subject_name: 'some_subject_2', trusted: true },
],
},
},
});
expect(codeSignatures).toEqual([
- { subjectName: 'some_subject', trusted: 'false' },
{
- subjectName: 'some_subject_2',
- trusted: 'true',
+ field: 'process.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ {
+ field: 'process.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject_2',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
},
]);
});
- test('it returns default when file.Ext.code_signatures values are empty', () => {
- const codeSignatures = getProcessCodeSignature({
+ test('it returns an exception entry when process.Ext.code_signature is undefined but process.code_signature is defined', () => {
+ const codeSignature = getProcessCodeSignature({
+ _id: '123',
+ process: {
+ code_signature: { subject_name: 'some_subject', trusted: true },
+ },
+ });
+
+ expect(codeSignature).toEqual([
+ {
+ field: 'process.code_signature.subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'process.code_signature.trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ]);
+ });
+
+ test('it does not return an exception entry when code signature "trusted: false"', () => {
+ const extCodeSignatures = getProcessCodeSignature({
_id: '123',
process: {
Ext: {
- code_signature: { subject_name: '', trusted: '' },
+ code_signature: [
+ { subject_name: 'some_subject', trusted: false },
+ { subject_name: 'some_subject_2', trusted: false },
+ ],
},
},
});
+ const codeSignature = getProcessCodeSignature({
+ _id: '123',
+ file: {
+ code_signature: { subject_name: 'some_subject', trusted: false },
+ },
+ });
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(extCodeSignatures).toEqual([]);
+ expect(codeSignature).toEqual(undefined);
});
- test('it returns default when file.Ext.code_signatures is empty array', () => {
+ test('it returns undefined when process.Ext.code_signatures values are empty', () => {
+ const codeSignatures = getProcessCodeSignature({
+ _id: '123',
+ process: {
+ Ext: {},
+ },
+ });
+
+ expect(codeSignatures).toEqual(undefined);
+ });
+
+ test('it returns undefined when process.Ext.code_signatures is empty array', () => {
const codeSignatures = getProcessCodeSignature({
_id: '123',
process: {
@@ -591,15 +779,184 @@ describe('Exception helpers', () => {
},
});
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(codeSignatures).toEqual(undefined);
});
- test('it returns default when file.Ext.code_signatures does not exist', () => {
+ test('it returns undefined when process.Ext.code_signatures does not exist', () => {
const codeSignatures = getProcessCodeSignature({
_id: '123',
});
- expect(codeSignatures).toEqual([{ subjectName: '', trusted: '' }]);
+ expect(codeSignatures).toEqual(undefined);
+ });
+ });
+
+ describe('getDllCodeSignature', () => {
+ test('it works when dll.Ext.code_signature is an object', () => {
+ const codeSignatures = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ Ext: {
+ code_signature: {
+ subject_name: 'some_subject',
+ trusted: true,
+ },
+ },
+ },
+ });
+
+ expect(codeSignatures).toEqual([
+ {
+ field: 'dll.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ]);
+ });
+
+ test('it works when dll.Ext.code_signature is nested type', () => {
+ const codeSignatures = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ Ext: {
+ code_signature: [
+ { subject_name: 'some_subject', trusted: true },
+ { subject_name: 'some_subject_2', trusted: true },
+ ],
+ },
+ },
+ });
+
+ expect(codeSignatures).toEqual([
+ {
+ field: 'dll.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ {
+ field: 'dll.Ext.code_signature',
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject_2',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ]);
+ });
+
+ test('it returns an exception entry when dll.Ext.code_signature is undefined but dll.code_signature is defined', () => {
+ const codeSignature = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ code_signature: { subject_name: 'some_subject', trusted: true },
+ },
+ });
+
+ expect(codeSignature).toEqual([
+ {
+ field: 'dll.code_signature.subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'some_subject',
+ },
+ {
+ field: 'dll.code_signature.trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ]);
+ });
+
+ test('it does not return an exception entry when code signature "trusted: false"', () => {
+ const extCodeSignatures = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ Ext: {
+ code_signature: [
+ { subject_name: 'some_subject', trusted: false },
+ { subject_name: 'some_subject_2', trusted: false },
+ ],
+ },
+ },
+ });
+ const codeSignature = getDllCodeSignature({
+ _id: '123',
+ file: {
+ code_signature: { subject_name: 'some_subject', trusted: false },
+ },
+ });
+
+ expect(extCodeSignatures).toEqual([]);
+ expect(codeSignature).toEqual(undefined);
+ });
+
+ test('it returns undefined when dll.Ext.code_signatures values are empty', () => {
+ const codeSignatures = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ Ext: {},
+ },
+ });
+
+ expect(codeSignatures).toEqual(undefined);
+ });
+
+ test('it returns undefined when dll.Ext.code_signatures is empty array', () => {
+ const codeSignatures = getDllCodeSignature({
+ _id: '123',
+ dll: {
+ Ext: {
+ code_signature: [],
+ },
+ },
+ });
+
+ expect(codeSignatures).toEqual(undefined);
+ });
+
+ test('it returns undefined when dll.Ext.code_signatures does not exist', () => {
+ const codeSignatures = getDllCodeSignature({
+ _id: '123',
+ });
+
+ expect(codeSignatures).toEqual(undefined);
});
});
@@ -610,8 +967,8 @@ describe('Exception helpers', () => {
file: {
Ext: {
code_signature: [
- { subject_name: 'some_subject', trusted: 'false' },
- { subject_name: 'some_subject_2', trusted: 'true' },
+ { subject_name: 'some_subject', trusted: false },
+ { subject_name: 'some_subject_2', trusted: true },
],
},
path: 'some file path',
@@ -626,21 +983,6 @@ describe('Exception helpers', () => {
});
expect(defaultItems[0].entries).toEqual([
- {
- entries: [
- {
- id: '123',
- field: 'subject_name',
- operator: 'included',
- type: 'match',
- value: 'some_subject',
- },
- { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' },
- ],
- field: 'file.Ext.code_signature',
- type: 'nested',
- id: '123',
- },
{
id: '123',
field: 'file.path.caseless',
@@ -662,8 +1004,6 @@ describe('Exception helpers', () => {
type: 'match',
value: 'some event code',
},
- ]);
- expect(defaultItems[1].entries).toEqual([
{
entries: [
{
@@ -679,9 +1019,75 @@ describe('Exception helpers', () => {
type: 'nested',
id: '123',
},
+ ]);
+ });
+
+ test('it should skip empty fields and "trusted:false" code signature fields"', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ file: {
+ Ext: {
+ code_signature: [{ subject_name: 'some_subject', trusted: false }],
+ },
+ path: '',
+ hash: {
+ sha256: 'some hash',
+ },
+ },
+ event: {
+ code: 'some event code',
+ },
+ 'event.code': 'some event code',
+ });
+
+ expect(defaultItems[0].entries).toEqual([
+ {
+ id: '123',
+ field: 'file.hash.sha256',
+ operator: 'included',
+ type: 'match',
+ value: 'some hash',
+ },
{
id: '123',
- field: 'file.path.caseless',
+ field: 'event.code',
+ operator: 'included',
+ type: 'match',
+ value: 'some event code',
+ },
+ ]);
+ });
+
+ test('it should not return code signature fields for linux hosts', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ host: {
+ os: {
+ name: 'Linux',
+ },
+ },
+ file: {
+ Ext: {
+ code_signature: [
+ { subject_name: 'some_subject', trusted: true },
+ { subject_name: 'some_subject_2', trusted: true },
+ ],
+ },
+ path: 'some file path',
+ hash: {
+ sha256: 'some hash',
+ },
+ },
+ event: {
+ code: 'some event code',
+ },
+ 'event.code': 'some event code',
+ });
+
+ expect(defaultItems[0].entries).toEqual([
+ {
+ id: '123',
+ field: 'file.path',
operator: 'included',
type: 'match',
value: 'some file path',
@@ -710,8 +1116,8 @@ describe('Exception helpers', () => {
process: {
Ext: {
code_signature: [
- { subject_name: 'some_subject', trusted: 'false' },
- { subject_name: 'some_subject_2', trusted: 'true' },
+ { subject_name: 'some_subject', trusted: false },
+ { subject_name: 'some_subject_2', trusted: true },
],
},
executable: 'some file path',
@@ -729,6 +1135,34 @@ describe('Exception helpers', () => {
});
expect(defaultItems[0].entries).toEqual([
+ {
+ id: '123',
+ field: 'process.executable',
+ operator: 'included',
+ type: 'match',
+ value: 'some file path',
+ },
+ {
+ id: '123',
+ field: 'process.hash.sha256',
+ operator: 'included',
+ type: 'match',
+ value: 'some hash',
+ },
+ {
+ id: '123',
+ field: 'Ransomware.feature',
+ operator: 'included',
+ type: 'match',
+ value: 'some ransomware feature',
+ },
+ {
+ id: '123',
+ field: 'event.code',
+ operator: 'included',
+ type: 'match',
+ value: 'ransomware',
+ },
{
entries: [
{
@@ -736,14 +1170,39 @@ describe('Exception helpers', () => {
field: 'subject_name',
operator: 'included',
type: 'match',
- value: 'some_subject',
+ value: 'some_subject_2',
},
- { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'false' },
+ { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'true' },
],
field: 'process.Ext.code_signature',
type: 'nested',
id: '123',
},
+ ]);
+ });
+
+ test('it should skip empty fields and "trusted:false" code signature fields', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ process: {
+ Ext: {
+ code_signature: [{ subject_name: 'some_subject', trusted: false }],
+ },
+ executable: 'some file path',
+ hash: {
+ sha256: '',
+ },
+ },
+ Ransomware: {
+ feature: 'some ransomware feature',
+ },
+ event: {
+ code: 'ransomware',
+ },
+ 'event.code': 'ransomware',
+ });
+
+ expect(defaultItems[0].entries).toEqual([
{
id: '123',
field: 'process.executable',
@@ -753,42 +1212,48 @@ describe('Exception helpers', () => {
},
{
id: '123',
- field: 'process.hash.sha256',
+ field: 'Ransomware.feature',
operator: 'included',
type: 'match',
- value: 'some hash',
+ value: 'some ransomware feature',
},
{
id: '123',
- field: 'Ransomware.feature',
+ field: 'event.code',
operator: 'included',
type: 'match',
- value: 'some ransomware feature',
+ value: 'ransomware',
+ },
+ ]);
+ });
+
+ it('should not return code signatures for linux hosts', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ host: {
+ os: {
+ name: 'Linux',
+ },
+ },
+ process: {
+ Ext: {
+ code_signature: [{ subject_name: 'some_subject', trusted: true }],
+ },
+ executable: 'some file path',
+ hash: {
+ sha256: 'some hash',
+ },
},
- {
- id: '123',
- field: 'event.code',
- operator: 'included',
- type: 'match',
- value: 'ransomware',
+ Ransomware: {
+ feature: 'some ransomware feature',
},
- ]);
- expect(defaultItems[1].entries).toEqual([
- {
- entries: [
- {
- id: '123',
- field: 'subject_name',
- operator: 'included',
- type: 'match',
- value: 'some_subject_2',
- },
- { id: '123', field: 'trusted', operator: 'included', type: 'match', value: 'true' },
- ],
- field: 'process.Ext.code_signature',
- type: 'nested',
- id: '123',
+ event: {
+ code: 'ransomware',
},
+ 'event.code': 'ransomware',
+ });
+
+ expect(defaultItems[0].entries).toEqual([
{
id: '123',
field: 'process.executable',
@@ -1077,7 +1542,7 @@ describe('Exception helpers', () => {
},
code_signature: {
subject_name: 'subject-name',
- trusted: 'true',
+ trusted: true,
},
},
event: {
@@ -1105,7 +1570,7 @@ describe('Exception helpers', () => {
path: 'dll-path',
code_signature: {
subject_name: 'dll-code-signature-subject-name',
- trusted: 'false',
+ trusted: true,
},
pe: {
original_file_name: 'dll-pe-original-file-name',
@@ -1151,13 +1616,6 @@ describe('Exception helpers', () => {
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',
@@ -1214,13 +1672,6 @@ describe('Exception helpers', () => {
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',
@@ -1249,9 +1700,37 @@ describe('Exception helpers', () => {
type: 'match' as const,
value: '0987',
},
+ {
+ id: '123',
+ field: 'process.code_signature.subject_name',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'subject-name',
+ },
+ {
+ id: '123',
+ field: 'process.code_signature.trusted',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'true',
+ },
+ {
+ 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.code_signature.trusted',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'true',
+ },
]);
});
- test('it should return pre-populated behavior protection fields and skip empty', () => {
+ test('it should skip empty fields and "trusted: false" code signature fields', () => {
const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
_id: '123',
rule: {
@@ -1265,7 +1744,7 @@ describe('Exception helpers', () => {
},
code_signature: {
subject_name: 'subject-name',
- trusted: 'true',
+ trusted: true,
},
},
event: {
@@ -1294,7 +1773,7 @@ describe('Exception helpers', () => {
path: 'dll-path',
code_signature: {
subject_name: 'dll-code-signature-subject-name',
- trusted: 'false',
+ trusted: false,
},
pe: {
original_file_name: 'dll-pe-original-file-name',
@@ -1333,6 +1812,62 @@ describe('Exception helpers', () => {
type: 'match' as const,
value: 'parent 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: 'dll.path',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'dll-path',
+ },
+ {
+ 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',
+ },
{
id: '123',
field: 'process.code_signature.subject_name',
@@ -1340,6 +1875,116 @@ describe('Exception helpers', () => {
type: 'match' as const,
value: 'subject-name',
},
+ {
+ id: '123',
+ field: 'process.code_signature.trusted',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'true',
+ },
+ ]);
+ });
+
+ test('it should not return code signature fields for linux hosts', () => {
+ const defaultItems = defaultEndpointExceptionItems('list_id', 'my_rule', {
+ _id: '123',
+ rule: {
+ id: '123',
+ },
+ host: {
+ os: {
+ name: 'Linux',
+ },
+ },
+ 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',
+ },
+ '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: true,
+ },
+ 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: 'file.path',
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: 'fake-file-path',
+ },
{
id: '123',
field: 'file.name',
@@ -1363,17 +2008,31 @@ describe('Exception helpers', () => {
},
{
id: '123',
- field: 'dll.path',
+ field: 'registry.path',
operator: 'included' as const,
type: 'match' as const,
- value: 'dll-path',
+ value: 'registry-path',
},
{
id: '123',
- field: 'dll.code_signature.subject_name',
+ field: 'registry.value',
operator: 'included' as const,
type: 'match' as const,
- value: 'dll-code-signature-subject-name',
+ 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',
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
index d879b79a042f7..0953c35f8bd42 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx
@@ -26,6 +26,7 @@ import type {
UpdateExceptionListItemSchema,
ExceptionListSchema,
EntriesArray,
+ EntriesArrayOrUndefined,
} from '@kbn/securitysolution-io-ts-list-types';
import {
ListOperatorTypeEnum,
@@ -41,11 +42,17 @@ import type {
import { getNewExceptionItem, addIdToEntries } from '@kbn/securitysolution-list-utils';
import { removeIdFromExceptionItemsEntries } from '@kbn/securitysolution-list-hooks';
-import type { EcsSecurityExtension as Ecs, CodeSignature } from '@kbn/securitysolution-ecs';
+import type {
+ EcsSecurityExtension as Ecs,
+ CodeSignature,
+ FileEcs,
+ DllEcs,
+ ProcessEcs,
+} from '@kbn/securitysolution-ecs';
import type { EventSummaryField } from '../../../common/components/event_details/types';
import { getHighlightedFieldsToDisplay } from '../../../common/components/event_details/get_alert_summary_rows';
import * as i18n from './translations';
-import type { AlertData, Flattened } from './types';
+import type { AlertData, Flattened, FlattenedCodeSignature } from './types';
import { WithCopyToClipboard } from '../../../common/lib/clipboard/with_copy_to_clipboard';
import { ALERT_ORIGINAL_EVENT } from '../../../../common/field_maps/field_names';
@@ -292,78 +299,150 @@ export const lowercaseHashValues = (
};
/**
- * Returns the value for `file.Ext.code_signature` which
- * can be an object or array of objects
+ * Generic function to get code signature entries from any entity
*/
-export const getFileCodeSignature = (
- alertData: Flattened
-): Array<{ subjectName: string; trusted: string }> => {
- const { file } = alertData;
- const codeSignature = file && file.Ext && file.Ext.code_signature;
+export const getEntityCodeSignature = <
+ T extends {
+ Ext?: { code_signature?: Flattened };
+ code_signature?: CodeSignature;
+ }
+>(
+ entity: Flattened | T | undefined,
+ fieldPrefix: string
+): EntriesArrayOrUndefined => {
+ if (!entity) return undefined;
- return getCodeSignatureValue(codeSignature);
+ // Check Ext.code_signature first
+ if (entity.Ext?.code_signature) {
+ return getCodeSignatureValue(entity.Ext.code_signature, `${fieldPrefix}.Ext.code_signature`);
+ }
+
+ // Then check direct code_signature
+ if (entity.code_signature?.trusted === true) {
+ return [
+ {
+ field: `${fieldPrefix}.code_signature.subject_name`,
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: entity.code_signature?.subject_name.toString() ?? '',
+ },
+ {
+ field: `${fieldPrefix}.code_signature.trusted`,
+ operator: 'included' as const,
+ type: 'match' as const,
+ value: entity.code_signature.trusted.toString(),
+ },
+ ];
+ }
+ return undefined;
};
/**
- * Returns the value for `process.Ext.code_signature` which
- * can be an object or array of objects
+ * Returns an array of exception entries for either
+ * `file.Ext.code_signature` or 'file.code_signature`
+ * as long as the `trusted` field is `true`.
*/
-export const getProcessCodeSignature = (
- alertData: Flattened
-): Array<{ subjectName: string; trusted: string }> => {
- const { process } = alertData;
- const codeSignature = process && process.Ext && process.Ext.code_signature;
- return getCodeSignatureValue(codeSignature);
-};
+export const getFileCodeSignature = (alertData: Flattened): EntriesArrayOrUndefined =>
+ getEntityCodeSignature(alertData.file, 'file');
+
+/**
+ * Returns an array of exception entries for either
+ * `process.Ext.code_signature` or 'process.code_signature`
+ * as long as the `trusted` field is `true`.
+ */
+export const getProcessCodeSignature = (alertData: Flattened): EntriesArrayOrUndefined =>
+ getEntityCodeSignature(alertData.process, 'process');
+
+/**
+ * Returns an array of exception entries for either
+ * `dll.Ext.code_signature` or 'dll.code_signature`
+ * as long as the `trusted` field is `true`.
+ */
+export const getDllCodeSignature = (alertData: Flattened): EntriesArrayOrUndefined =>
+ getEntityCodeSignature(alertData.dll, 'dll');
/**
* Pre 7.10 `Ext.code_signature` fields were mistakenly populated as
* a single object with subject_name and trusted.
*/
export const getCodeSignatureValue = (
- codeSignature: Flattened | Flattened | undefined
-): Array<{ subjectName: string; trusted: string }> => {
+ codeSignature: Flattened | FlattenedCodeSignature[] | undefined,
+ field: string
+): EntryNested[] | undefined => {
if (Array.isArray(codeSignature) && codeSignature.length > 0) {
- return codeSignature.map((signature) => {
- return {
- subjectName: signature?.subject_name ?? '',
- trusted: signature?.trusted?.toString() ?? '',
- };
- });
+ const codeSignatureEntries: EntryNested[] = [];
+ const noDuplicates = new Map();
+ return codeSignature.reduce((acc, signature) => {
+ if (signature?.trusted === true && !noDuplicates.has(signature?.subject_name)) {
+ noDuplicates.set(signature.subject_name, signature.trusted);
+ acc.push({
+ field,
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: signature?.subject_name ?? '',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: signature.trusted.toString(),
+ },
+ ],
+ });
+ }
+ return acc;
+ }, codeSignatureEntries);
} else {
const signature: Flattened | undefined = !Array.isArray(codeSignature)
? codeSignature
: undefined;
-
- return [
- {
- subjectName: signature?.subject_name ?? '',
- trusted: signature?.trusted ?? '',
- },
- ];
+ if (signature?.trusted === true) {
+ return [
+ {
+ field,
+ type: 'nested',
+ entries: [
+ {
+ field: 'subject_name',
+ operator: 'included',
+ type: 'match',
+ value: signature?.subject_name ?? '',
+ },
+ {
+ field: 'trusted',
+ operator: 'included',
+ type: 'match',
+ value: signature.trusted.toString(),
+ },
+ ],
+ },
+ ];
+ }
}
};
-// 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[] = [];
+
+function filterEmptyExceptionEntries(entries: EntriesArray): EntriesArray {
+ const finalEntries: EntriesArray = [];
for (const entry of entries) {
- if (entry.entries !== undefined) {
- entry.entries = entry.entries.filter((el) => el.value !== undefined && el.value.length > 0);
+ if ('entries' in entry && entry.entries !== undefined) {
+ entry.entries = entry.entries.filter(
+ (el) => 'value' in el && el.value !== undefined && el.value.length > 0
+ );
finalEntries.push(entry);
- } else if (entry.value !== undefined && entry.value.length > 0) {
+ } else if ('value' in entry && entry?.value?.length > 0) {
finalEntries.push(entry);
}
}
+
return finalEntries;
}
@@ -373,7 +452,6 @@ function filterEmptyExceptionEntries(entries: T[]): T[
export const getPrepopulatedEndpointException = ({
listId,
name,
- codeSignature,
eventCode,
listNamespace = 'agnostic',
alertEcsData,
@@ -381,21 +459,16 @@ export const getPrepopulatedEndpointException = ({
listId: string;
listNamespace?: NamespaceType;
name: string;
- codeSignature: { subjectName: string; trusted: string };
eventCode: string;
alertEcsData: Flattened;
}): ExceptionsBuilderExceptionItem => {
const { file, host } = alertEcsData;
+ const fileCodeSignature = getFileCodeSignature(alertEcsData);
const filePath = file?.path ?? '';
const sha256Hash = file?.hash?.sha256 ?? '';
const isLinux = host?.os?.name === 'Linux';
- const commonFields: Array<{
- field: string;
- operator: 'excluded' | 'included';
- type: 'match';
- value: string;
- }> = [
+ const commonFields: EntriesArray = [
{
field: isLinux ? 'file.path' : 'file.path.caseless',
operator: 'included',
@@ -416,30 +489,10 @@ export const getPrepopulatedEndpointException = ({
},
];
const entriesToAdd = () => {
- if (isLinux) {
- return addIdToEntries(commonFields);
+ if (!isLinux && fileCodeSignature !== undefined) {
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields.concat(fileCodeSignature)));
} else {
- return addIdToEntries([
- {
- field: 'file.Ext.code_signature',
- type: 'nested',
- entries: [
- {
- field: 'subject_name',
- operator: 'included',
- type: 'match',
- value: codeSignature != null ? codeSignature.subjectName : '',
- },
- {
- field: 'trusted',
- operator: 'included',
- type: 'match',
- value: codeSignature != null ? codeSignature.trusted : '',
- },
- ],
- },
- ...commonFields,
- ]);
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields));
}
};
@@ -455,7 +508,6 @@ export const getPrepopulatedEndpointException = ({
export const getPrepopulatedRansomwareException = ({
listId,
name,
- codeSignature,
eventCode,
listNamespace = 'agnostic',
alertEcsData,
@@ -463,60 +515,54 @@ export const getPrepopulatedRansomwareException = ({
listId: string;
listNamespace?: NamespaceType;
name: string;
- codeSignature: { subjectName: string; trusted: string };
eventCode: string;
alertEcsData: Flattened;
}): ExceptionsBuilderExceptionItem => {
- const { process, Ransomware } = alertEcsData;
+ const { process, Ransomware, host } = alertEcsData;
+ const processCodeSignature = getProcessCodeSignature(alertEcsData);
const sha256Hash = process?.hash?.sha256 ?? '';
const executable = process?.executable ?? '';
const ransomwareFeature = Ransomware?.feature ?? '';
+ const isLinux = host?.os?.name === 'Linux';
+
+ const commonFields: EntriesArray = [
+ {
+ field: 'process.executable',
+ operator: 'included',
+ type: 'match',
+ value: executable ?? '',
+ },
+ {
+ field: 'process.hash.sha256',
+ operator: 'included',
+ type: 'match',
+ value: sha256Hash ?? '',
+ },
+ {
+ field: 'Ransomware.feature',
+ operator: 'included',
+ type: 'match',
+ value: ransomwareFeature ?? '',
+ },
+ {
+ field: 'event.code',
+ operator: 'included',
+ type: 'match',
+ value: eventCode ?? '',
+ },
+ ];
+
+ const entriesToAdd = () => {
+ if (!isLinux && processCodeSignature !== undefined) {
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields.concat(processCodeSignature)));
+ } else {
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields));
+ }
+ };
+
return {
...getNewExceptionItem({ listId, namespaceType: listNamespace, name }),
- entries: addIdToEntries([
- {
- field: 'process.Ext.code_signature',
- type: 'nested',
- entries: [
- {
- field: 'subject_name',
- operator: 'included',
- type: 'match',
- value: codeSignature != null ? codeSignature.subjectName : '',
- },
- {
- field: 'trusted',
- operator: 'included',
- type: 'match',
- value: codeSignature != null ? codeSignature.trusted : '',
- },
- ],
- },
- {
- field: 'process.executable',
- operator: 'included',
- type: 'match',
- value: executable ?? '',
- },
- {
- field: 'process.hash.sha256',
- operator: 'included',
- type: 'match',
- value: sha256Hash ?? '',
- },
- {
- field: 'Ransomware.feature',
- operator: 'included',
- type: 'match',
- value: ransomwareFeature ?? '',
- },
- {
- field: 'event.code',
- operator: 'included',
- type: 'match',
- value: eventCode ?? '',
- },
- ]),
+ entries: entriesToAdd(),
};
};
@@ -618,6 +664,7 @@ export const getPrepopulatedMemoryShellcodeException = ({
};
};
+/* eslint complexity: ["error", 21]*/
export const getPrepopulatedBehaviorException = ({
listId,
name,
@@ -631,8 +678,11 @@ export const getPrepopulatedBehaviorException = ({
eventCode: string;
alertEcsData: Flattened;
}): ExceptionsBuilderExceptionItem => {
- const { process } = alertEcsData;
- const entries = filterEmptyExceptionEntries([
+ const { process, host } = alertEcsData;
+ const processCodeSignature = getProcessCodeSignature(alertEcsData);
+ const dllCodeSignature = getDllCodeSignature(alertEcsData);
+ const isLinux = host?.os?.name === 'Linux';
+ const commonFields: EntriesArray = [
{
field: 'rule.id',
operator: 'included' as const,
@@ -657,12 +707,6 @@ export const getPrepopulatedBehaviorException = ({
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,
@@ -711,12 +755,6 @@ export const getPrepopulatedBehaviorException = ({
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,
@@ -741,10 +779,28 @@ export const getPrepopulatedBehaviorException = ({
type: 'match' as const,
value: alertEcsData.user?.id ?? '',
},
- ]);
+ ];
+
+ const entriesToAdd = () => {
+ if (!isLinux) {
+ if (processCodeSignature !== undefined && dllCodeSignature !== undefined) {
+ return addIdToEntries(
+ filterEmptyExceptionEntries(commonFields.concat(processCodeSignature, dllCodeSignature))
+ );
+ } else if (processCodeSignature !== undefined) {
+ return addIdToEntries(
+ filterEmptyExceptionEntries(commonFields.concat(processCodeSignature))
+ );
+ } else if (dllCodeSignature !== undefined) {
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields.concat(dllCodeSignature)));
+ }
+ }
+ return addIdToEntries(filterEmptyExceptionEntries(commonFields));
+ };
+
return {
...getNewExceptionItem({ listId, namespaceType: listNamespace, name }),
- entries: addIdToEntries(entries),
+ entries: entriesToAdd(),
};
};
@@ -757,7 +813,6 @@ export const defaultEndpointExceptionItems = (
alertEcsData: Flattened & { 'event.code'?: string }
): ExceptionsBuilderExceptionItem[] => {
const eventCode = alertEcsData['event.code'] ?? alertEcsData.event?.code;
-
switch (eventCode) {
case 'behavior':
return [
@@ -787,26 +842,24 @@ export const defaultEndpointExceptionItems = (
}),
];
case 'ransomware':
- return getProcessCodeSignature(alertEcsData).map((codeSignature) =>
+ return [
getPrepopulatedRansomwareException({
listId,
name,
eventCode,
- codeSignature,
alertEcsData,
- })
- );
+ }),
+ ];
default:
// By default return the standard prepopulated Endpoint Exception fields
- return getFileCodeSignature(alertEcsData).map((codeSignature) =>
+ return [
getPrepopulatedEndpointException({
listId,
name,
eventCode: eventCode ?? '',
- codeSignature,
alertEcsData,
- })
- );
+ }),
+ ];
}
};
diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/types.ts b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/types.ts
index 4e87e89b2964c..f6f6e5e29f8f1 100644
--- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/types.ts
+++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_exceptions/utils/types.ts
@@ -23,7 +23,7 @@ export interface ExceptionsPagination {
export interface FlattenedCodeSignature {
subject_name: string;
- trusted: string;
+ trusted: boolean;
}
export type Flattened = {