mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Fixes threat field appearing as modified when reset to base version value (#208530)
**Fixes https://github.com/elastic/kibana/issues/208251** ## Summary This bug was caused by the local generated MITRE data we have stored in `x-pack/solutions/security/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts` having an inconsistency in the way its reference urls were written compared to the TRADE team's prebuilt rule packages. The trailing backslash was present in the prebuilt rule packages (and added by browsers) but not in the url field from the `.json` file we scrape the MITRE data from in our script. For example, this is the url from the script: ``` https://attack.mitre.org/techniques/T1078/004 ``` and this is the url directly from the rule package: ``` https://attack.mitre.org/techniques/T1078/004/ ``` This PR adds a normalization function that adds a trailing backslash to the comparison string for the diff algorithm if it doesn't already exist. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
5a18f66ed6
commit
6f55501a75
4 changed files with 803 additions and 671 deletions
|
@ -12,6 +12,87 @@ import { extractThreatArray } from './extract_threat_array';
|
|||
const mockThreat = getThreatMock()[0];
|
||||
|
||||
describe('extractThreatArray', () => {
|
||||
it('normalizes url ending backslashes', () => {
|
||||
const mockRule = {
|
||||
...getRulesSchemaMock(),
|
||||
threat: [
|
||||
{
|
||||
...mockThreat,
|
||||
tactic: {
|
||||
...mockThreat.tactic,
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000',
|
||||
},
|
||||
technique: [
|
||||
{
|
||||
...mockThreat.technique![0],
|
||||
reference: 'https://attack.mitre.org/techniques/T0000/',
|
||||
subtechnique: [
|
||||
{
|
||||
...mockThreat.technique![0].subtechnique![0],
|
||||
reference: 'https://attack.mitre.org/techniques/T0000/000',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
const normalizedThreatArray = extractThreatArray(mockRule);
|
||||
|
||||
expect(normalizedThreatArray).toEqual([
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
id: 'TA0000',
|
||||
name: 'test tactic',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000/',
|
||||
},
|
||||
technique: [
|
||||
{
|
||||
id: 'T0000',
|
||||
name: 'test technique',
|
||||
reference: 'https://attack.mitre.org/techniques/T0000/',
|
||||
subtechnique: [
|
||||
{
|
||||
id: 'T0000.000',
|
||||
name: 'test subtechnique',
|
||||
reference: 'https://attack.mitre.org/techniques/T0000/000/',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('normalizes url ending backslashes with query strings', () => {
|
||||
const mockRule = {
|
||||
...getRulesSchemaMock(),
|
||||
threat: [
|
||||
{
|
||||
...mockThreat,
|
||||
tactic: {
|
||||
...mockThreat.tactic,
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000?query=test',
|
||||
},
|
||||
technique: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
const normalizedThreatArray = extractThreatArray(mockRule);
|
||||
|
||||
expect(normalizedThreatArray).toEqual([
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
id: 'TA0000',
|
||||
name: 'test tactic',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0000/?query=test',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('trims empty technique fields from threat object', () => {
|
||||
const mockRule = { ...getRulesSchemaMock(), threat: [{ ...mockThreat, technique: [] }] };
|
||||
const normalizedThreatArray = extractThreatArray(mockRule);
|
||||
|
|
|
@ -8,21 +8,56 @@
|
|||
import type {
|
||||
RuleResponse,
|
||||
ThreatArray,
|
||||
ThreatSubtechnique,
|
||||
ThreatTechnique,
|
||||
} from '../../../api/detection_engine/model/rule_schema';
|
||||
|
||||
export const extractThreatArray = (rule: RuleResponse): ThreatArray =>
|
||||
rule.threat.map((threat) => {
|
||||
if (threat.technique && threat.technique.length) {
|
||||
return { ...threat, technique: trimTechniqueArray(threat.technique) };
|
||||
return {
|
||||
...threat,
|
||||
tactic: { ...threat.tactic, reference: normalizeThreatReference(threat.tactic.reference) },
|
||||
technique: trimTechniqueArray(threat.technique),
|
||||
};
|
||||
}
|
||||
return { ...threat, technique: undefined }; // If `technique` is an empty array, remove the field from the `threat` object
|
||||
return {
|
||||
...threat,
|
||||
tactic: { ...threat.tactic, reference: normalizeThreatReference(threat.tactic.reference) },
|
||||
technique: undefined,
|
||||
}; // If `technique` is an empty array, remove the field from the `threat` object
|
||||
});
|
||||
|
||||
const trimTechniqueArray = (techniqueArray: ThreatTechnique[]): ThreatTechnique[] => {
|
||||
return techniqueArray.map((technique) => ({
|
||||
...technique,
|
||||
reference: normalizeThreatReference(technique.reference),
|
||||
subtechnique:
|
||||
technique.subtechnique && technique.subtechnique.length ? technique.subtechnique : undefined, // If `subtechnique` is an empty array, remove the field from the `technique` object
|
||||
technique.subtechnique && technique.subtechnique.length
|
||||
? trimSubtechniqueArray(technique.subtechnique)
|
||||
: undefined, // If `subtechnique` is an empty array, remove the field from the `technique` object
|
||||
}));
|
||||
};
|
||||
|
||||
const trimSubtechniqueArray = (subtechniqueArray: ThreatSubtechnique[]): ThreatSubtechnique[] => {
|
||||
return subtechniqueArray.map((subtechnique) => ({
|
||||
...subtechnique,
|
||||
reference: normalizeThreatReference(subtechnique.reference),
|
||||
}));
|
||||
};
|
||||
|
||||
const normalizeThreatReference = (reference: string): string => {
|
||||
try {
|
||||
const parsed = new URL(reference);
|
||||
|
||||
if (!parsed.pathname.endsWith('/')) {
|
||||
// Adds a trailing backslash in urls if it doesn't exist to account for
|
||||
// any inconsistencies between our script generated data and prebuilt rules packages
|
||||
parsed.pathname = `${parsed.pathname}/`;
|
||||
}
|
||||
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return reference;
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -80,12 +80,28 @@ const getSubtechniquesOptions = (subtechniques) =>
|
|||
}`.replace(/(\r\n|\n|\r)/gm, ' ')
|
||||
);
|
||||
|
||||
const normalizeThreatReference = (reference) => {
|
||||
try {
|
||||
const parsed = new URL(reference);
|
||||
|
||||
if (!parsed.pathname.endsWith('/')) {
|
||||
// Adds a trailing backslash in urls if it doesn't exist to account for
|
||||
// any inconsistencies between our script generated data and prebuilt rules packages
|
||||
parsed.pathname = `${parsed.pathname}/`;
|
||||
}
|
||||
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return reference;
|
||||
}
|
||||
};
|
||||
|
||||
const getIdReference = (references) => {
|
||||
const ref = references.find((r) => r.source_name === 'mitre-attack');
|
||||
if (ref != null) {
|
||||
return {
|
||||
id: ref.external_id,
|
||||
reference: ref.url,
|
||||
reference: normalizeThreatReference(ref.url),
|
||||
};
|
||||
} else {
|
||||
return { id: '', reference: '' };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue