new: [AttackMatrix] added Mobile/Pre-Attack Matrix support, UI

improvements and code refacto
pull/3347/head
Sami Mokaddem 2018-06-15 09:19:53 +00:00
parent 95e694f054
commit bc156ab13a
5 changed files with 246 additions and 114 deletions

View File

@ -4564,46 +4564,90 @@ class EventsController extends AppController {
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('Invalid method.');
}
$killChainOrder = array('initial-access', 'execution', 'persistence', 'privilege-escalation', 'defense-evasion', 'credential-access', 'discovery', 'lateral-movement', 'collection', 'exfiltration', 'command-and-control');
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $eventId));
if (empty($event)) throw new NotFoundException('Event not found or you are not authorised to view it.');
$event = $event[0];
$mitreAttackMatrix = $this->Event->GalaxyCluster->Galaxy->getMitreAttackMatrix();
$attackClusters = $mitreAttackMatrix['attackClusters'];
$attackGalaxyId = $mitreAttackMatrix['attackGalaxyId'];
$killChainOrderEnterprise = array(
'initial-access',
'execution',
'persistence',
'privilege-escalation',
'defense-evasion',
'credential-access',
'discovery',
'lateral-movement',
'collection',
'exfiltration',
'command-and-control'
);
$killChainOrderMobile = array(
'persistence',
'privilege-escalation',
'defense-evasion',
'credential-access',
'discovery',
'lateral-movement',
'effects', 'collection',
'exfiltration',
'command-and-control',
'general-network-based',
'cellular-network-based',
'could-based'
);
$killChainOrderPre = array(
'priority-definition-planning',
'priority-definition-direction',
'target-selection',
'technical-information-gathering',
'people-information-gathering',
'organizational-information-gathering',
'technical-weakness-identification',
'people-weakness-identification',
'organizational-weakness-identification',
'adversary-opsec',
'establish-&-maintain-infrastructure',
'persona-development',
'build-capabilities',
'test-capabilities',
'stage-capabilities',
'app-delivery-via-authorized-app-store',
'app-delivery-via-other-means',
'exploit-via-cellular-network',
'exploit-via-internet',
);
$type = "mitre-enterprise-attack-attack-pattern";
$killChainOrders = array(
'mitre-enterprise-attack-attack-pattern' => $killChainOrderEnterprise,
'mitre-mobile-attack-attack-pattern' => $killChainOrderMobile,
'mitre-pre-attack-attack-pattern' => $killChainOrderPre,
);
$eventTags = $this->Event->EventTag->find('list', array(
'recursive' => -1,
'conditions' => array('event_id' => $eventId),
'fields' => array('Tag.name'),
'contain' => 'Tag',
));
$attributeTags = $this->Event->Attribute->AttributeTag->find('list', array(
'recursive' => -1,
'conditions' => array('event_id' => $eventId),
'fields' => array('Tag.name'),
'contain' => array(
'Tag' => array(
'fields' => array('name')
),
)
));
$tags = array('eventTags' => $eventTags, 'attributeTags' => $attributeTags);
$this->loadModel('GalaxyCluster');
$attackTactic = $this->GalaxyCluster->Galaxy->getMitreAttackMatrix();
// get score of galaxy
$db = $this->Event->getDataSource();
// tag along with its occurence in the event
$subQuery = $db->buildStatement(
array(
'fields' => array('attr_tag.tag_id as id', 'count(attr_tag.tag_id) as value'),
'table' => $db->fullTableName($this->Event->Attribute->AttributeTag),
'alias' => 'attr_tag',
'conditions' => array('event_id' => $eventId),
'group' => 'tag_id'
),
$this->Event
);
$subQueryExpression = $db->expression($subQuery)->value;
// get related galaxies
$attributeTagScores = $this->Event->query("SELECT name, value FROM (" . $subQueryExpression . ") AS score, tags WHERE tags.id=score.id;");
// arrange data
$scores = array();
$maxScore = 0;
foreach ($attributeTags as $name) {
if (strpos($name, $type) === false) { // do not belong to mitre attack
continue;
}
if (!isset($scores[$name])) {
$scores[$name] = 0;
}
$scores[$name]++;
$maxScore = $scores[$name] > $maxScore ? $scores[$name] : $maxScore;
foreach($attributeTagScores as $item) {
$score = $item['score']['value'];
$name = $item['tags']['name'];
$maxScore = $score > $maxScore ? $score : $maxScore;
$scores[$name] = $score;
}
App::uses('ColourGradientTool', 'Tools');
@ -4611,10 +4655,8 @@ class EventsController extends AppController {
$colours = $gradientTool->createGradientFromValues($scores);
$this->set('target_type', $itemType);
$this->set('killChainOrder', $killChainOrder);
$this->set('killChainNames', $killChainOrder);
$this->set('attackGalaxyId', $attackGalaxyId);
$this->set('attackClusters', $attackClusters);
$this->set('killChainOrders', $killChainOrders);
$this->set('attackTactic', $attackTactic);
$this->set('scores', $scores);
$this->set('maxScore', $maxScore);
$this->set('colours', $colours);

View File

@ -219,57 +219,55 @@ class Galaxy extends AppModel{
}
public function getMitreAttackMatrix($type="mitre-enterprise-attack-attack-pattern") {
$conditions = array('Galaxy.type' => $type);
$expectedDescription = 'ATT&CK Tactic';
$expectedNamespace = 'mitre-attack';
$conditions = array('Galaxy.description' => $expectedDescription, 'Galaxy.namespace' => $expectedNamespace);
$contains = array(
//'GalaxyCluster.GalaxyElement' => function($q) {
// return $q->where(['GalaxyElement.key' => 'kill_chain']);
//}
//'GalaxyCluster' => array('GalaxyElement'),
//'GalaxyCluster.GalaxyElement' => array(
// 'conditions' => array('GalaxyElement.key' => 'kill_chain')
//),
'GalaxyCluster' => array('GalaxyElement'),
//'GalaxyCluster' => array('GalaxyElement' => function ($q) {
// return $q->where(array('GalaxyElement.key' => 'kill_chain'));
//}),
);
$galaxy = $this->find('first', array(
$galaxies = $this->find('all', array(
'recursive' => -1,
'contain' => $contains,
'conditions' => $conditions,
));
if (empty($galaxy)) {
throw new NotFoundException('Galaxy not found.');
if (empty($galaxies)) {
throw new NotFoundException('Galaxies not found.');
}
if (empty($galaxy['GalaxyCluster'])) {
throw new NotFoundException('Galaxy not found.');
}
$clusters = $galaxy['GalaxyCluster'];
$attackClusters = array();
$attackTactic = array();
foreach ($clusters as $cluster) {
if (empty($cluster['GalaxyElement'])) {
continue;
}
$toBeAdded = false;
$galaxyElements = $cluster['GalaxyElement'];
foreach ($galaxyElements as $element) {
if ($element['key'] == 'kill_chain') {
$kc = explode(":", $element['value'])[2];
$toBeAdded = true;
foreach ($galaxies as $galaxy) {
$galaxyType = $galaxy['Galaxy']['type'];
$clusters = $galaxy['GalaxyCluster'];
$attackClusters = array();
// add cluster if kill_chain is present
foreach ($clusters as $cluster) {
if (empty($cluster['GalaxyElement'])) {
continue;
}
if ($element['key'] == 'external_id') {
$cluster['external_id'] = $element['value'];
$toBeAdded = false;
$clusterType = $cluster['type'];
$galaxyElements = $cluster['GalaxyElement'];
foreach ($galaxyElements as $element) {
if ($element['key'] == 'kill_chain') {
$kc = explode(":", $element['value'])[2];
$toBeAdded = true;
}
if ($element['key'] == 'external_id') {
$cluster['external_id'] = $element['value'];
}
}
if ($toBeAdded) {
$attackClusters[$kc][] = $cluster;
}
}
if ($toBeAdded) {
$attackClusters[$kc][] = $cluster;
}
$attackTactic[$galaxyType] = array(
'clusters' => $attackClusters,
'galaxy' => $galaxy['Galaxy']
);
}
return array('attackClusters' => $attackClusters, 'attackGalaxyId' => $galaxy['Galaxy']['id']);
return $attackTactic;
}
}

View File

@ -1,3 +1,15 @@
<div class="attack-matrix-options" style="right: initial; background: transparent;">
<ul id="attack-matrix-tabscontroller" class="nav nav-tabs" style="margin-bottom: 2px;">
<?php
$enterpriseTag = "mitre-enterprise-attack-attack-pattern";
foreach($attackTactic as $tactic) {
$galaxy = $tactic['galaxy'];
?>
<li class="tactic <?php echo $galaxy['type']==$enterpriseTag ? "active" : ""; ?>"><span href="#tabMatrix-<?php echo $galaxy['type']; ?>" data-toggle="tab" style="padding-top: 3px; padding-bottom: 3px;"><?php echo($galaxy['name']); ?></span></li>
<?php } ?>
</ul>
</div>
<div class="attack-matrix-options">
<span id="matrix-heatmap-legend-caret">
<span id="matrix-heatmap-legend-caret-value">0</span>
@ -21,22 +33,27 @@
</div>
<?php endif; ?>
<div id="matrix_container" class="fixed-table-container-inner" style="height: 670px;" data-picking-mode="<?php echo $pickingMode ? 'true' : 'false'; ?>">
<div id="matrix_container" class="fixed-table-container-inner" style="max-height: 670px;" data-picking-mode="<?php echo $pickingMode ? 'true' : 'false'; ?>">
<div class="tab-content">
<?php foreach($attackTactic as $galaxy):
$galaxyType = $galaxy['galaxy']['type'];
?>
<div class="tab-pane <?php echo $galaxyType==$enterpriseTag ? "active" : ""; ?>" id="tabMatrix-<?php echo $galaxyType; ?>">
<div class="header-background"></div>
<div class="fixed-table-container-inner" style="height: 670px;">
<div class="fixed-table-container-inner" style="max-height: 670px;">
<table class="table table-condensed matrix-table">
<thead>
<tr>
<?php
foreach($killChainNames as $kc) {
foreach($killChainOrders[$galaxyType] as $kc):
$name = str_replace("-", " ", $kc);
echo '<th>
<div class="extra-wrap">
<div class="th-inner">'.ucfirst($name).'</div>
</div>
</th>';
}
?>
<th>
<?php echo ucfirst($name); ?>
<div class="th-inner"><?php echo ucfirst($name); ?></div>
</th>
<?php endforeach; ?>
</tr>
</thead>
<tbody style="overflow-y: scroll;">
@ -46,28 +63,34 @@
do {
$added = false;
echo '<tr>';
$killChainOrder = $killChainOrders[$galaxyType];
$attackClusters = $galaxy['clusters'];
foreach($killChainOrder as $kc) {
$clusters = $attackClusters[$kc];
$td = '<td ';
if ($i < count($clusters)) {
$clusterId = $clusters[$i]['id'];
$tagName = $clusters[$i]['tag_name'];
$score = empty($scores[$tagName]) ? 0 : $scores[$tagName];
$name = join(" ", array_slice(explode(" ", $clusters[$i]['value']), 0, -2)); // remove " - external_id"
$td .= ' class="heatCell matrix-interaction ' . ($pickingMode ? 'cell-picking"' : '"');
$td .= isset($colours[$tagName]) ? ' style="background: ' . $colours[$tagName] . '; color: ' . $this->TextColour->getTextColour($colours[$tagName]) . '"' : '' ;
$td .= ' data-score="'.h($score).'"';
$td .= ' data-tag_name="'.h($tagName).'"';
if ($pickingMode) {
$td .= ' data-target-type="attribute"';
$td .= ' data-target-id="'.h($target_id).'"';
$td .= ' data-cluster-id="'.h($clusterId).'"';
}
$td .= ' title="'.h($clusters[$i]['external_id']).'"';
$td .= '>' . h($name);
$added = true;
if(!isset($attackClusters[$kc])) { // undefined index
$td = '<td class="">';
} else {
$td .= 'class="">';
$clusters = $attackClusters[$kc];
$td = '<td ';
if ($i < count($clusters)) {
$clusterId = $clusters[$i]['id'];
$tagName = $clusters[$i]['tag_name'];
$score = empty($scores[$tagName]) ? 0 : $scores[$tagName];
$name = join(" ", array_slice(explode(" ", $clusters[$i]['value']), 0, -2)); // remove " - external_id"
$td .= ' class="heatCell matrix-interaction ' . ($pickingMode ? 'cell-picking"' : '"');
$td .= isset($colours[$tagName]) ? ' style="background: ' . $colours[$tagName] . '; color: ' . $this->TextColour->getTextColour($colours[$tagName]) . '"' : '' ;
$td .= ' data-score="'.h($score).'"';
$td .= ' data-tag_name="'.h($tagName).'"';
if ($pickingMode) {
$td .= ' data-target-type="attribute"';
$td .= ' data-target-id="'.h($target_id).'"';
$td .= ' data-cluster-id="'.h($clusterId).'"';
}
$td .= ' title="'.h($clusters[$i]['external_id']).'"';
$td .= '>' . h($name);
$added = true;
} else {
$td .= 'class="">';
}
}
$td .= '</td>';
echo $td;
@ -79,6 +102,9 @@
</tbody>
</table>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php if($pickingMode): ?>

View File

@ -4,6 +4,11 @@
.matrix-table th {
padding: 0px 5px;
color: transparent;
line-height: 12px;
}
.matrix-table thead > tr {
}
.matrix-table thead {
@ -16,6 +21,7 @@
table.matrix-table {
table-layout: fixed;
margin-bottom: unset;
}
td.matrix-interaction {
@ -41,15 +47,23 @@ td.matrix-interaction:hover {
color: white;
position: absolute;
top: 0;
line-height: 30px;
text-align: left;
padding-left: 5px;
margin-left: -5px;
/*line-height: 30px;*/
line-height: 12px;
}
div.th-inner {
min-height: 30px;
display: flex;
align-items: center;
}
.header-background {
background-color: #363636;
height: 30px;
min-height: 30px;
position: absolute;
top: 0;
right: 0;
@ -64,7 +78,7 @@ td.matrix-interaction:hover {
.attack-matrix-options {
position: absolute;
right: -1px;
top: -18px;
top: -22px;
background: #363636;
color: white;
padding: 1px 5px;
@ -106,3 +120,23 @@ td.matrix-interaction:hover {
position: relative;
display: block;
}
li.tactic {
color: #555555;
cursor: default;
background-color: #ffffff;
border: 1px solid #ddd;
border-bottom-color: transparent;
padding: 2px 8px 2px 8px;
}
li.tactic:hover {
background-color: #6f6f6f;
color: white;
}
li.tactic.active {
background-color: #363636;
color: white;
font-weight: bold;
}

View File

@ -1,5 +1,9 @@
(function () {
$(document).ready(function() {
$('#attack-matrix-tabscontroller span').click(function (e) {
$(this).tab('show');
})
var pickingMode = $('#matrix_container').data('picking-mode');
if (pickingMode) {
$('.ajax_popover_form .cell-picking').click(function() {
@ -24,20 +28,45 @@
$('#checkbox_attackMatrix_showAll').click(function() { toggleAttackMatrixCells('.info_container_eventgraph_network'); });
}
scoredCells.tooltip({
container: 'body',
placement: 'top',
});
scoredCells.hover(enteringScoredCell, leavingScoredCell);
$('span[data-toggle="tab"]').on('shown', function (e) {
var tabId = $(e.target).attr('href');
resizeHeader(tabId);
})
toggleAttackMatrixCells();
});
function toggleAttackMatrixCells(jfilter) {
function resizeHeader(tabId) {
if (tabId === undefined) {
tabId = '';
}
// resize fixed header div based on dimension of th cell
$(tabId + ' .matrix-table').each(function() {
var max_height = 0;
var div = $(this).find('thead > tr > th > div');
var cell = $(this).find('thead > tr > th');
for(var i=0; i<cell.length; i++) {
var cellH = $(cell[i]).css('height')
console.log(cellH);
max_height = $(cell[i]).height() > max_height ? $(cell[i]).height() : max_height;
$(div[i]).css({
width: $(cell[i]).css('width'),
height: cellH,
});
}
console.log(max_height);
$(tabId + ' .header-background').css('height', max_height+'px');
});
}
function toggleAttackMatrixCells(jfilterOrig) {
// get active tab
var activeTableId = $('#attack-matrix-tabscontroller > li.active > span').attr('href');
jfilter = jfilterOrig === undefined ? activeTableId : jfilterOrig+' '+activeTableId;
var visibilityVal, displayVal;
if($(jfilter+' #checkbox_attackMatrix_showAll').prop('checked')) {
if($(jfilterOrig+' #checkbox_attackMatrix_showAll').prop('checked')) {
visibilityVal = 'visible';
displayVal = 'table-cell';
displayVal = '';
@ -53,6 +82,8 @@
});
var rowNum = $(jfilter+' .matrix-table > tbody > tr').length;
var colNum = $(jfilter+' .matrix-table > thead > tr > th').length;
// hide empty row
for (var i=1; i<=rowNum; i++) {
var cellNoValues = $(jfilter+' .matrix-table > tbody > tr:nth-child('+i+') > td').filter(function() {
return $(this).attr('data-score') == 0 || $(this).attr('data-score') === undefined;
@ -62,6 +93,7 @@
}
}
// hide empty column
for (var i=1; i<=colNum; i++) {
var cellNoValues = $(jfilter+' .matrix-table tr td:nth-child('+i+')').filter(function() {
return $(this).attr('data-score') == 0 || $(this).attr('data-score') === undefined;