mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Threat match tests for term query (#152071)
Added several tests for terms query rules, which tests that match and term query rules are the same. --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
06c70d96c6
commit
b956d91b10
2 changed files with 311 additions and 262 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get, isEqual } from 'lodash';
|
||||
import { get, isEqual, omit } from 'lodash';
|
||||
import expect from '@kbn/expect';
|
||||
import {
|
||||
ALERT_REASON,
|
||||
|
@ -19,6 +19,7 @@ import {
|
|||
VERSION,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { flattenWithPrefix } from '@kbn/securitysolution-rules';
|
||||
import { ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
|
||||
import { ThreatMatchRuleCreateProps } from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
|
||||
|
||||
|
@ -32,6 +33,7 @@ import {
|
|||
ALERT_ORIGINAL_EVENT_MODULE,
|
||||
ALERT_ORIGINAL_TIME,
|
||||
} from '@kbn/security-solution-plugin/common/field_maps/field_names';
|
||||
import { RuleExecutionStatus } from '@kbn/security-solution-plugin/common/detection_engine/rule_monitoring';
|
||||
import {
|
||||
previewRule,
|
||||
getOpenSignals,
|
||||
|
@ -41,7 +43,6 @@ import {
|
|||
createRule,
|
||||
} from '../../utils';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
const format = (value: unknown): string => JSON.stringify(value, null, 2);
|
||||
|
||||
// Asserts that each expected value is included in the subject, independent of
|
||||
|
@ -54,6 +55,85 @@ const assertContains = (subject: unknown[], expected: unknown[]) =>
|
|||
)
|
||||
);
|
||||
|
||||
const createThreatMatchRule = ({
|
||||
name = 'Query with a rule id',
|
||||
index = ['auditbeat-*'],
|
||||
query = '*:*',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
rule_id = 'rule-1',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
threat_indicator_path = 'threat.indicator',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
threat_query = 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
threat_index = ['auditbeat-*'],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
threat_mapping = [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
override = {},
|
||||
}: {
|
||||
threat_mapping?: ThreatMapping;
|
||||
name?: string;
|
||||
query?: string;
|
||||
rule_id?: string;
|
||||
index?: string[];
|
||||
threat_index?: string[];
|
||||
threat_query?: string;
|
||||
threat_indicator_path?: string;
|
||||
override?: any;
|
||||
} = {}): ThreatMatchRuleCreateProps => ({
|
||||
description: 'Detecting root and admin users',
|
||||
name,
|
||||
severity: 'high',
|
||||
index,
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id,
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query,
|
||||
threat_query,
|
||||
threat_index,
|
||||
threat_mapping,
|
||||
threat_filters: [],
|
||||
threat_indicator_path,
|
||||
...override,
|
||||
});
|
||||
|
||||
function alertsAreTheSame(alertsA: any[], alertsB: any[]): void {
|
||||
const mapAlert = (alert: any) => {
|
||||
return omit(alert._source, [
|
||||
'@timestamp',
|
||||
'kibana.alert.last_detected',
|
||||
'kibana.rule.created_at',
|
||||
'kibana.rule.execution.uuid',
|
||||
'kibana.name.execution.uuid',
|
||||
'kibana.alert.rule.parameters',
|
||||
'kibana.alert.rule.rule_id',
|
||||
'kibana.alert.rule.name',
|
||||
'kibana.alert.rule.created_at',
|
||||
'kibana.alert.rule.updated_at',
|
||||
'kibana.alert.rule.uuid',
|
||||
'kibana.alert.rule.execution.uuid',
|
||||
'kibana.alert.start',
|
||||
'kibana.alert.reason',
|
||||
'kibana.alert.uuid',
|
||||
]);
|
||||
};
|
||||
|
||||
expect(alertsA.map(mapAlert)).to.eql(alertsB.map(mapAlert));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
@ -66,6 +146,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
*/
|
||||
describe('Threat match type rules', () => {
|
||||
before(async () => {
|
||||
// await deleteSignalsIndex(supertest, log);
|
||||
// await deleteAllAlerts(supertest, log);
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
|
||||
});
|
||||
|
||||
|
@ -76,38 +158,19 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
// First 2 test creates a real rule - remaining tests use preview API
|
||||
it('should be able to execute and get 10 signals when doing a specific query (terms query)', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
it('should be able to execute and get all signals when doing a specific query (terms query)', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule();
|
||||
|
||||
const createdRule = await createRule(supertest, log, rule);
|
||||
const alerts = await getOpenSignals(supertest, log, es, createdRule);
|
||||
expect(alerts.hits.hits.length).equal(10);
|
||||
const alerts = await getOpenSignals(
|
||||
supertest,
|
||||
log,
|
||||
es,
|
||||
createdRule,
|
||||
RuleExecutionStatus.succeeded,
|
||||
100
|
||||
);
|
||||
expect(alerts.hits.hits.length).equal(88);
|
||||
const fullSource = alerts.hits.hits.find(
|
||||
(signal) =>
|
||||
(signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
|
||||
|
@ -257,20 +320,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
}),
|
||||
});
|
||||
});
|
||||
it('should be able to execute and get 10 signals when doing a specific query (match query)', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
it('should be able to execute and get all signals when doing a specific query (match query)', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
|
@ -288,12 +339,18 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const createdRule = await createRule(supertest, log, rule);
|
||||
const alerts = await getOpenSignals(supertest, log, es, createdRule);
|
||||
expect(alerts.hits.hits.length).equal(10);
|
||||
const alerts = await getOpenSignals(
|
||||
supertest,
|
||||
log,
|
||||
es,
|
||||
createdRule,
|
||||
RuleExecutionStatus.succeeded,
|
||||
100
|
||||
);
|
||||
expect(alerts.hits.hits.length).equal(88);
|
||||
const fullSource = alerts.hits.hits.find(
|
||||
(signal) =>
|
||||
(signal._source?.[ALERT_ANCESTORS] as Ancestor[])[0].id === '7yJ-B2kBR346wHgnhlMn'
|
||||
|
@ -444,20 +501,64 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
it('terms and match should have the same alerts with pagination', async () => {
|
||||
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
override: {
|
||||
items_per_search: 1,
|
||||
concurrent_searches: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
override: {
|
||||
items_per_search: 1,
|
||||
concurrent_searches: 1,
|
||||
},
|
||||
name: 'Math rule',
|
||||
rule_id: 'rule-2',
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const createdRuleTerm = await createRule(supertest, log, termRule);
|
||||
const createdRuleMatch = await createRule(supertest, log, matchRule);
|
||||
const alertsTerm = await getOpenSignals(
|
||||
supertest,
|
||||
log,
|
||||
es,
|
||||
createdRuleTerm,
|
||||
RuleExecutionStatus.succeeded,
|
||||
100
|
||||
);
|
||||
const alertsMatch = await getOpenSignals(
|
||||
supertest,
|
||||
log,
|
||||
es,
|
||||
createdRuleMatch,
|
||||
RuleExecutionStatus.succeeded,
|
||||
100
|
||||
);
|
||||
|
||||
alertsAreTheSame(alertsTerm.hits.hits, alertsMatch.hits.hits);
|
||||
});
|
||||
|
||||
it('should return 0 matches if the mapping does not match against anything in the mapping', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
|
@ -470,8 +571,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -479,19 +579,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should return 0 signals when using an AND and one of the clauses does not have data', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
|
@ -508,8 +596,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -517,19 +604,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should return 0 signals when using an AND and one of the clauses has a made up value that does not exist', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
type: 'threat_match',
|
||||
index: ['auditbeat-*'],
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
|
@ -546,8 +621,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -557,35 +631,13 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
describe('timeout behavior', () => {
|
||||
// TODO: unskip this and see if we can make it not flaky
|
||||
it.skip('will return an error if a rule execution exceeds the rule interval', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a short interval',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*',
|
||||
threat_query: '*:*', // broad query to take more time
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'host.name',
|
||||
value: 'host.name',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
concurrent_searches: 1,
|
||||
interval: '1s', // short interval
|
||||
items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow
|
||||
};
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
override: {
|
||||
concurrent_searches: 1,
|
||||
interval: '1s', // short interval
|
||||
items_per_search: 1, // iterate only 1 threat item per loop to ensure we're slow
|
||||
},
|
||||
});
|
||||
|
||||
const { logs } = await previewRule({ supertest, rule });
|
||||
expect(logs[0].errors[0]).to.contain('execution has exceeded its allotted interval');
|
||||
|
@ -602,20 +654,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('enriches signals with the single indicator that matched', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*', // narrow events down to 2 with a destination.ip
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
|
@ -627,8 +666,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
threat_query: 'threat.indicator.domain: 159.89.119.67', // narrow things down to indicators with a domain
|
||||
threat_index: ['filebeat-*'],
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -690,18 +730,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('enriches signals with multiple indicators if several matched', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: 'threat.indicator.ip: *',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -715,9 +745,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
|
||||
});
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
expect(previewAlerts.length).equal(1);
|
||||
|
@ -767,18 +795,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('adds a single indicator that matched multiple fields', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'NOT source.port:35326', // specify query to have signals more than treat indicators, but only 1 will match
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: 'threat.indicator.port: 57324 or threat.indicator.ip:45.115.45.3', // narrow our query to a single indicator
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -801,8 +819,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -876,19 +893,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('generates multiple signals with multiple matches', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
threat_language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: '*:*', // narrow our query to a single record that matches two indicators
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
threat_query: '*:*',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -916,8 +921,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -1023,18 +1027,8 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('enriches signals with the single indicator that matched', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'destination.ip:159.89.119.67',
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -1048,14 +1042,36 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'destination.ip:159.89.119.67',
|
||||
threat_query: 'threat.indicator.domain: *', // narrow things down to indicators with a domain
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
value: 'threat.indicator.domain',
|
||||
field: 'destination.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
{
|
||||
value: 'threat.indicator.domain',
|
||||
field: 'destination.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
expect(previewAlerts.length).equal(2);
|
||||
const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule });
|
||||
const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId });
|
||||
const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule });
|
||||
const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId });
|
||||
expect(termPreviewAlerts.length).equal(2);
|
||||
|
||||
const threats = previewAlerts.map((hit) => hit._source?.threat);
|
||||
const threats = termPreviewAlerts.map((hit) => hit._source?.threat);
|
||||
expect(threats).to.eql([
|
||||
{
|
||||
enrichments: [
|
||||
|
@ -1108,21 +1124,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
]);
|
||||
alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts);
|
||||
});
|
||||
|
||||
it('enriches signals with multiple indicators if several matched', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: 'source.port: 57324', // narrow our query to a single record that matches two indicators
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *',
|
||||
threat_query: 'threat.indicator.ip: *',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -1136,14 +1143,36 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'source.port: 57324', // narrow our query to a single record that matches two indicatorsthreat_query: 'threat.indicator.ip: *',
|
||||
threat_query: 'threat.indicator.ip: *',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
value: 'threat.indicator.ip',
|
||||
field: 'source.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
{
|
||||
value: 'threat.indicator.ip',
|
||||
field: 'source.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
expect(previewAlerts.length).equal(1);
|
||||
const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule });
|
||||
const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId });
|
||||
const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule });
|
||||
const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId });
|
||||
expect(termPreviewAlerts.length).equal(1);
|
||||
|
||||
const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{
|
||||
const [threat] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{
|
||||
enrichments: unknown[];
|
||||
}>;
|
||||
|
||||
|
@ -1185,21 +1214,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
alertsAreTheSame(termPreviewAlerts, matchPrevieAlerts);
|
||||
});
|
||||
|
||||
it('adds a single indicator that matched multiple fields', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const termRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'source.port: 57324', // narrow our query to a single record that matches two indicators
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: 'threat.indicator.ip: *',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -1222,18 +1242,57 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
const matchRule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: 'source.port: 57324', // narrow our query to a single record that matches two indicators
|
||||
threat_query: 'threat.indicator.ip: *',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
value: 'threat.indicator.port',
|
||||
field: 'source.port',
|
||||
type: 'mapping',
|
||||
},
|
||||
{
|
||||
value: 'threat.indicator.port',
|
||||
field: 'source.port',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
value: 'threat.indicator.ip',
|
||||
field: 'source.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
{
|
||||
value: 'threat.indicator.ip',
|
||||
field: 'source.ip',
|
||||
type: 'mapping',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
expect(previewAlerts.length).equal(1);
|
||||
const { previewId: termPrevieId } = await previewRule({ supertest, rule: termRule });
|
||||
const termPreviewAlerts = await getPreviewAlerts({ es, previewId: termPrevieId });
|
||||
const { previewId: matchPreviewId } = await previewRule({ supertest, rule: matchRule });
|
||||
const matchPrevieAlerts = await getPreviewAlerts({ es, previewId: matchPreviewId });
|
||||
expect(termPreviewAlerts.length).equal(1);
|
||||
|
||||
const [threat] = previewAlerts.map((hit) => hit._source?.threat) as Array<{
|
||||
const [threatTerm] = termPreviewAlerts.map((hit) => hit._source?.threat) as Array<{
|
||||
enrichments: unknown[];
|
||||
}>;
|
||||
|
||||
assertContains(threat.enrichments, [
|
||||
const [threatMatch] = matchPrevieAlerts.map((hit) => hit._source?.threat) as Array<{
|
||||
enrichments: unknown[];
|
||||
}>;
|
||||
assertContains(threatTerm.enrichments, [
|
||||
{
|
||||
feed: {},
|
||||
indicator: {
|
||||
|
@ -1294,22 +1353,23 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
},
|
||||
},
|
||||
]);
|
||||
const sortEnrichments = (a: any, b: any) => {
|
||||
const atomicA = a.matched.atomic.toString();
|
||||
const atomicB = b.matched.atomic.toString();
|
||||
if (atomicA === atomicB) {
|
||||
return a.indicator.description > b.indicator.description ? 1 : -1;
|
||||
}
|
||||
return atomicA > atomicB ? 1 : -1;
|
||||
};
|
||||
|
||||
expect(threatTerm.enrichments.sort(sortEnrichments)).to.be.eql(
|
||||
threatMatch.enrichments.sort(sortEnrichments)
|
||||
);
|
||||
});
|
||||
|
||||
it('generates multiple signals with multiple matches', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
threat_language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: '(source.port:57324 and source.ip:45.115.45.3) or destination.ip:159.89.119.67', // narrow our query to a single record that matches two indicators
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
threat_query: '*:*',
|
||||
threat_index: ['filebeat-*'], // Mimics indicators from the filebeat MISP module
|
||||
threat_mapping: [
|
||||
|
@ -1337,8 +1397,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId });
|
||||
|
@ -1444,19 +1503,9 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
|
||||
it('should be enriched with host risk score', async () => {
|
||||
const rule: ThreatMatchRuleCreateProps = {
|
||||
description: 'Detecting root and admin users',
|
||||
name: 'Query with a rule id',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'threat_match',
|
||||
risk_score: 55,
|
||||
language: 'kuery',
|
||||
rule_id: 'rule-1',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
const rule: ThreatMatchRuleCreateProps = createThreatMatchRule({
|
||||
query: '*:*',
|
||||
threat_query: 'source.ip: "188.166.120.93"', // narrow things down with a query to a specific source ip
|
||||
threat_index: ['auditbeat-*'], // We use auditbeat as both the matching index and the threat list for simplicity
|
||||
threat_mapping: [
|
||||
// We match host.name against host.name
|
||||
{
|
||||
|
@ -1469,8 +1518,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
],
|
||||
},
|
||||
],
|
||||
threat_filters: [],
|
||||
};
|
||||
});
|
||||
|
||||
const { previewId } = await previewRule({ supertest, rule });
|
||||
const previewAlerts = await getPreviewAlerts({ es, previewId, size: 100 });
|
||||
|
|
|
@ -117,5 +117,6 @@
|
|||
"@kbn/security-api-integration-helpers",
|
||||
"@kbn/alerts-as-data-utils",
|
||||
"@kbn/discover-plugin",
|
||||
"@kbn/securitysolution-io-ts-alerting-types",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue