From bd441946de56c735f8084d63065ef28b74d5c73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chantal=20Bel=C3=A9n=20kelm?= <99441266+chantal-kelm@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:43:58 -0300 Subject: [PATCH] Agent view Mitre ATT&CK exception (#7116) * add a validation to filterParams and filterParams.filters * add key to map * update changelog * change url * Fix url from endpoint summary to dashboard and events in mitre * Correction of link to technique in intellicense flyout * Go to tactics in intellicense, clean code and eui accordion start open * clean code * clean code * update changelog * clean code * fix card endpoint summary * update changelog * rename function * rename function goToTechniqueInIntelligence * rename function goToTechniqueInIntelligence --------- Co-authored-by: Federico Rodriguez --- CHANGELOG.md | 1 + .../components/mitre_top/mitre-top.tsx | 88 ++++++---- .../flyout-technique/flyout-technique.tsx | 76 ++++++--- .../components/techniques/techniques.tsx | 150 +++++++++++------- 4 files changed, 203 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f283ba3b0e..a58c302796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ All notable changes to the Wazuh app project will be documented in this file. - Fixed export formatted csv data with special characters from tables [#7048](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7048) - Fixed column reordering feature [#7072](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7072) - Fixed filter management to prevent hiding when adding multiple filters [#7077](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7077) +- Fixed the Mitre ATT&CK exception in the agent view, the redirections of ID, Tactics, Dashboard Icon and Event Icon in the drop-down menu and the card not displaying information when the flyout was opened [#7116](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7116) - Fixed vulnerabilities inventory table scroll [#7118](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7118) - Fixed the filter are displayed cropped on screens of 575px to 767px in vulnerability detection module [#7047](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7047) - Fixed ability to filter from files inventory details flyout of File Integrity Monitoring [#7119](https://github.com/wazuh/wazuh-dashboard-plugins/pull/7119) diff --git a/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx b/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx index 69fa2f8c1f..b1f2e71e95 100644 --- a/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx +++ b/plugins/main/public/components/common/welcome/components/mitre_top/mitre-top.tsx @@ -25,6 +25,12 @@ import { FlyoutTechnique } from '../../../../overview/mitre/framework/components import { getMitreCount } from './lib'; import { useAsyncActionRunOnStart, useTimeFilter } from '../../../hooks'; import NavigationService from '../../../../../react-services/navigation-service'; +import { AppState } from '../../../../../react-services'; +import { mitreAttack } from '../../../../../utils/applications'; +import { + FILTER_OPERATOR, + PatternDataSourceFilterManager, +} from '../../../data-source/pattern/pattern-data-source-filter-manager'; const getTacticsData = async (agentId, timeFilter) => { return await getMitreCount(agentId, timeFilter, undefined); @@ -103,37 +109,47 @@ const MitreTopTacticsTechniques = ({ const [showTechniqueDetails, setShowTechniqueDetails] = useState(''); - if (showTechniqueDetails) { - const onChangeFlyout = () => { - setShowTechniqueDetails(''); - }; - - const openDiscover = (e, techniqueID) => { - NavigationService.getInstance().navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'discover', - filters: { 'rule.mitre.id': techniqueID }, - }); - }; - - const openDashboard = (e, techniqueID) => { - NavigationService.getInstance().navigateToModule(e, 'overview', { - tab: 'mitre', - tabView: 'dashboard', - filters: { 'rule.mitre.id': techniqueID }, - }); - }; - return ( - openDashboard(e, itemId)} - openDiscover={(e, itemId) => openDiscover(e, itemId)} - implicitFilters={[{ 'agent.id': agentId }]} - agentId={agentId} - onChangeFlyout={onChangeFlyout} - currentTechnique={showTechniqueDetails} - /> - ); - } + const onChangeFlyout = () => { + setShowTechniqueDetails(''); + }; + + const goToDashboardWithFilter = async (e, techniqueID) => { + const indexPatternId = AppState.getCurrentPattern(); + const filters = [ + PatternDataSourceFilterManager.createFilter( + FILTER_OPERATOR.IS, + `rule.mitre.id`, + techniqueID, + indexPatternId, + ), + ]; + + const params = `tab=mitre&tabView=dashboard&agentId=${agentId}&_g=${PatternDataSourceFilterManager.filtersToURLFormat( + filters, + )}`; + NavigationService.getInstance().navigateToApp(mitreAttack.id, { + path: `#/overview?${params}`, + }); + }; + + const goToEventsWithFilter = async (e, techniqueID) => { + const indexPatternId = AppState.getCurrentPattern(); + const filters = [ + PatternDataSourceFilterManager.createFilter( + FILTER_OPERATOR.IS, + `rule.mitre.id`, + techniqueID, + indexPatternId, + ), + ]; + + const params = `tab=mitre&tabView=events&agentId=${agentId}&_g=${PatternDataSourceFilterManager.filtersToURLFormat( + filters, + )}`; + NavigationService.getInstance().navigateToApp(mitreAttack.id, { + path: `#/overview?${params}`, + }); + }; if (getData.running) { return ( @@ -180,6 +196,16 @@ const MitreTopTacticsTechniques = ({ ))} + {showTechniqueDetails && ( + goToDashboardWithFilter(e, itemId)} + openDiscover={(e, itemId) => goToEventsWithFilter(e, itemId)} + implicitFilters={[{ 'agent.id': agentId }]} + agentId={agentId} + onChangeFlyout={onChangeFlyout} + currentTechnique={showTechniqueDetails} + /> + )} ); diff --git a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx index 958311c76f..868693439b 100644 --- a/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx +++ b/plugins/main/public/components/overview/mitre/framework/components/techniques/components/flyout-technique/flyout-technique.tsx @@ -9,7 +9,7 @@ * * Find more information about this on the LICENSE file. */ -import React, { useEffect, useState, useMemo } from 'react'; +import React, { useEffect, useState, useMemo, Fragment } from 'react'; import $ from 'jquery'; import { EuiFlyoutHeader, @@ -39,7 +39,11 @@ import { techniquesColumns, agentTechniquesColumns, } from './flyout-technique-columns'; -import { PatternDataSource } from '../../../../../../../../components/common/data-source'; +import { + FILTER_OPERATOR, + PatternDataSourceFilterManager, + PatternDataSource, +} from '../../../../../../../../components/common/data-source'; import { WazuhFlyoutDiscover } from '../../../../../../../common/wazuh-discover/wz-flyout-discover'; import { tFilterParams } from '../../../../mitre'; import TechniqueRowDetails from './technique-row-details'; @@ -47,6 +51,8 @@ import { buildPhraseFilter } from '../../../../../../../../../../../src/plugins/ import store from '../../../../../../../../redux/store'; import NavigationService from '../../../../../../../../react-services/navigation-service'; import { wzDiscoverRenderColumns } from '../../../../../../../common/wazuh-discover/render-columns'; +import { AppState } from '../../../../../../../../react-services'; +import { mitreAttack } from '../../../../../../../../utils/applications'; import { setFilters } from '../../../../../../../common/search-bar/set-filters'; type tFlyoutTechniqueProps = { @@ -258,6 +264,42 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => { : addRenderColumn(techniquesColumns); }; + const goToTechniqueInIntelligence = async (e, currentTechnique) => { + const indexPatternId = AppState.getCurrentPattern(); + const filters = [ + PatternDataSourceFilterManager.createFilter( + FILTER_OPERATOR.IS, + `rule.mitre.id`, + currentTechnique, + indexPatternId, + ), + ]; + const params = `tab=mitre&tabView=intelligence&tabRedirect=techniques&idToRedirect=${currentTechnique}&_g=${PatternDataSourceFilterManager.filtersToURLFormat( + filters, + )}`; + NavigationService.getInstance().navigateToApp(mitreAttack.id, { + path: `#/overview?${params}`, + }); + }; + + const goToTacticInIntelligence = async (e, tactic) => { + const indexPatternId = AppState.getCurrentPattern(); + const filters = [ + PatternDataSourceFilterManager.createFilter( + FILTER_OPERATOR.IS, + `rule.mitre.id`, + tactic, + indexPatternId, + ), + ]; + const params = `tab=mitre&tabView=intelligence&tabRedirect=tactics&idToRedirect=${ + tactic.id + }&_g=${PatternDataSourceFilterManager.filtersToURLFormat(filters)}`; + NavigationService.getInstance().navigateToApp(mitreAttack.id, { + path: `#/overview?${params}`, + }); + }; + const renderBody = () => { const { currentTechnique } = props; const { techniqueData } = state; @@ -271,16 +313,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => { > { - NavigationService.getInstance().navigateToModule( - e, - 'overview', - { - tab: 'mitre', - tabView: 'intelligence', - tabRedirect: 'techniques', - idToRedirect: currentTechnique, - }, - ); + goToTechniqueInIntelligence(e, currentTechnique); e.stopPropagation(); }} > @@ -294,23 +327,14 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => { description: techniqueData.tactics ? techniqueData.tactics.map(tactic => { return ( - <> + { - NavigationService.getInstance().navigateToModule( - e, - 'overview', - { - tab: 'mitre', - tabView: 'intelligence', - tabRedirect: 'tactics', - idToRedirect: tactic.id, - }, - ); + goToTacticInIntelligence(e, tactic); e.stopPropagation(); }} > @@ -318,7 +342,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {
- +
); }) : '', @@ -332,12 +356,12 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => {

Technique details

} - initialIsOpen={true} >
@@ -405,7 +429,7 @@ export const FlyoutTechnique = (props: tFlyoutTechniqueProps) => { DataSource={PatternDataSource} tableColumns={getDiscoverColumns()} filterManager={filterManager} - initialFetchFilters={filterParams.filters} + initialFetchFilters={filterParams?.filters || []} expandedRowComponent={expandedRow} /> diff --git a/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx b/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx index 2533d1643d..607f2c37a0 100644 --- a/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx +++ b/plugins/main/public/components/overview/mitre/framework/components/techniques/techniques.tsx @@ -183,7 +183,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { } }; - const buildPanel = (techniqueID) => { + const buildPanel = techniqueID => { return [ { id: 0, @@ -191,21 +191,21 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { items: [ { name: 'Filter for value', - icon: , + icon: , onClick: () => { closeActionsMenu(); }, }, { name: 'Filter out value', - icon: , + icon: , onClick: () => { closeActionsMenu(); }, }, { name: 'View technique details', - icon: , + icon: , onClick: () => { closeActionsMenu(); showFlyout(techniqueID); @@ -218,13 +218,17 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { const techniqueColumnsResponsive = () => { if (props && props?.windowSize) { - return props.windowSize.width < 930 ? 2 : props.windowSize.width < 1200 ? 3 : 4; + return props.windowSize.width < 930 + ? 2 + : props.windowSize.width < 1200 + ? 3 + : 4; } else { return 4; } }; - const getMitreTechniques = async (params) => { + const getMitreTechniques = async params => { try { return await WzRequest.apiReq('GET', '/mitre/techniques', { params }); } catch (error) { @@ -250,10 +254,16 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { const params = { limit: limitResults }; setIsSearching(true); const output = await getMitreTechniques(params); - const totalItems = (((output || {}).data || {}).data || {}).total_affected_items; + const totalItems = (((output || {}).data || {}).data || {}) + .total_affected_items; let mitreTechniques = []; mitreTechniques.push(...output.data.data.affected_items); - if (totalItems && output.data && output.data.data && totalItems > limitResults) { + if ( + totalItems && + output.data && + output.data.data && + totalItems > limitResults + ) { const extraResults = await Promise.all( Array(Math.ceil((totalItems - params.limit) / params.limit)) .fill() @@ -263,7 +273,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { offset: limitResults * (1 + index), }); return response.data.data.affected_items; - }) + }), ); mitreTechniques.push(...extraResults.flat()); } @@ -271,16 +281,17 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { setIsSearching(false); }; - const buildObjTechniques = (techniques) => { + const buildObjTechniques = techniques => { const techniquesObj = []; - techniques.forEach((element) => { - const mitreObj = state.mitreTechniques.find((item) => item.id === element); + techniques.forEach(element => { + const mitreObj = state.mitreTechniques.find(item => item.id === element); if (mitreObj) { const mitreTechniqueName = mitreObj.name; const mitreTechniqueID = mitreObj.source === MITRE_ATTACK ? mitreObj.external_id - : mitreObj.references.find((item) => item.source === MITRE_ATTACK).external_id; + : mitreObj.references.find(item => item.source === MITRE_ATTACK) + .external_id; mitreTechniqueID ? techniquesObj.push({ id: mitreTechniqueID, @@ -292,7 +303,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { return techniquesObj; }; - const addFilter = (filter) => { + const addFilter = filter => { const { filterManager } = getDataPlugin().query; const matchPhrase = {}; matchPhrase[filter.key] = filter.value; @@ -333,37 +344,43 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { let hash = {}; let tacticsToRender: Array = []; const currentTechniques = Object.keys(tacticsObject) - .map((tacticsKey) => ({ + .map(tacticsKey => ({ tactic: tacticsKey, techniques: buildObjTechniques(tacticsObject[tacticsKey].techniques), })) - .filter((tactic) => selectedTactics[tactic.tactic]) - .map((tactic) => tactic.techniques) + .filter(tactic => selectedTactics[tactic.tactic]) + .map(tactic => tactic.techniques) .flat() - .filter((techniqueID, index, array) => array.indexOf(techniqueID) === index); + .filter( + (techniqueID, index, array) => array.indexOf(techniqueID) === index, + ); tacticsToRender = currentTechniques - .filter((technique) => + .filter(technique => state.filteredTechniques ? state.filteredTechniques.includes(technique.id) : technique.id && hash[technique.id] ? false - : (hash[technique.id] = true) + : (hash[technique.id] = true), ) - .map((technique) => { + .map(technique => { return { id: technique.id, label: `${technique.id} - ${technique.name}`, quantity: - (techniquesCount.find((item) => item.key === technique.id) || {}).doc_count || 0, + (techniquesCount.find(item => item.key === technique.id) || {}) + .doc_count || 0, }; }) - .filter((technique) => (state.hideAlerts ? technique.quantity !== 0 : true)); + .filter(technique => + state.hideAlerts ? technique.quantity !== 0 : true, + ); const tacticsToRenderOrdered = tacticsToRender .sort((a, b) => b.quantity - a.quantity) .map((item, idx) => { const tooltipContent = `View details of ${item.label} (${item.id})`; const toolTipAnchorClass = - 'wz-display-inline-grid' + (state.hover === item.id ? ' wz-mitre-width' : ' '); + 'wz-display-inline-grid' + + (state.hover === item.id ? ' wz-mitre-width' : ' '); return ( setState({ ...state, hover: item.id })} @@ -376,8 +393,8 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { }} > { onClick={() => showFlyout(item.id)} > @@ -409,25 +426,31 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { {state.hover === item.id && ( - + { + onClick={e => { openDashboard(e, item.id); e.stopPropagation(); }} - color="primary" - type="visualizeApp" + color='primary' + type='visualizeApp' > {' '}   - + { + onClick={e => { openDiscover(e, item.id); e.stopPropagation(); }} - color="primary" - type="discoverApp" + color='primary' + type='discoverApp' > @@ -436,9 +459,9 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { } isOpen={state.actionsOpen === item.id} closePopover={() => closeActionsMenu()} - panelPaddingSize="none" + panelPaddingSize='none' style={{ width: '100%' }} - anchorPosition="downLeft" + anchorPosition='downLeft' > @@ -447,8 +470,10 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { }); if (isSearching || loadingAlerts || isLoading) { return ( - - + + ); } @@ -456,7 +481,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { return ( { ); } else { return ( - + ); } }; - const onChange = (searchValue) => { + const onChange = searchValue => { if (!searchValue) { setState({ ...state, filteredTechniques: false }); setIsSearching(false); } }; - const onSearch = async (searchValue) => { + const onSearch = async searchValue => { try { if (searchValue) { setIsSearching(true); @@ -490,8 +519,12 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { search: searchValue, }, }); - const filteredTechniques = (((response || {}).data || {}).data.affected_items || []).map( - (item) => [item].filter((reference) => reference.source === MITRE_ATTACK)[0].external_id + const filteredTechniques = ( + ((response || {}).data || {}).data.affected_items || [] + ).map( + item => + [item].filter(reference => reference.source === MITRE_ATTACK)[0] + .external_id, ); setState({ ...state, @@ -525,7 +558,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { setState({ ...state, actionsOpen: false }); }; - const showFlyout = (techniqueData) => { + const showFlyout = techniqueData => { setState({ ...state, isFlyoutVisible: true, @@ -545,7 +578,7 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => {
- +

Techniques

@@ -555,23 +588,27 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { Hide techniques with no alerts   - hideAlerts()} /> + hideAlerts()} + />
- + - +
{renderFacet()}
@@ -580,12 +617,15 @@ export const Techniques = withWindowSize((props: tTechniquesProps) => { onChangeFlyout={onChangeFlyout} currentTechnique={currentTechnique} filterParams={{ - filters: [...filterParams.filters, ...getMitreRuleIdFilter(currentTechnique)], // the flyout must receive the filters from the mitre global search bar + filters: [ + ...filterParams.filters, + ...getMitreRuleIdFilter(currentTechnique), + ], // the flyout must receive the filters from the mitre global search bar query: filterParams.query, time: filterParams.time, }} - openDashboard={(e) => openDashboard(e, currentTechnique)} - openDiscover={(e) => openDiscover(e, currentTechnique)} + openDashboard={e => openDashboard(e, currentTechnique)} + openDiscover={e => openDiscover(e, currentTechnique)} /> )}