diff --git a/app/Controller/AnalystDataController.php b/app/Controller/AnalystDataController.php index ef7b305fb..9659555d8 100644 --- a/app/Controller/AnalystDataController.php +++ b/app/Controller/AnalystDataController.php @@ -80,6 +80,7 @@ class AnalystDataController extends AppController $this->set('id', $id); $conditions = $this->AnalystData->buildConditions($this->Auth->user()); $params = [ + 'fields' => $this->AnalystData->getEditableFields(), 'conditions' => $conditions, 'afterFind' => function(array $analystData): array { $canEdit = $this->ACL->canEditAnalystData($this->Auth->user(), $analystData, $this->modelSelection); @@ -89,7 +90,7 @@ class AnalystDataController extends AppController return $analystData; }, 'beforeSave' => function(array $analystData): array { - $analystData[$this->modelSelection]['modified'] = date ('Y-m-d H:i:s'); + $analystData[$this->modelSelection]['modified'] = date('Y-m-d H:i:s'); return $analystData; } ]; diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index c65a98721..466c767ae 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -2408,14 +2408,14 @@ class EventsController extends AppController } if (isset($this->params['named']['distribution'])) { $distribution = intval($this->params['named']['distribution']); - if (array_key_exists($distribution, $distributionLevels)) { - $initialDistribution = $distribution; - } else { + if (!array_key_exists($distribution, $distributionLevels)) { throw new MethodNotAllowedException(__('Wrong distribution level')); } + } else { + $distribution = $initialDistribution; } $sharingGroupId = null; - if ($initialDistribution == 4) { + if ($distribution == 4) { if (!isset($this->params['named']['sharing_group_id'])) { throw new MethodNotAllowedException(__('The sharing group id is needed when the distribution is set to 4 ("Sharing group").')); } @@ -2424,8 +2424,25 @@ class EventsController extends AppController throw new MethodNotAllowedException(__('Please select a valid sharing group id.')); } } + $clusterDistribution = $initialDistribution; + $clusterSharingGroupId = null; if (isset($this->params['named']['galaxies_as_tags'])) { $galaxies_as_tags = $this->params['named']['galaxies_as_tags']; + if (isset($this->params['name']['cluster_distribution'])) { + $clusterDistribution = intval($this->params['named']['cluster_distribution']); + if (!array_key_exists($clusterDistribution, $distributionLevels)) { + throw new MethodNotAllowedException(__('Wrong cluster distribution level')); + } + if ($clusterDistribution == 4) { + if (!isset($this->params['named']['cluster_sharing_group_id'])) { + throw new MethodNotAllowedException(__('The cluster sharing group id is needed when the cluster distribution is set to 4 ("Sharing group").')); + } + $clusterSharingGroupId = intval($this->params['named']['cluster_sharing_group_id']); + if (!array_key_exists($clusterSharingGroupId, $sgs)) { + throw new MethodNotAllowedException(__('Please select a valid cluster sharing group id.')); + } + } + } } if (isset($this->params['named']['debugging'])) { $debug = $this->params['named']['debugging']; @@ -2437,9 +2454,11 @@ class EventsController extends AppController $stix_version, 'uploaded_stix_file.' . ($stix_version == '1' ? 'xml' : 'json'), $publish, - $initialDistribution, + $distribution, $sharingGroupId, $galaxies_as_tags, + $clusterDistribution, + $clusterSharingGroupId, $debug ); if (is_numeric($result)) { @@ -2471,6 +2490,8 @@ class EventsController extends AppController $this->data['Event']['distribution'], $this->data['Event']['sharing_group_id'] ?? null, $this->data['Event']['galaxies_handling'], + $this->data['Event']['cluster_distribution'], + $this->data['Event']['cluster_sharing_group_id'] ?? null, $debug ); if (is_numeric($result)) { diff --git a/app/Controller/OrganisationsController.php b/app/Controller/OrganisationsController.php index 2150a5b59..fd0f98931 100644 --- a/app/Controller/OrganisationsController.php +++ b/app/Controller/OrganisationsController.php @@ -481,8 +481,8 @@ class OrganisationsController extends AppController $extension = pathinfo($logo['name'], PATHINFO_EXTENSION); $filename = $orgId . '.' . ($extension === 'svg' ? 'svg' : 'png'); - if ($logo['size'] > 250*1024) { - $this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250kB.')); + if ($logo['size'] > 250 * 1024) { + $this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250 kB.')); return false; } @@ -492,10 +492,12 @@ class OrganisationsController extends AppController } $imgMime = mime_content_type($logo['tmp_name']); - if ($extension === 'png' && !exif_imagetype($logo['tmp_name'])) { + if ($extension === 'png' && (function_exists('exif_imagetype') && !exif_imagetype($logo['tmp_name']))) { $this->Flash->error(__('This is not a valid PNG image.')); return false; - } else if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) { + } + + if ($extension === 'svg' && !($imgMime === 'image/svg+xml' || $imgMime === 'image/svg')) { $this->Flash->error(__('This is not a valid SVG image.')); return false; } diff --git a/app/Lib/Tools/ProcessTool.php b/app/Lib/Tools/ProcessTool.php index b0003938b..30b28e18d 100644 --- a/app/Lib/Tools/ProcessTool.php +++ b/app/Lib/Tools/ProcessTool.php @@ -55,8 +55,19 @@ class ProcessTool if ($logToFile) { self::logMessage('Running command ' . implode(' ', $command)); } - - $process = proc_open($command, $descriptorSpec, $pipes, $cwd); + if (version_compare(phpversion(), '7.4.0', '<')) { + $temp = []; + foreach ($command as $k => $part) { + if ($k >= 1) { + $part = escapeshellarg($part); + } + $temp[] = $part; + } + $command_stringified = implode(' ', $temp); + $process = proc_open($command_stringified, $descriptorSpec, $pipes, $cwd); + } else { + $process = proc_open($command, $descriptorSpec, $pipes, $cwd); + } if (!$process) { $commandForException = self::commandFormat($command); throw new Exception("Command '$commandForException' could be started."); diff --git a/app/Model/AnalystData.php b/app/Model/AnalystData.php index f7023afe4..42ff8e268 100644 --- a/app/Model/AnalystData.php +++ b/app/Model/AnalystData.php @@ -36,6 +36,15 @@ class AnalystData extends AppModel 'Relationship', ]; + protected const BASE_EDITABLE_FIELDS = [ + 'language', + 'authors', + 'modified', + 'distribution', + 'sharing_group_id', + ]; + protected $EDITABLE_FIELDS = []; + /** @var object|null */ protected $Note; /** @var object|null */ @@ -126,7 +135,7 @@ class AnalystData extends AppModel public function beforeValidate($options = array()) { - parent::beforeValidate(); + parent::beforeValidate($options); if (empty($this->id) && empty($this->data[$this->current_type]['uuid'])) { $this->data[$this->current_type]['uuid'] = CakeText::uuid(); } @@ -142,6 +151,25 @@ class AnalystData extends AppModel return true; } + public function beforeSave($options = []) + { + parent::beforeSave($options); + if (empty($this->data[$this->current_type]['created'])) { + $this->data[$this->current_type]['created'] = (new DateTime())->format('Y-m-d H:i:s'); + } + if (empty($this->data[$this->current_type]['modified'])) { + $this->data[$this->current_type]['modified'] = (new DateTime())->format('Y-m-d H:i:s'); + } + $this->data[$this->current_type]['modified'] = (new DateTime($this->data[$this->current_type]['modified'], new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); + $this->data[$this->current_type]['created'] = (new DateTime($this->data[$this->current_type]['created'], new DateTimeZone('UTC')))->format('Y-m-d H:i:s'); + return true; + } + + public function getEditableFields(): array + { + return array_merge(self::BASE_EDITABLE_FIELDS, $this->EDITABLE_FIELDS); + } + /** * Checks if user can modify given analyst data * diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 3077fa34e..48582c699 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -91,6 +91,7 @@ class AppModel extends Model 105 => false, 106 => false, 107 => false, 108 => false, 109 => false, 110 => false, 111 => false, 112 => false, 113 => true, 114 => false, 115 => false, 116 => false, 117 => false, 118 => false, 119 => false, 120 => false, 121 => false, 122 => false, + 123 => false, ); const ADVANCED_UPDATES_DESCRIPTION = array( @@ -2155,6 +2156,14 @@ class AppModel extends Model UNIQUE KEY `unique_element` (`element_uuid`, `collection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; break; + case 123: + $sqlArray[] = 'ALTER TABLE `notes` MODIFY `created` datetime NOT NULL'; + $sqlArray[] = 'ALTER TABLE `opinions` MODIFY `created` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `relationships` MODIFY `created` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `notes` MODIFY `modified` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `opinions` MODIFY `modified` datetime NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `relationships` MODIFY `modified` datetime NOT NULL;'; + break; case 'fixNonEmptySharingGroupID': $sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; $sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; diff --git a/app/Model/Event.php b/app/Model/Event.php index 834db06b0..342337a45 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -5995,15 +5995,17 @@ class Event extends AppModel * @param int $distribution * @param int|null $sharingGroupId * @param bool $galaxiesAsTags + * @param int $clusterDistribution + * @param int|null $clusterSharingGroupId * @param bool $debug * @return int|string|array * @throws JsonException * @throws InvalidArgumentException * @throws Exception */ - public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $debug = false) + public function upload_stix(array $user, $file, $stixVersion, $originalFile, $publish, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $debug = false) { - $decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug); + $decoded = $this->convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $user['Organisation']['uuid'], $debug); if (!empty($decoded['success'])) { $data = JsonTool::decodeArray($decoded['converted']); @@ -6067,11 +6069,14 @@ class Event extends AppModel * @param int $distribution * @param int|null $sharingGroupId * @param bool $galaxiesAsTags + * @param int $clusterDistribution + * @param int|null $clusterSharingGroupId + * @param string $orgUuid * @param bool $debug * @return array * @throws Exception */ - private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $debug) + private function convertStixToMisp($stixVersion, $file, $distribution, $sharingGroupId, $galaxiesAsTags, $clusterDistribution, $clusterSharingGroupId, $orgUuid, $debug) { $scriptDir = APP . 'files' . DS . 'scripts'; if ($stixVersion === '2' || $stixVersion === '2.0' || $stixVersion === '2.1') { @@ -6082,12 +6087,18 @@ class Event extends AppModel $scriptFile, '-i', $file, '--distribution', $distribution, + '--org_uuid', $orgUuid ]; if ($distribution == 4) { array_push($shellCommand, '--sharing_group_id', $sharingGroupId); } if ($galaxiesAsTags) { $shellCommand[] = '--galaxies_as_tags'; + } else { + array_push($shell_command, '--cluster_distribution', $clusterDistribution); + if ($clusterDistribution == 4) { + array_push($shell_command, '--cluster_sharing_group_id', $clusterSharingGroupId); + } } if ($debug) { $shellCommand[] = '--debug'; diff --git a/app/Model/Note.php b/app/Model/Note.php index 9f41df961..481a3711c 100644 --- a/app/Model/Note.php +++ b/app/Model/Note.php @@ -15,8 +15,11 @@ class Note extends AnalystData public $current_type = 'Note'; public $current_type_id = 0; - public $validate = array( - ); + public const EDITABLE_FIELDS = [ + 'note', + ]; + + public $validate = []; public function beforeValidate($options = array()) { diff --git a/app/Model/Opinion.php b/app/Model/Opinion.php index 33b65f29c..8a7c589d2 100644 --- a/app/Model/Opinion.php +++ b/app/Model/Opinion.php @@ -15,8 +15,12 @@ class Opinion extends AnalystData public $current_type = 'Opinion'; public $current_type_id = 1; - public $validate = array( - ); + public const EDITABLE_FIELDS = [ + 'opinion', + 'comment', + ]; + + public $validate = []; public function beforeValidate($options = array()) { diff --git a/app/Model/Relationship.php b/app/Model/Relationship.php index ea73cd777..b469e24a5 100644 --- a/app/Model/Relationship.php +++ b/app/Model/Relationship.php @@ -15,8 +15,11 @@ class Relationship extends AnalystData public $current_type = 'Relationship'; public $current_type_id = 2; - public $validate = array( - ); + protected $EDITABLE_FIELDS = [ + 'relationship_type', + ]; + + public $validate = []; /** @var object|null */ protected $Event; diff --git a/app/Plugin/AadAuth/README.md b/app/Plugin/AadAuth/README.md index d5e9447eb..07e49e486 100755 --- a/app/Plugin/AadAuth/README.md +++ b/app/Plugin/AadAuth/README.md @@ -1,16 +1,16 @@ -# Configure Azure AD to use SIngle SignOn for MISP +# Configure Azure AD to use Single Sign-On (SSO) for MISP -This plugin enables authentication with an Azure Active Directory server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works. +This plugin enables authentication with an Azure Active Directory (now called [Entra-ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id)) server. Under the hood it uses oAuth2. There are still a number of rough edges but in general the plugin works. It supports verification if a user has the proper ‘MISP AD’ groups. Users should already exist in MISP. Future enhancement could include auto-create users ## Azure AD — App Registration Configuration -In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration. +In Azure, add a new App Registration. Select Web and set the Redirect URI to your MISP server login page `https://misp.yourdomain.com/users/login`. The MISP instance does not need to be publicly accessible if it is reachable by your browser. The redirect URI that you specify here must be the same as used in the MISP configuration (including `/users/login`). You can add as many redirect URIs as needed, meaning you can have multiple MISP servers use the same Azure App. ![AppReg Configuration](.images/Picture29.png) -On the Overview page of the new MISP App Registration capture the following inforamtion. +On the Overview page of the new MISP App Registration capture the following information. - [x] Application (client) ID - [x] Directory (tenant) ID @@ -44,7 +44,7 @@ Create the following groups in Azure AD, these can be called anything you like f Make a name of your groups, we'll need these later. - [x] Misp Users -- [x] Misp ORG Admins +- [x] Misp Org Admins - [x] Misp Site Admins ## Enable the AAD Plugin for MISP @@ -122,7 +122,7 @@ Scroll down to near the bottom of the page and add in the following configuratio ), ``` -Add the information we made a note of earlier when creating the `App Registation` and optionally the Azure AD groups you created. +Add the information we made a note of earlier when creating the `App Registration` and optionally the Azure AD groups you created. ![AadAuth.configuration](.images/Picture38.png) @@ -139,4 +139,12 @@ Additionally, it is recommended to set the following settings in the MISP config * `MISP.disable_user_login_change => true`: Removes the ability of users to change their username (email), except for site admins. * `MISP.disable_user_password_change => true`: Removes the ability of users to change their own password. -This way users will not be able to change their passwords and by-pass the AAD login flow. \ No newline at end of file +This way users will not be able to change their passwords and by-pass the AAD login flow. + +# Create users via the MISP REST API + +Because users already need to exist in MISP before they can authenticate with AAD it can be useful to provision them in an automated fashion. This can be done by creating the users via the MISP REST API. The below `curl` command provides an example on how to do this. Note that you need an API key. + +``` +curl -k -d '{"email":"newuser@mycompany.com", "role_id":"3", "org_id":"1", "enable_password":"1", "change_pw":"0"}' -H "Authorization: API_KEY" -H "Accept: application/json" -H "Content-type: application/json" -X POST htps://misp.mycompany.com/admin/users/add +``` diff --git a/app/View/AnalystData/add.ctp b/app/View/AnalystData/add.ctp index de7ae5437..9d6eaf389 100644 --- a/app/View/AnalystData/add.ctp +++ b/app/View/AnalystData/add.ctp @@ -89,10 +89,12 @@ if ($modelSelection === 'Note') { 'options' => $dropdownData['valid_targets'], 'type' => 'dropdown', 'stayInLine' => 1, + 'disabled' => !empty($this->data[$modelSelection]['related_object_type']), ], [ 'field' => 'related_object_uuid', 'class' => 'span4', + 'disabled' => !empty($this->data[$modelSelection]['related_object_uuid']), ], sprintf('
', __('Related Object'), __('- No UUID provided -')) ] diff --git a/app/View/Elements/genericElements/Analyst_data/generic.ctp b/app/View/Elements/genericElements/Analyst_data/generic.ctp index be41a365e..3784d94a9 100644 --- a/app/View/Elements/genericElements/Analyst_data/generic.ctp +++ b/app/View/Elements/genericElements/Analyst_data/generic.ctp @@ -43,7 +43,7 @@ $relationshipsCount = count($relationships); - + diff --git a/app/View/Elements/genericElements/Analyst_data/thread.ctp b/app/View/Elements/genericElements/Analyst_data/thread.ctp index 1bb31583d..2d33f6753 100644 --- a/app/View/Elements/genericElements/Analyst_data/thread.ctp +++ b/app/View/Elements/genericElements/Analyst_data/thread.ctp @@ -89,9 +89,9 @@ function getURLFromRelationship(note) { if (note.related_object_type == 'Event') { return baseurl + '/events/view/' + note.related_object_uuid } else if (note.related_object_type == 'Attribute') { - return baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid + return note?.attribute?.event_id ? baseurl + '/events/view/' + note.attribute.event_id + '/focus:' + note.related_object_uuid : '#' } else if (note.related_object_type == 'Object') { - return baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid + return note?.object?.event_id ? baseurl + '/events/view/' + note.object.event_id + '/focus:' + note.related_object_uuid : '#' } return '#' } @@ -100,9 +100,9 @@ function renderRelationshipEntryFromType(note, relationship_related_object) { var contentHtml = '' var template = doT.template('\ \ - {{=it.related_object_type}} \ + {{!it.related_object_type}} \ :: \ - {{=it.related_object_uuid}} \ + {{!it.related_object_uuid}} \ \ ') var templateEvent = doT.template('\ @@ -110,16 +110,16 @@ function renderRelationshipEntryFromType(note, relationship_related_object) { \ \ \ - {{=it.content}} \ + {{!it.content}} \ \ \ \ ') if (note.related_object_type == 'Event' && relationship_related_object.Event[note.related_object_uuid]) { note.event = relationship_related_object.Event[note.related_object_uuid] - template = doT.template(templateEvent({content: '{{=it.event.info}}', urlEvent: '{{=it.url}}'})) + template = doT.template(templateEvent({content: '{{!it.event.info}}', urlEvent: '{{!it.url}}'})) } else if (note.related_object_type == 'Attribute' && relationship_related_object.Attribute[note.related_object_uuid]) { - var event = templateEvent({content: '{{=it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.attribute.event_id}}'}) + var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'}) note.attribute = relationship_related_object.Attribute[note.related_object_uuid] if (note.attribute.object_relation !== undefined && note.attribute.object_relation !== null) { template = doT.template('\ @@ -128,27 +128,27 @@ function renderRelationshipEntryFromType(note, relationship_related_object) { \ \ \ - {{=it.attribute.Object.name}} \ - ↦ {{=it.attribute.object_relation}} \ + {{!it.attribute.Object.name}} \ + ↦ {{!it.attribute.object_relation}} \ \ - {{=it.attribute.value}} \ + {{!it.attribute.value}} \ \ ') } else if (relationship_related_object.Attribute[note.related_object_uuid]) { - var event = templateEvent({content: '{{=it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.attribute.event_id}}'}) + var event = templateEvent({content: '{{!it.attribute.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.attribute.event_id}}'}) template = doT.template('\ ' + event + ' \ \ \ \ - {{=it.attribute.type}} \ - {{=it.attribute.value}} \ + {{!it.attribute.type}} \ + {{!it.attribute.value}} \ \ \ ') } } else if (note.related_object_type == 'Object') { - var event = templateEvent({content: '{{=it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{=it.object.event_id}}'}) + var event = templateEvent({content: '{{!it.object.Event.info}}', urlEvent: baseurl + '/events/view/{{!it.object.event_id}}'}) note.object = relationship_related_object.Object[note.related_object_uuid] template = doT.template('\ ' + event + ' \ @@ -157,9 +157,9 @@ function renderRelationshipEntryFromType(note, relationship_related_object) { \ \ \ - {{=it.object.name}} \ + {{!it.object.name}} \ \ - {{=it.object.id}} \ + {{!it.object.id}} \ \ \ ') @@ -178,40 +178,40 @@ var noteFilteringTemplate = '\ ' var baseNoteTemplate = doT.template('\ -
\
\
\
\ - Organisation logo \ + Organisation logo \ \ - {{=it.Orgc.name}} \ + {{!it.Orgc.name}} \ \ - {{=it.authors}} \ + {{!it.authors}} \ \ - {{=it.modified_relative}} • {{=it.modified}} \ - \ + {{!it.modified_relative}} • {{!it.modified}} \ + \ {{? it.distribution == 4 }} \ - {{=it.distribution_text}} \ + {{!it.distribution_text}} \ {{??}} \ - {{=it.distribution_text}} \ + {{!it.distribution_text}} \ {{?}} \ \ \ {{? 1 == }} \ - \ + \ {{?}} \ {{? 1 == }} \ - \ + \ {{?}} \ {{? it._canEdit }} \ - \ + \ {{?}} \ {{? it._canEdit }} \ - \ + \ {{?}} \ \
\ @@ -222,7 +222,7 @@ var baseNoteTemplate = doT.template('\ ') var analystTemplate = doT.template('\
\ - {{=it.note}} \ + {{!it.note}} \
\ ') var opinionGradient = '\ @@ -233,17 +233,17 @@ var opinionGradient = '\
\ ' var opinionTemplate = doT.template('\ -
\ +
\ ' + opinionGradient + ' \ \ - {{=it.opinion_text}} \ - {{=it.opinion}} \ + {{!it.opinion_text}} \ + {{!it.opinion}} \ /100 \ \
\ {{? it.comment }} \
\ - {{=it.comment}} \ + {{!it.comment}} \
\ {{?}} \ ') @@ -253,17 +253,17 @@ var relationshipDefaultEntryTemplate = doT.template('\ \ \ {{? it.relationship_type }} \ - {{=it.relationship_type}} \ + {{!it.relationship_type}} \ {{??}} \ - empty - \ {{?}} \ \ \ -
{{=it.content}}
\ +
{{!it.content}}
\
\ {{? it.comment }} \
\ - {{=it.comment}} \ + {{!it.comment}} \
\ {{?}} \
\ @@ -284,7 +284,7 @@ var maxDepthReachedTemplate = doT.template('\
\ \ - Max depth reached, there is at least one entry remaining - \ - \ + \ \ \ \ @@ -292,7 +292,7 @@ var maxDepthReachedTemplate = doT.template('\
\
\ \ - \ + \ \ \ \ diff --git a/app/View/EventReports/view.ctp b/app/View/EventReports/view.ctp index c399f5b1a..ce8b880ea 100644 --- a/app/View/EventReports/view.ctp +++ b/app/View/EventReports/view.ctp @@ -1,4 +1,9 @@ element('genericElements/assetLoader', [ + 'js' => ['doT', 'moment.min'], + 'css' => ['analyst-data',], + ]); + $table_data = array(); $table_data[] = array('key' => __('ID'), 'value' => $report['EventReport']['id']); $table_data[] = [ diff --git a/app/View/Events/upload_stix.ctp b/app/View/Events/upload_stix.ctp index 84ee2c1de..7e6bf1b53 100644 --- a/app/View/Events/upload_stix.ctp +++ b/app/View/Events/upload_stix.ctp @@ -28,10 +28,13 @@ 'selected' => $initialDistribution, )); if (!empty($sharingGroups)) { - echo $this->Form->input('sharing_group_id', array( - 'options' => array($sharingGroups), - 'label' => __('Sharing Group'), - )); + $SGContainer = $this->Form->input( + 'sharing_group_id', array( + 'options' => array($sharingGroups), + 'label' => __('Sharing Group'), + ) + ); + echo ''; } ?>
@@ -64,6 +67,36 @@ 'label' => __('How to handle Galaxies and Clusters') . $galaxiesFormInfo, 'selected' => 0 )); +?> +
+element( + 'genericElements/Form/formInfo', + [ + 'field' => [ + 'field' => 'cluster_distribution' + ], + 'modelForForm' => 'Event', + 'fieldDesc' => $fieldDesc['distribution'], + ] + ); + $clusterDistribution = $this->Form->input( + 'cluster_distribution', array( + 'options' => $distributionLevels, + 'label' => __('Cluster distribution ') . $clusterDistributionFormInfo, + 'selected' => $initialDistribution, + ) + ); + echo ''; + if (!empty($sharingGroups)) { + $clusterSGContainer = $this->Form->input( + 'cluster_sharing_group_id', array( + 'options' => array($sharingGroups), + 'label' => __('Cluster Sharing Group'), + ) + ); + echo ''; + } } if ($me['Role']['perm_site_admin'] && Configure::read('debug') > 0) { $debugFormInfo = $this->element( @@ -101,4 +134,26 @@ $(function(){ }); checkSharingGroup('Event'); }); +$(function(){ + $('#EventGalaxiesHandling').change(function() { + if ($(this).val() == 0) { + $('#ClusterDistribution').show(); + if ($('#EventClusterDistribution').val() == 4) { + $('#ClusterSGContainer').show(); + } + } else { + $('#ClusterDistribution').hide(); + $('#ClusterSGContainer').hide(); + } + }).change(); +}); +$(function(){ + $('#EventClusterDistribution').change(function() { + if ($(this).val() == 4 && $('#EventGalaxiesHandling').val() == 0) { + $('#ClusterSGContainer').show(); + } else { + $('#ClusterSGContainer').hide(); + } + }).change(); +}); \ No newline at end of file diff --git a/app/View/GalaxyClusters/view.ctp b/app/View/GalaxyClusters/view.ctp index f998d711f..02406b306 100755 --- a/app/View/GalaxyClusters/view.ctp +++ b/app/View/GalaxyClusters/view.ctp @@ -1,4 +1,9 @@ element('genericElements/assetLoader', [ + 'js' => ['doT', 'moment.min'], + 'css' => ['analyst-data',], +]); + $extendedFromHtml = ''; if (!empty($cluster['GalaxyCluster']['extended_from'])) { $element = $this->element('genericElements/IndexTable/Fields/links', array( @@ -174,10 +179,6 @@ $options = [ 'relationships' => $cluster['GalaxyCluster']['Relationship'] ?? [], ]; -echo $this->element('genericElements/assetLoader', [ - 'js' => ['doT', 'moment.min'], - 'css' => ['analyst-data',], -]); echo $this->element('genericElements/Analyst_data/thread', $options); ?> \ No newline at end of file diff --git a/app/View/Helper/ImageHelper.php b/app/View/Helper/ImageHelper.php index 7507eb921..90db5c22a 100644 --- a/app/View/Helper/ImageHelper.php +++ b/app/View/Helper/ImageHelper.php @@ -27,8 +27,15 @@ class ImageHelper extends AppHelper throw new InvalidArgumentException("Only SVG and PNG images are supported"); } - $fileContent = base64_encode(FileAccessTool::readFromFile($imagePath)); - $base64 = "data:$mime;base64,$fileContent"; + try { + $fileContent = FileAccessTool::readFromFile($imagePath); + } catch (Exception $e) { + CakeLog::warning($e); + return 'data:null'; // in case file doesn't exists or is not readable + } + + $fileContentEncoded = base64_encode($fileContent); + $base64 = "data:$mime;base64,$fileContentEncoded"; return $this->imageCache[$imagePath] = $base64; } diff --git a/app/View/Users/login.ctp b/app/View/Users/login.ctp index 4b947894a..bc6ce22ea 100644 --- a/app/View/Users/login.ctp +++ b/app/View/Users/login.ctp @@ -5,7 +5,9 @@
- Html->image('custom/' . h(Configure::read('MISP.welcome_logo')), array('alt' => __('Logo'), 'onerror' => "this.style.display='none';")); ?> + + <?= __('Logo') ?> + @@ -16,8 +18,8 @@ ?>

- - + + @@ -82,7 +84,9 @@ ?>
- Html->image('custom/' . h(Configure::read('MISP.welcome_logo2')), array('alt' => 'Logo2', 'onerror' => "this.style.display='none';")); ?> + + <?= __('Logo2') ?> +
diff --git a/app/View/Users/logout401.ctp b/app/View/Users/logout401.ctp index 21b1fbab2..4ce2f33ab 100644 --- a/app/View/Users/logout401.ctp +++ b/app/View/Users/logout401.ctp @@ -29,8 +29,8 @@

- - + + diff --git a/app/files/scripts/misp-stix b/app/files/scripts/misp-stix index b8b8b7445..dd3037ee7 160000 --- a/app/files/scripts/misp-stix +++ b/app/files/scripts/misp-stix @@ -1 +1 @@ -Subproject commit b8b8b7445754ea3cbc84e2d0b434ecd08740ef95 +Subproject commit dd3037ee7f31c1f43a3ad3aaaa6cccfa232dc530 diff --git a/app/files/scripts/stix2/stix2misp.py b/app/files/scripts/stix2/stix2misp.py index ae5b40c08..6fdad7073 100644 --- a/app/files/scripts/stix2/stix2misp.py +++ b/app/files/scripts/stix2/stix2misp.py @@ -29,10 +29,26 @@ sys.path.insert(2, str(_scripts_path / 'python-cybox')) sys.path.insert(3, str(_scripts_path / 'mixbox')) sys.path.insert(4, str(_scripts_path / 'misp-stix')) from misp_stix_converter import ( - ExternalSTIX2toMISPParser, InternalSTIX2toMISPParser, _from_misp) + ExternalSTIX2toMISPParser, InternalSTIX2toMISPParser, + MISP_org_uuid, _from_misp) from stix2.parsing import parse as stix2_parser +def _get_stix_parser(from_misp, args): + arguments = { + 'distribution': args.distribution, + 'galaxies_as_tags': args.galaxies_as_tags + } + if args.distribution == 4 and args.sharing_group_id is not None: + arguments['sharing_group_id'] = args.sharing_group_id + if from_misp: + return 'InternalSTIX2toMISPParser', arguments + arguments['cluster_distribution'] = args.cluster_distribution + if args.cluster_distribution == 4 and args.cluster_sharing_group_id is not None: + arguments['cluster_sharing_group_id'] = args.cluster_sharing_group_id + return 'ExternalSTIX2toMISPParser', arguments + + def _handle_return_message(traceback): if isinstance(traceback, dict): messages = [] @@ -51,14 +67,8 @@ def _process_stix_file(args: argparse.Namespace): f.read(), allow_custom=True, interoperability=True ) stix_version = getattr(bundle, 'version', '2.1') - to_call = 'Internal' if _from_misp(bundle.objects) else 'External' - arguments = { - 'distribution': args.distribution, - 'galaxies_as_tags': args.galaxies_as_tags - } - if args.distribution == 4 and args.sharing_group_id is not None: - arguments['sharing_group_id'] = args.sharing_group_id - parser = globals()[f'{to_call}STIX2toMISPParser'](**arguments) + to_call, arguments = _get_stix_parser(_from_misp(bundle.objects), args) + parser = globals()[to_call](**arguments) parser.load_stix_bundle(bundle) parser.parse_stix_bundle(single_event=True) with open(f'{args.input}.out', 'wt', encoding='utf-8') as f: @@ -94,6 +104,10 @@ if __name__ == '__main__': '-i', '--input', required=True, type=Path, help='Input file containing STIX 2 content.' ) + argparser.add_argument( + '--org_uuid', default=MISP_org_uuid, + help='Organisation UUID to use when creating custom Galaxy clusters.' + ) argparser.add_argument( '--distribution', type=int, default=0, help='Distribution level for the resulting MISP Event.' @@ -110,6 +124,14 @@ if __name__ == '__main__': '--galaxies_as_tags', action='store_true', help='Import MISP Galaxies as tag names.' ) + argparser.add_argument( + '--cluster_distribution', type=int, default=0, + help='Cluster distribution level for clusters generated from STIX 2.x objects' + ) + argparser.add_argument( + '--cluster_sharing_group_id', type=int, + help='Cluster sharing group id when the cluster distribution level is 4.' + ) try: args = argparser.parse_args() except SystemExit as e: diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 1b691b0a1..772c138db 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -2936,6 +2936,10 @@ Query builder padding-right: 3px; } +.highlight-on-hover:hover { + filter: brightness(1.1); +} + .special-tag { animation: special-tag-color 4s infinite linear; } diff --git a/db_schema.json b/db_schema.json index eddfd6cba..aa1f9f524 100644 --- a/db_schema.json +++ b/db_schema.json @@ -10547,5 +10547,5 @@ "uuid": false } }, - "db_version": "122" + "db_version": "123" }