new: [ATT&CK] Added new export system for restsearch for ATT&CK

- Return the ATT&CK matrix data as HTML via the API
- Directly viewable via the REST client

- Greetings from the ATT&CK workshop @ Eurocontrol
pull/4576/merge
iglocska 2019-05-10 14:25:38 +02:00
parent 55cb19ca2f
commit e899eb8b9d
6 changed files with 192 additions and 13 deletions

View File

@ -2034,10 +2034,20 @@ class AttributesController extends AppController
$returnFormat = 'json';
}
$elementCounter = 0;
$final = $this->Attribute->restSearch($user, $returnFormat, $filters, false, false, $elementCounter);
$responseType = $validFormats[$returnFormat][0];
$renderView = '';
$final = $this->Attribute->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
$this->layout = false;
$final = json_decode($final, true);
foreach ($final as $key => $data) {
$this->set($key, $data);
}
$this->render('/Events/module_views/' . $renderView);
} else {
$responseType = $this->Attribute->validFormats[$returnFormat][0];
return $this->RestResponse->viewData($final, $responseType, false, true, false, array('X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType));
}
}
// returns an XML with attributes that belong to an event. The type of attributes to be returned can be restricted by type using the 3rd parameter.
// Similar to the restSearch, this parameter can be chained with '&&' and negations are accepted too. For example filename&&!filename|md5 would return all filenames that don't have an md5

View File

@ -3314,11 +3314,22 @@ class EventsController extends AppController
$returnFormat = 'json';
}
$elementCounter = 0;
$final = $this->Event->restSearch($user, $returnFormat, $filters, false, false, $elementCounter);
$renderView = false;
$final = $this->Event->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
$this->layout = false;
$final = json_decode($final, true);
foreach ($final as $key => $data) {
$this->set($key, $data);
}
$this->render('/Events/module_views/' . $renderView);
} else {
$responseType = $this->Event->validFormats[$returnFormat][0];
return $this->RestResponse->viewData($final, $responseType, false, true, false, array('X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType));
}
}
public function downloadOpenIOCEvent($key, $eventid, $enforceWarninglist = false)
{
// return a downloadable text file called misp.openIOC.<eventId>.ioc for individual events

View File

@ -0,0 +1,128 @@
<?php
class AttackExport
{
public $additional_params = array(
'flatten' => 1,
'includeEventTags' => 1,
'includeGalaxy' => 1
);
public $non_restrictive_export = true;
public $renderView = 'attack_view';
private $__clusterCounts = array();
private $__attackGalaxy = 'mitre-attack-pattern';
private $__galaxy_id = 0;
private $__galaxy_name = '';
private $__GalaxyModel = null;
private $__tabs = false;
private $__matrixTags = false;
private $__killChainOrders = false;
private $__instanceUUID = false;
private $__scope = 'Event';
public function handler($data, $options = array())
{
if (empty($this->__GalaxyModel)) {
$this->__GalaxyModel = ClassRegistry::init('Galaxy');
}
$this->__attackGalaxy = empty($options['attackGalaxy']) ? $this->__attackGalaxy : empty($options['attackGalaxy']);
$temp = $this->__GalaxyModel->find('first', array(
'recursive' => -1,
'fields' => array('id', 'name'),
'conditions' => array('Galaxy.type' => $this->__attackGalaxy, 'Galaxy.namespace !=' => 'deprecated'),
));
if (empty($temp)) {
return '';
} else {
$this->__galaxy_id = $temp['Galaxy']['id'];
$this->__galaxy_name = $temp['Galaxy']['name'];
}
$matrixData = $this->__GalaxyModel->getMatrix($this->__galaxy_id);
if (empty($this->__tabs)) {
$this->__tabs = $matrixData['tabs'];
$this->__matrixTags = $matrixData['matrixTags'];
$this->__killChainOrders = $matrixData['killChain'];
$this->__instanceUUID = $matrixData['instance-uuid'];
}
$this->__scope = empty($options['scope']) ? 'Event' : $options['scope'];
$clusterData = array();
if ($this->__scope === 'Event') {
$clusterData = $this->__aggregate($data, $clusterData);
if (!empty($data['Attribute'])) {
foreach ($data['Attribute'] as $attribute) {
$clusterData = $this->__aggregate($attribute, $clusterData);
}
}
} else {
$clusterData = $this->__aggregate($data, $clusterData);
}
foreach ($clusterData as $key => $value) {
if (empty($this->__clusterCounts[$key])) {
$this->__clusterCounts[$key] = 1;
} else {
$this->__clusterCounts[$key] += 1;
}
}
return '';
}
private function __aggregate($data, $clusterData)
{
if (!empty($data['Galaxy'])) {
foreach ($data['Galaxy'] as $galaxy) {
if ($galaxy['type'] == $this->__attackGalaxy) {
foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
$clusterData[$galaxyCluster['tag_name']] = 1;
}
}
}
}
return $clusterData;
}
public function header($options = array())
{
return '';
}
public function footer()
{
if (empty($this->__GalaxyModel)) {
return '';
}
$maxScore = 0;
foreach ($this->__clusterCounts as $clusterCount) {
if ($clusterCount > $maxScore) {
$maxScore = $clusterCount;
}
}
App::uses('ColourGradientTool', 'Tools');
$gradientTool = new ColourGradientTool();
$colours = $gradientTool->createGradientFromValues($this->__clusterCounts);
$result = array(
'target_type' => strtolower($this->__scope),
'columnOrders' => $this->__killChainOrders,
'tabs' => $this->__tabs,
'scores' => $this->__clusterCounts,
'maxScore' => $maxScore,
'pickingMode' => false
);
if (!empty($colours)) {
$result['colours'] = $colours['mapping'];
$result['interpolation'] = $colours['interpolation'];
}
$result['galaxyName'] = $this->__galaxy_name;
$result['galaxyId'] = $this->__galaxy_id;
$matrixGalaxies = $this->__GalaxyModel->getAllowedMatrixGalaxies();
$result['matrixGalaxies'] = $matrixGalaxies;
return json_encode($result);
}
public function separator()
{
return '';
}
}

View File

@ -392,7 +392,8 @@ class Attribute extends AppModel
'yara-json' => array('json', 'YaraExport', 'json'),
'rpz' => array('rpz', 'RPZExport', 'rpz'),
'csv' => array('csv', 'CsvExport', 'csv'),
'cache' => array('txt', 'CacheExport', 'cache')
'cache' => array('txt', 'CacheExport', 'cache'),
'attack' => array('html', 'AttackExport', 'html')
);
// FIXME we need a better way to list the defaultCategories knowing that new attribute types will continue to appear in the future. We should generate this dynamically or use a function using the default_category of the $typeDefinitions
@ -2972,6 +2973,9 @@ class Attribute extends AppModel
if (isset($options['limit'])) {
$params['limit'] = $options['limit'];
}
if (!empty($options['includeGalaxy'])) {
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
}
if (Configure::read('MISP.proposals_block_attributes') && isset($options['conditions']['AND']['Attribute.to_ids']) && $options['conditions']['AND']['Attribute.to_ids'] == 1) {
$this->bindModel(array('hasMany' => array('ShadowAttribute' => array('foreignKey' => 'old_id'))));
$proposalRestriction = array(
@ -3114,6 +3118,10 @@ class Attribute extends AppModel
}
}
if (!empty($results[$key])) {
if (!empty($options['includeGalaxy'])) {
$results[$key] = $this->Event->massageTags($results[$key], 'Attribute');
$results[$key] = $this->Event->massageTags($results[$key], 'Event');
}
$attributes[] = $results[$key];
}
}
@ -3142,9 +3150,11 @@ class Attribute extends AppModel
$eventTags[$results[$key]['Event']['id']][] = $tag;
}
}
if (!empty($eventTags)) {
foreach ($eventTags[$results[$key]['Event']['id']] as $eventTag) {
$results[$key]['EventTag'][] = $eventTag['EventTag'];
}
}
return $results;
}
@ -3926,7 +3936,7 @@ class Attribute extends AppModel
return $conditions;
}
public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0)
public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0, &$renderView = false)
{
if (!isset($this->validFormats[$returnFormat][1])) {
throw new NotFoundException('Invalid output format.');
@ -3947,6 +3957,9 @@ class Attribute extends AppModel
unset($filters['value']);
}
}
if (!empty($exportTool->renderView)) {
$renderView = $exportTool->renderView;
}
if (isset($filters['searchall'])) {
if (!empty($filters['value'])) {
$filters['wildcard'] = $filters['value'];

View File

@ -178,7 +178,8 @@ class Event extends AppModel
'stix2' => array('json', 'Stix2Export', 'json'),
'yara' => array('txt', 'YaraExport', 'yara'),
'yara-json' => array('json', 'YaraExport', 'json'),
'cache' => array('txt', 'CacheExport', 'cache')
'cache' => array('txt', 'CacheExport', 'cache'),
'attack' => array('html', 'AttackExport', 'html')
);
public $csv_event_context_fields_to_fetch = array(
@ -6040,7 +6041,7 @@ class Event extends AppModel
}
}
public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0)
public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0, &$renderView = false)
{
if (!isset($this->validFormats[$returnFormat][1])) {
throw new NotFoundException('Invalid output format.');
@ -6061,6 +6062,11 @@ class Event extends AppModel
$filters['published'] = 1;
}
}
if (!empty($exportTool->renderView)) {
$renderView = $exportTool->renderView;
}
if (!empty($filters['ignore'])) {
$filters['to_ids'] = array(0, 1);
$filters['published'] = array(0, 1);
@ -6140,7 +6146,11 @@ class Event extends AppModel
unset($temp);
fwrite($tmpfile, $exportTool->footer($exportToolParams));
fseek($tmpfile, 0);
if (fstat($tmpfile)['size'] > 0) {
$final = fread($tmpfile, fstat($tmpfile)['size']);
} else {
$final = 0;
}
fclose($tmpfile);
return $final;
}

View File

@ -0,0 +1,7 @@
<div>
<div id="attackmatrix_div" style="position: relative; border: solid 1px;" class="statistics_attack_matrix">
<?php
echo $this->element('view_galaxy_matrix');
?>
</div>
</div>