Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/change tactis and techniques resources #3346

Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,12 @@

All notable changes to the Wazuh app project will be documented in this file.

## Wazuh v4.3.0 - Kibana 7.10.2 , 7.11.2 - Revision 4301

### Added

- Create the detail section for Mitre Intelligence resources. [#3344](https://github.com/wazuh/wazuh-kibana-app/pull/3344)

## Wazuh v4.2.0 - Kibana 7.10.2 , 7.11.2 - Revision 4201

### Added
Expand All @@ -23,6 +29,7 @@ All notable changes to the Wazuh app project will be documented in this file.
- Changed position of Top users on Integrity Monitoring Top 5 user. [#2892](https://github.com/wazuh/wazuh-kibana-app/pull/2892)
- Changed user allow_run_as way of editing. [#3080](https://github.com/wazuh/wazuh-kibana-app/pull/3080)
- Rename some ossec references to Wazuh [#3046](https://github.com/wazuh/wazuh-kibana-app/pull/3046)
- Changed tactis and techniques resources [#3346](https://github.com/wazuh/wazuh-kibana-app/pull/3346)

### Fixed

Expand Down
Expand Up @@ -56,6 +56,7 @@ export class FlyoutTechnique extends Component {
props!: {
currentTechniqueData: any
currentTechnique: string
tacticsObject: any
}

filterManager: FilterManager;
Expand Down Expand Up @@ -118,9 +119,9 @@ export class FlyoutTechnique extends Component {
try{
this.setState({loading: true, techniqueData: {}});
const { currentTechnique } = this.props;
const result = await WzRequest.apiReq('GET', '/mitre', {
const result = await WzRequest.apiReq('GET', '/mitre/techniques', {
params: {
q: `id=${currentTechnique}`
q: `references.external_id=${currentTechnique}`
}
});
const rawData = (((result || {}).data || {}).data || {}).affected_items
Expand All @@ -130,36 +131,20 @@ export class FlyoutTechnique extends Component {
}
}

formatTechniqueData (rawData) {
const { platform_name, phase_name} = rawData;
const { name, description, x_mitre_version: version, x_mitre_data_sources, external_references } = rawData.json;

const replaced_external_references = [];
let index_replaced_external_references = 0;
let last_citation_string = '';
const descriptionWithCitations = external_references.reduce((accum, reference) => {
return accum
.replace(new RegExp(`\\(Citation: ${reference.source_name}\\)`,'g'), (token) => {
if(last_citation_string !== token){
index_replaced_external_references++;
replaced_external_references.push({...reference, index: index_replaced_external_references});
last_citation_string = token;
}
return `<a style="vertical-align: super;" rel="noreferrer" class="euiLink euiLink--primary technique-reference-citation-${index_replaced_external_references}">[${String(index_replaced_external_references)}]</a>`;
})
}, description);
this.setState({techniqueData: { name, description: descriptionWithCitations, phase_name, platform_name, version, x_mitre_data_sources, external_references, replaced_external_references }, loading: false })
findTacticName(tactics){
const { tacticsObject } = this.props;
const getTactic = (element) => {
const tactic = Object.values(tacticsObject).find(obj => obj.id === element);
return { id:tactic.references[0].external_id, name: tactic.name};
};
return tactics.reduce((tacticsObj, element) => [...tacticsObj, getTactic(element)], []);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use .map instead of .reduce here.

}

getArrayFormatted(arrayText) {
try {
const stringText = arrayText.toString();
const splitString = stringText.split(',');
const resultString = splitString.join(', ');
return resultString;
} catch (err) {
return arrayText;
}
formatTechniqueData (rawData) {
const { tactics, name, mitre_version } = rawData;
const tacticsObj = this.findTacticName(tactics)

this.setState({techniqueData: { name, mitre_version, tacticsObj }, loading: false })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon

}

renderHeader() {
Expand All @@ -180,7 +165,7 @@ export class FlyoutTechnique extends Component {
</EuiFlyoutHeader>
)
}

renderBody() {
const { currentTechnique } = this.props;
const { techniqueData } = this.state;
Expand All @@ -204,60 +189,30 @@ export class FlyoutTechnique extends Component {
title: 'ID',
description: ( <EuiToolTip
position="top"
content={"Open " + currentTechnique + " details in a new page"}>
<EuiLink href={link} external target="_blank">
content={"Open " + currentTechnique + " details in Intelligence section"}>
<EuiLink onClick={(e) => {this.props.openIntelligence(e,'techniques',currentTechnique);e.stopPropagation()}}>
{currentTechnique}
</EuiLink>
</EuiToolTip>)
},
{
title: 'Tactic',
description: this.getArrayFormatted(
techniqueData.phase_name
)
},
{
title: 'Platform',
description: this.getArrayFormatted(
techniqueData.platform_name
)
},
{
title: 'Data sources',
description: this.getArrayFormatted(
techniqueData.x_mitre_data_sources
)
title: 'Tactics',
description: techniqueData.tacticsObj ? techniqueData.tacticsObj.map(tactic => { return ( <><EuiToolTip
position="top"
content={"Open " + tactic.name + " details in a Intelligence section"}>
<EuiLink onClick={(e) => {this.props.openIntelligence(e,'tactics',tactic.id);e.stopPropagation()}}>
{tactic.name}
</EuiLink>
</EuiToolTip>
<br/>
</>)}) : ''
frankeros marked this conversation as resolved.
Show resolved Hide resolved
},
{
title: 'Version',
description: techniqueData.version
},
{
title: 'Description',
description: formattedDescription
},
description: techniqueData.mitre_version
}

];
if(techniqueData && techniqueData.replaced_external_references && techniqueData.replaced_external_references.length > 0){
data.push({
title: 'References',
description: (
<EuiFlexGroup>
<EuiFlexItem>
{techniqueData.replaced_external_references.map((external_reference, external_reference_index) => (
<div key={`external_reference-${external_reference.index}`} id={`technique-reference-${external_reference.index}`}>
<span>{external_reference.index}. </span>
<EuiLink href={external_reference.url} target='_blank'>
{external_reference.source_name}
</EuiLink>
</div>
)
)}
</EuiFlexItem>
</EuiFlexGroup>
)
})
}
return (
<EuiFlyoutBody className="flyout-body" >
<EuiAccordion
Expand All @@ -279,13 +234,6 @@ export class FlyoutTechnique extends Component {
)) || (
<div style={{marginBottom: 30}}>
<EuiDescriptionList listItems={data} />
<EuiSpacer />
<p>
More info:{' '}
<EuiLink href={link} target="_blank">
{`MITRE ATT&CK - ${currentTechnique}`}
</EuiLink>
</p>
</div>
)}
</div>
Expand Down

This file was deleted.

This file was deleted.

Expand Up @@ -29,14 +29,15 @@ import {
EuiLoadingSpinner,
} from '@elastic/eui';
import { FlyoutTechnique } from './components/flyout-technique/';
import { mitreTechniques, getElasticAlerts, IFilterParams } from '../../lib'
import { getElasticAlerts, IFilterParams } from '../../lib'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semicolon

import { ITactic } from '../../';
import { withWindowSize } from '../../../../../components/common/hocs/withWindowSize';
import { WzRequest } from '../../../../../react-services/wz-request';
import {WAZUH_ALERTS_PATTERN} from '../../../../../../common/constants';
import { AppState } from '../../../../../react-services/app-state';
import { WzFieldSearchDelay } from '../../../../common/search'
import { getDataPlugin } from '../../../../../kibana-services';
import { ModuleMitreIntelligence } from '../../../mitre_attack_intelligence'

export const Techniques = withWindowSize(class Techniques extends Component {
_isMount = false;
Expand All @@ -56,6 +57,7 @@ export const Techniques = withWindowSize(class Techniques extends Component {
hideAlerts: boolean,
actionsOpen: string,
filteredTechniques: boolean | [string]
mitreTechniques: [],
isSearching: boolean
}

Expand All @@ -70,13 +72,15 @@ export const Techniques = withWindowSize(class Techniques extends Component {
hideAlerts: false,
actionsOpen: "",
filteredTechniques: false,
mitreTechniques: [],
isSearching: false
}
this.onChangeFlyout.bind(this);
}

async componentDidMount(){
this._isMount = true;
this.getMitreTechniques()
}

shouldComponentUpdate(nextProps, nextState) {
Expand Down Expand Up @@ -177,29 +181,47 @@ export const Techniques = withWindowSize(class Techniques extends Component {
}
}

async getMitreTechniques () {
const data = await WzRequest.apiReq('GET', '/mitre/techniques', {}).then(res => res).catch(err => mitreTechniques);
const result = (((data || {}).data || {}).data || {}).affected_items;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will throw if data comes from the catch
.catch(err => mitreTechniques);
I think there is needed a better error handling

this.setState({ mitreTechniques: result })
}

buildObjTechniques(techniques){
const techniquesObj = []
techniques.forEach(element => {
const mitreObj = this.state.mitreTechniques.filter(item => item.id === element);
if(mitreObj.length != 0){
const mitreTechniqueName = mitreObj[0].name;
const mitreTechniqueID = mitreObj[0].references.filter(item => item.source === "mitre-attack")[0].external_id;
mitreTechniqueID ? techniquesObj.push({ id : mitreTechniqueID, name: mitreTechniqueName}) : '';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To search one element, you could be used the .find instead of .filter.

});
return techniquesObj
eze9252 marked this conversation as resolved.
Show resolved Hide resolved
}

renderFacet() {
const { tacticsObject } = this.props;
const { techniquesCount } = this.state;
let tacticsToRender: Array<any> = [];
const currentTechniques = Object.keys(tacticsObject).map(tacticsKey => ({tactic: tacticsKey, techniques: tacticsObject[tacticsKey]}))
const currentTechniques = Object.keys(tacticsObject).map(tacticsKey => ({tactic: tacticsKey, techniques: this.buildObjTechniques(tacticsObject[tacticsKey].techniques)}))
.filter(tactic => this.props.selectedTactics[tactic.tactic])
.map(tactic => tactic.techniques)
.flat()
.filter((techniqueID, index, array) => array.indexOf(techniqueID) === index);

tacticsToRender = currentTechniques
.filter(techniqueID => this.state.filteredTechniques ? this.state.filteredTechniques.includes(techniqueID) : techniqueID)
.filter(techniqueID => this.state.filteredTechniques ? this.state.filteredTechniques.includes(techniqueID.id) : techniqueID.id)
.map(techniqueID => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace techniqueID with technique

return {
id: techniqueID,
label: `${techniqueID} - ${mitreTechniques[techniqueID].name}`,
quantity: (techniquesCount.find(item => item.key === techniqueID) || {}).doc_count || 0
id: techniqueID.id,
label: `${techniqueID.id} - ${techniqueID.name}`,
quantity: (techniquesCount.find(item => item.key === techniqueID.id) || {}).doc_count || 0
}
})
.filter(technique => this.state.hideAlerts ? technique.quantity !== 0 : true);

const tacticsToRenderOrdered = tacticsToRender.sort((a, b) => b.quantity - a.quantity).map( (item,idx) => {
const tooltipContent = `View details of ${mitreTechniques[item.id].name} (${item.id})`;
const tooltipContent = `View details of ${item.label} (${item.id})`;
const toolTipAnchorClass = "wz-display-inline-grid" + (this.state.hover=== item.id ? " wz-mitre-width" : " ");
return(
<EuiFlexItem
Expand All @@ -222,7 +244,7 @@ export const Techniques = withWindowSize(class Techniques extends Component {
overflow: "hidden",
whiteSpace: "nowrap",
textOverflow: "ellipsis"}}>
{item.id} - {mitreTechniques[item.id].name}
{item.label}
</span>
</EuiToolTip>

Expand Down Expand Up @@ -281,6 +303,12 @@ export const Techniques = withWindowSize(class Techniques extends Component {
this.props.onSelectedTabChanged('dashboard');
}

openIntelligence(e,redirectTo,itemId){
sessionStorage.setItem("tabRedirect", redirectTo);
sessionStorage.setItem("idToRedirect", itemId);
this.props.onSelectedTabChanged('intelligence');
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could do the redirection through a query param in the URL instead of using the sessionStorage.

Copy link
Contributor Author

@eze9252 eze9252 Jun 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we should show the flyout, it was my mistake. I fix this

/**
* Adds a new filter with format { "filter_key" : "filter_value" }, e.g. {"agent.id": "001"}
* @param filter
Expand Down Expand Up @@ -314,14 +342,13 @@ export const Techniques = withWindowSize(class Techniques extends Component {
try{
if(searchValue){
this._isMount && this.setState({isSearching: true});
const response = await WzRequest.apiReq('GET', '/mitre', {
const response = await WzRequest.apiReq('GET', '/mitre/techniques', {
params: {
select: "id",
search: searchValue,
limit: 500
}
});
const filteredTechniques = ((((response || {}).data || {}).data).affected_items || []).map(item => item.id);
const filteredTechniques = ((((response || {}).data || {}).data).affected_items || []).map(item => item.references.filter(reference => reference.source === "mitre-attack")[0].external_id);
this._isMount && this.setState({ filteredTechniques, isSearching: false });
}else{
this._isMount && this.setState({ filteredTechniques: false, isSearching: false });
Expand Down Expand Up @@ -403,9 +430,11 @@ export const Techniques = withWindowSize(class Techniques extends Component {
<FlyoutTechnique
openDashboard={(e,itemId) => this.openDashboard(e,itemId)}
openDiscover={(e,itemId) => this.openDiscover(e,itemId)}
openIntelligence={(e,redirectTo,itemId) => this.openIntelligence(e,redirectTo,itemId)}
onChangeFlyout={this.onChangeFlyout}
currentTechniqueData={this.state.currentTechniqueData}
currentTechnique={currentTechnique} />
currentTechnique={currentTechnique}
tacticsObject={this.props.tacticsObject} />
</EuiOverlayMask>
}
</div>
Expand Down
2 changes: 0 additions & 2 deletions public/components/overview/mitre/lib/index.ts
Expand Up @@ -11,5 +11,3 @@
*/

export { IFilterParams, getElasticAlerts, getIndexPattern } from './elastic-helpers';

export { mitreTechniques } from './mitre_techniques';