Skip to content

Commit

Permalink
Handle Undefined Ability in Adversary Profile (#2574)
Browse files Browse the repository at this point in the history
* Now missing abilities in an adversary profile does not lock up the UI and is gracefully handled by warnings

* Working undefined ability highlight option 1

* Undefined Ability now with IDs and finalized visual look

* Fixed console error messages and addressed formatting issues

* Added compatibility for exporting undefined abilities without losing the ID

* minor code style tweaks

Co-authored-by: Palmer <[email protected]>
  • Loading branch information
ZacharyLPalmer and Palmer authored May 27, 2022
1 parent 723378e commit a966833
Showing 1 changed file with 85 additions and 35 deletions.
120 changes: 85 additions & 35 deletions templates/adversaries.html
Original file line number Diff line number Diff line change
Expand Up @@ -114,37 +114,54 @@ <h3 x-text="selectedProfileName" class="pointer tooltip has-tooltip-arrow" data-
</thead>
<tbody>
<template x-for="(ability, index) of selectedProfileAbilities">
<tr @click="selectAbility(ability.ability_id)" class="ability-row" x-bind:class="{ 'red-row': needsParser.indexOf(ability.name) > -1, 'row-hover': ability.ability_id === abilityTableDragHoverId }" x-on:mouseenter="setAbilityHover(ability.ability_id)" x-on:mouseleave="clearAbilityHover()" x-on:dragenter="abilityTableDragHoverId = ability.ability_id">
<tr @click="selectAbility(ability.ability_id)" class="ability-row"
x-bind:class="{ 'orange-row': needsParser.indexOf(ability.name) > -1 ,
'row-hover': ability.ability_id === abilityTableDragHoverId && abilityTableDragHoverId != undefined,
'red-row-unclickable': undefinedAbilities.indexOf(ability.ability_id) > -1 }"
x-on:mouseenter="setAbilityHover(ability.ability_id)" x-on:mouseleave="clearAbilityHover()" x-on:dragenter="abilityTableDragHoverId = ability.ability_id">
<td class="has-text-centered drag" @click.stop draggable="true" x-on:dragstart="startAbilitySwap" x-on:dragover.prevent="swapAbilitiesHover" x-on:dragend="swapAbilities">&#9776;</td>
<td x-text="index + 1"></td>
<td x-text="ability.name"></td>
<td>
<div class="icon-text">
<span x-text="index + 1"></span>
<span class="icon has-text-danger" x-show="!ability.ability_id">
<em class="fas fa-ban"></em>
</span>
<span class="icon has-text-warning" x-show="needsParser.indexOf(ability.name) > -1">
<em class="fas fa-exclamation-triangle"></em>
</span>
</div>
</td>
<td x-text="ability.ability_id ? ability.name : 'Undefined Ability'"></td>
<template x-if="!ability.ability_id">
<td colspan="7" x-text="'ID - ' + abilityIDs[index]"></td>
</template>
<td x-show="undefinedAbilities.indexOf(ability.ability_id)">
<span x-text="ability.tactic" x-bind:style="`border-bottom: 1px ridge ${hashStringToColor(ability.tactic)}`"></span>
</td>
<td x-text="ability.technique_name"></td>
<td>
<td x-text="ability.technique_name" x-show="undefinedAbilities.indexOf(ability.ability_id)"></td>
<td x-show="undefinedAbilities.indexOf(ability.ability_id)">
<template x-for="platform of getExecutorDetail('platforms', ability)">
<span class="has-tooltip-arrow no-underline" x-bind:data-tooltip="platform">
<span class="icon is-small"><em class="fab" x-bind:class="if (platform.includes('windows')) return 'fa-windows'; else if (platform.includes('darwin')) return 'fa-apple'; else if (platform.includes('linux')) return 'fa-linux'"></em></span>
</span>
</template>
</td>
<td class="has-text-centered" x-bind:class="{ 'unlock': onHoverUnlocks.indexOf(ability.ability_id) > -1 }">
<td class="has-text-centered" x-show="undefinedAbilities.indexOf(ability.ability_id)" x-bind:class="{ 'unlock': onHoverUnlocks.indexOf(ability.ability_id) > -1 }">
<span class=" has-tooltip-arrow no-underline" x-show="getExecutorDetail('requirements', ability)" x-bind:data-tooltip="`This ability has requirements: (${abilityDependencies[ability.ability_id].requireTypes})`">
<span class="icon is-small"><em class="fas fa-lock"></em></span>
</span>
</td>
<td class="has-text-centered" x-bind:class="{ 'lock': onHoverLocks.indexOf(ability.ability_id) > -1 }">
<td class="has-text-centered" x-show="undefinedAbilities.indexOf(ability.ability_id)" x-bind:class="{ 'lock': onHoverLocks.indexOf(ability.ability_id) > -1 }">
<span class="has-tooltip-arrow no-underline" x-show="getExecutorDetail('parser', ability)" x-bind:data-tooltip="`This ability unlocks other abilities: (${abilityDependencies[ability.ability_id].enableTypes})`">
<span class="icon is-small"><em class="fas fa-key"></em></span>
</span>
</td>
<td class="has-text-centered">
<td class="has-text-centered" x-show="undefinedAbilities.indexOf(ability.ability_id)">
<span class="has-tooltip-arrow no-underline" x-show="getExecutorDetail('payload', ability)" data-tooltip="This ability uses a payload">
<span class="icon is-small"><em class="fas fa-weight-hanging"></em></span>
</span>
</td>
<td class="has-text-centered">
<td class="has-text-centered" x-show="undefinedAbilities.indexOf(ability.ability_id)">
<span class="has-tooltip-arrow no-underline" x-show="getExecutorDetail('cleanup', ability)" data-tooltip="This ability can clean itself up">
<span class="icon is-small"><em class="fas fa-trash"></em></span>
</span>
Expand All @@ -163,6 +180,12 @@ <h3 x-text="selectedProfileName" class="pointer tooltip has-tooltip-arrow" data-
</span>
<span>One or more of the abilities have unmet requirements, which may result in a failed operation if ran sequentially.</span>
</div>
<div class="icon-text mt-2" x-show="undefinedAbilities.length">
<span class="icon has-text-danger">
<em class="fas fa-ban"></em>
</span>
<span>One or more of the referenced abilities are not defined.</span>
</div>
</div>
</template>

