mirror of https://github.com/MISP/MISP
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 @ Eurocontrolpull/4576/merge
parent
55cb19ca2f
commit
e899eb8b9d
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 '';
|
||||
}
|
||||
}
|
|
@ -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'];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue