MISP/app/Lib/Export/StixExport.php

394 lines
14 KiB
PHP

<?php
App::uses('JSONConverterTool', 'Tools');
App::uses('TmpFileTool', 'Tools');
App::uses('JsonTool', 'Tools');
App::uses('ProcessTool', 'Tools');
abstract class StixExport
{
public $additional_params = array(
'includeEventTags' => 1,
'includeGalaxy' => 1
);
protected $__return_format = 'json';
protected $__scripts_dir = APP . 'files/scripts/';
protected $__framing_script = APP . 'files/scripts/misp_framing.py';
protected $__return_type = null;
/** @var array Full paths to files to convert */
protected $__filenames = array();
protected $__version = null;
protected $__scope = null;
protected $stixFile = null;
private $__cluster_uuids = array();
private $__empty_file = null;
private $__event_galaxies = array();
/** @var File */
private $__tmp_file = null;
private $__n_attributes = 0;
public $non_restrictive_export = true;
public function setDefaultFilters($filters)
{
$sane_version = !empty($filters['stix-version']) && in_array($filters['stix-version'], $this->__sane_versions, true);
$this->__version = $sane_version ? $filters['stix-version'] : $this->__default_version;
}
public function handler($data, $options = array())
{
if ($this->__scope === 'Attribute') {
return $this->__attributesHandler($data);
}
if ($this->__scope === 'Event') {
return $this->__eventsHandler($data);
}
return '';
}
public function modify_params($user, $params)
{
if (empty($params['contain'])) {
$params['contain'] = array();
}
$params['contain'] = array_merge($params['contain'], array(
'AttributeTag' => array('Tag'),
'Event' => array('fields' => array('Event.timestamp'), 'Org.name', 'Org.uuid', 'Orgc.name', 'Orgc.uuid')
));
unset($params['fields']);
$params['includeContext'] = 0;
return $params;
}
public function header($options = array())
{
$this->__scope = $options['scope'];
$this->__return_type = $options['returnFormat'];
if ($this->__return_type === 'stix-json') {
$this->__return_type = 'stix';
} else if ($this->__return_type === 'stix') {
$this->__return_format = 'xml';
}
$this->__initialize_misp_file();
return '';
}
/**
* @return TmpFileTool
* @throws Exception
*/
public function footer()
{
if ($this->__empty_file) {
$this->__tmp_file->close();
$this->__tmp_file->delete();
} else {
if (!empty($this->__event_galaxies)) {
$this->__write_event_galaxies();
}
$this->__tmp_file->append($this->__scope === 'Attribute' ? ']}}' : ']}');
$this->__tmp_file->close();
$this->__filenames[] = $this->__tmp_file->path;
}
$result = $this->__parse_misp_data();
$decoded = json_decode($result, true);
if (!isset($decoded['success']) || !$decoded['success']) {
if (!empty($decoded['filenames'])) {
$this->__delete_temporary_files(false, $decoded['filename']);
} else {
$this->__delete_temporary_files(true);
}
$error = $decoded && !empty($decoded['error']) ? $decoded['error'] : $result;
throw new Exception('Error while processing your query during STIX export: ' . $error);
}
$this->__delete_temporary_files();
$framing = $this->getFraming();
$this->stixFile = new TmpFileTool();
$this->stixFile->write($framing['header']);
$separator = $framing['separator'];
if (!empty($decoded['filenames'])) {
foreach ($decoded['filenames'] as $filename) {
$this->__write_stix_content($filename, $separator);
}
} else {
foreach ($this->__filenames as $filename) {
$this->__write_stix_content($filename . '.out', $separator);
}
}
$this->stixFile->write($framing['footer']);
return $this->stixFile;
}
public function separator()
{
return '';
}
private function __addMetadataToAttribute($raw_attribute)
{
$attribute = $raw_attribute['Attribute'];
if (isset($attribute['SharingGroup']) && empty($attribute['SharingGroup'])) {
unset($attribute['SharingGroup']);
}
unset($attribute['value1']);
unset($attribute['value2']);
if (!empty($raw_attribute['Galaxy'])) {
$galaxies = array(
'Attribute' => array(),
'Event' => array()
);
if (!empty($raw_attribute['AttributeTag'])) {
$tags = array();
foreach($raw_attribute['AttributeTag'] as $tag) {
$tag_name = $tag['Tag']['name'];
if (substr($tag_name, 0, 12) === 'misp-galaxy:') {
$this->__merge_galaxy_tag($galaxies['Attribute'], $tag_name);
} else {
$tags[] = $tag['Tag'];
}
}
if (!empty($tags)) {
$attribute['Tag'] = $tags;
}
}
if (!empty($raw_attribute['EventTag'])) {
foreach($raw_attribute['EventTag'] as $tag) {
$tag_name = $tag['Tag']['name'];
if (substr($tag_name, 0, 12) === 'misp-galaxy:') {
$this->__merge_galaxy_tag($galaxies['Event'], $tag_name);
}
}
}
if (!empty($galaxies['Attribute'])) {
$attribute['Galaxy'] = array();
}
$timestamp = $raw_attribute['Event']['timestamp'];
foreach($raw_attribute['Galaxy'] as $galaxy) {
$galaxy_type = $galaxy['type'];
if (!empty($galaxies['Attribute'][$galaxy_type])) {
if (empty($galaxies['Event'][$galaxy_type])) {
$attribute['Galaxy'][] = $this->__arrange_galaxy($galaxy, $attribute['timestamp']);
unset($galaxies['Attribute'][$galaxy_type]);
continue;
}
$in_attribute = array();
$in_event = array();
foreach($galaxy['GalaxyCluster'] as $cluster) {
$cluster_value = $cluster['value'];
$in_attribute[] = in_array($cluster_value, $galaxies['Attribute'][$galaxy_type]);
$in_event[] = in_array($cluster_value, $galaxies['Event'][$galaxy_type]);
}
if (!in_array(false, $in_attribute)) {
$attribute['Galaxy'][] = $this->__arrange_galaxy($galaxy, $attribute['timestamp']);
unset($galaxies['Attribute'][$galaxy_type]);
if (!in_array(false, $in_event)) {
$this->__handle_event_galaxies($galaxy, $timestamp);
unset($galaxies['Event'][$galaxy_type]);
}
continue;
}
}
if (!empty($galaxies['Event'][$galaxy_type])) {
$this->__handle_event_galaxies($galaxy, $timestamp);
unset($galaxies['Event'][$galaxy_type]);
}
}
} else {
if (!empty($raw_attribute['AttributeTag'])) {
$attribute['Tag'] = array();
foreach($raw_attribute['AttributeTag'] as $tag) {
$attribute['Tag'][] = $tag['Tag'];
}
}
}
$attribute['Org'] = $raw_attribute['Event']['Org'];
$attribute['Orgc'] = $raw_attribute['Event']['Orgc'];
return $attribute;
}
private function __arrange_cluster($cluster, $timestamp)
{
$arranged_cluster = array(
'collection_uuid' => $cluster['collection_uuid'],
'type' => $cluster['type'],
'value' => $cluster['value'],
'tag_name' => $cluster['tag_name'],
'description' => $cluster['description'],
'source' => $cluster['source'],
'authors' => $cluster['authors'],
'uuid' => $cluster['uuid'],
'timestamp' => $timestamp
);
return $arranged_cluster;
}
private function __arrange_galaxy($galaxy, $timestamp)
{
$arranged_galaxy = array(
'uuid' => $galaxy['uuid'],
'name' => $galaxy['name'],
'type' => $galaxy['type'],
'description' => $galaxy['description'],
'namespace' => $galaxy['namespace'],
'GalaxyCluster' => array()
);
foreach($galaxy['GalaxyCluster'] as $cluster) {
$arranged_galaxy['GalaxyCluster'][] = $this->__arrange_cluster($cluster, $timestamp);
}
return $arranged_galaxy;
}
private function __attributesHandler($attribute)
{
$attribute = json_encode($this->__addMetadataToAttribute($attribute));
if ($this->__n_attributes < $this->__attributes_limit) {
$this->__tmp_file->append($this->__n_attributes == 0 ? $attribute : ', ' . $attribute);
$this->__n_attributes += 1;
$this->__empty_file = false;
} else {
if (!empty($this->__event_galaxies)) {
$this->__write_event_galaxies();
}
$this->__terminate_misp_file($attribute);
$this->__n_attributes = 1;
}
return '';
}
private function __eventsHandler($event)
{
$attributes_count = count($event['Attribute']);
foreach ($event['Object'] as $_object) {
if (!empty($_object['Attribute'])) {
$attributes_count += count($_object['Attribute']);
}
}
$event = JsonTool::encode(JSONConverterTool::convert($event, false, true)); // we don't need pretty printed JSON
if ($this->__n_attributes + $attributes_count <= $this->__attributes_limit) {
$this->__tmp_file->append($this->__n_attributes == 0 ? $event : ', ' . $event);
$this->__n_attributes += $attributes_count;
$this->__empty_file = false;
} elseif ($attributes_count > $this->__attributes_limit) {
$filePath = FileAccessTool::writeToTempFile($event);
$this->__filenames[] = $filePath;
} else {
$this->__terminate_misp_file($event);
$this->__n_attributes = $attributes_count;
}
return '';
}
private function __handle_event_galaxies($galaxy, $timestamp)
{
$galaxy_type = $galaxy['type'];
if (!empty($this->__event_galaxies[$galaxy['type']])) {
foreach($galaxy['GalaxyCluster'] as $cluster) {
if (!in_array($cluster['uuid'], $this->__cluster_uuids)) {
$this->__event_galaxies[$galaxy_type]['GalaxyCluster'][] = $this->__arrange_cluster(
$cluster,
$timestamp
);
$this->__cluster_uuids[] = $cluster['uuid'];
}
}
} else {
$this->__event_galaxies[$galaxy_type] = $this->__arrange_galaxy($galaxy, $timestamp);
foreach($galaxy['GalaxyCluster'] as $cluster) {
$this->__cluster_uuids[] = $cluster['uuid'];
}
}
}
private function __initialize_misp_file()
{
$tmpFile = FileAccessTool::createTempFile();
$this->__tmp_file = new File($tmpFile);
$this->__tmp_file->write('{"response": ' . ($this->__scope === 'Attribute' ? '{"Attribute": [' : '['));
$this->__empty_file = true;
}
protected function __delete_temporary_files($removeOutput = false, $custom = null)
{
if (!is_null($custom)) {
foreach ($custom as $filename) {
FileAccessTool::deleteFileIfExists($filename);
}
}
foreach ($this->__filenames as $filename) {
FileAccessTool::deleteFileIfExists($filename);
}
if ($removeOutput) {
FileAccessTool::deleteFileIfExists($filename . '.out');
}
}
/**
* @return array
* @throws Exception
*/
private function getFraming()
{
$framingCmd = $this->__initiate_framing_params();
try {
$framing = JsonTool::decode(ProcessTool::execute($framingCmd, null, true));
if (isset($framing['error'])) {
throw new Exception("Framing command error: " . $framing['error']);
}
return $framing;
} catch (Exception $e) {
throw new Exception("Could not get results from framing cmd when exporting STIX file.", 0, $e);
}
}
private function __merge_galaxy_tag(&$galaxies, $tag_name)
{
list($galaxy_type, $value) = explode('=', explode(':', $tag_name)[1]);
$value = substr($value, 1, -1);
if (empty($galaxies[$galaxy_type])) {
$galaxies[$galaxy_type] = array($value);
} else {
$galaxies[$galaxy_type][] = $value;
}
}
private function __terminate_misp_file($content)
{
$this->__tmp_file->append($this->__scope === 'Attribute' ? ']}}' : ']}');
$this->__tmp_file->close();
$this->__filenames[] = $this->__tmp_file->path;
$this->__initialize_misp_file();
$this->__tmp_file->append($content);
}
private function __write_event_galaxies()
{
$this->__tmp_file->append('], "Galaxy": [');
$galaxies = array();
foreach($this->__event_galaxies as $type => $galaxy) {
$galaxies[] = json_encode($galaxy);
}
$this->__tmp_file->append(implode(', ', $galaxies));
$this->__event_galaxies = array();
}
private function __write_stix_content($filename, $separator)
{
$stix_content = FileAccessTool::readAndDelete($filename);
if ($this->__return_type === 'stix2') {
$stix_content = substr($stix_content, 1, -1);
}
$this->stixFile->writeWithSeparator($stix_content, $separator);
}
/**
* @return array
*/
abstract protected function __parse_misp_data();
/**
* @return array
*/
abstract protected function __initiate_framing_params();
}