Expand Down Expand Up @@ -787,6 +810,7 @@ <h3>Create a profile</h3>
// Global page variables
adversaries: [],
abilities: [],
abilityIDs: [],
objectives: [],
platforms: JSON.parse('{{ platforms | tojson }}'),
payloads: JSON.parse('{{ payloads | tojson }}').sort(),
Expand All @@ -800,6 +824,7 @@ <h3>Create a profile</h3>
// Ability dependencies & parsers
abilityDependencies: {},
needsParser: [],
undefinedAbilities: [],
onHoverUnlocks: [],
onHoverLocks: [],
isTacticBreakdownActive: false,
Expand Down Expand Up @@ -881,6 +906,7 @@ <h3>Create a profile</h3>
this.selectedProfileDescription = selectedAdversary.description;
this.selectedObjectiveId = this.objectives.find((objective) => objective.id === selectedAdversary.objective).id;
this.selectedProfileAbilities = selectedAdversary.atomic_ordering.map((ability_id) => ({ ...this.abilities.find((ability) => ability.ability_id === ability_id) }));
this.abilityIDs = selectedAdversary.atomic_ordering;
this.adversarySearchQuery = selectedAdversary.name;
this.findAbilityDependencies();
},
Expand Down Expand Up @@ -1012,8 +1038,8 @@ <h3>Create a profile</h3>
yaml += `description: ${this.selectedProfileDescription}\n`;
yaml += `objective: ${this.selectedObjectiveId}\n`;
yaml += `atomic_ordering:\n`;
this.selectedProfileAbilities.forEach((ability) => yaml += `- ${ability.ability_id}\n`);

this.selectedProfileAbilities.forEach((ability, index) =>
ability.ability_id ? yaml += `- ${ability.ability_id}\n` : yaml += `- ${this.abilityIDs[index]}\n`);
const blob = new Blob([yaml], { type: 'application/x-yaml' })
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
Expand All @@ -1025,6 +1051,7 @@ <h3>Create a profile</h3>
window.URL.revokeObjectURL(url);
},


getAdversaryTactics() {
this.adversaries.forEach((adversary) => {
let tactics = adversary.atomic_ordering.map((ability_id) => {
Expand Down Expand Up @@ -1058,12 +1085,15 @@ <h3>Create a profile</h3>
let hasPayload = false;
let hasParser = false;

ability.executors.forEach((executor) => {
if (executor.cleanup.length > 0) hasCleanup = true;
if (executor.parsers.length > 0) hasParser = true;
if (executor.payloads.length > 0) hasPayload = true;
plats.push(`${executor.platform} (${executorNameMap.get(executor.name) || executor.name})`);
});
if(ability.executors)
{
ability.executors.forEach((executor) => {
if (executor.cleanup.length > 0) hasCleanup = true;
if (executor.parsers.length > 0) hasParser = true;
if (executor.payloads.length > 0) hasPayload = true;
plats.push(`${executor.platform} (${executorNameMap.get(executor.name) || executor.name})`);
});
}

switch (detail) {
case 'cleanup':
Expand All @@ -1073,7 +1103,9 @@ <h3>Create a profile</h3>
case 'payload':
return hasPayload;
case 'requirements':
return ability.requirements.length > 0;
if(ability.requirements){
return ability.requirements.length > 0;
}
case 'platforms':
return plats;
default:
Expand All @@ -1085,22 +1117,30 @@ <h3>Create a profile</h3>
const types = {};
let factsCollected = [];
let factsRequired = [];
this.undefinedAbilities = [];

this.selectedProfileAbilities.forEach((ability, index) => {
this.selectedProfileAbilities.forEach((ability, index) => {
let requireTypes = [];
let enableTypes = [];

// Get all parser types from executors
ability.executors.forEach((executor) => {
executor.parsers.forEach((parser) => {
enableTypes = enableTypes.concat(parser.parserconfigs.map((rel) => rel.source));
//skip building out enable types and require types if ability is unknown
if(ability.ability_id != undefined) {

// Get all parser types from executors
ability.executors.forEach((executor) => {
executor.parsers.forEach((parser) => {
enableTypes = enableTypes.concat(parser.parserconfigs.map((rel) => rel.source));
});
});
});

// Get all requirement types
ability.requirements.forEach((requirement) => {
requireTypes = requireTypes.concat(requirement.relationship_match.map((match) => match.source));
});
// Get all requirement types
ability.requirements.forEach((requirement) => {
requireTypes = requireTypes.concat(requirement.relationship_match.map((match) => match.source));
});

} else {
this.undefinedAbilities.push(ability.ability_id);
}

types[ability.ability_id] = {
enableTypes: [...new Set(enableTypes)],
Expand All @@ -1115,9 +1155,11 @@ <h3>Create a profile</h3>

// For each parser, look at and forward for any ability it unlocks
types[ability.ability_id].enableTypes.forEach((key) => {
for (let i = index; i < this.selectedProfileAbilities.length; i++) {
if (types[this.selectedProfileAbilities[i].ability_id].requireTypes.indexOf(key) > -1) {
enablesAbilityIds.push(this.selectedProfileAbilities[i].ability_id);
if(this.selectedProfileAbilities) {
for (let i = index; i < this.selectedProfileAbilities.length; i++) {
if (types[this.selectedProfileAbilities[i].ability_id].requireTypes.indexOf(key) > -1) {
enablesAbilityIds.push(this.selectedProfileAbilities[i].ability_id);
}
}
}
});
Expand Down Expand Up @@ -1384,8 +1426,10 @@ <h3>Create a profile</h3>

hashStringToColor(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i);
if(str) {
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) + hash) + str.charCodeAt(i);
}
}

let r = (hash & 0xFF0000) >> 16;
Expand Down Expand Up @@ -1449,8 +1493,14 @@ <h3>Create a profile</h3>
cursor: pointer;
}

.red-row {
border: 2px solid #8B0000;
.red-row-unclickable {
pointer-events: none;
font-weight: bold;
border: 3px solid #8B0000;
}

.orange-row {
border: 2px solid orange;
}

.row-hover {
Expand Down

0 comments on commit a966833

Please sign in to comment.