diff --git a/INSTALL/INSTALL.txt b/INSTALL/INSTALL.txt index 0994a8bb8..98fecbf1c 100755 --- a/INSTALL/INSTALL.txt +++ b/INSTALL/INSTALL.txt @@ -72,10 +72,11 @@ php composer.phar install # CakeResque normally uses phpredis to connect to redis, but it has a (buggy) fallback connector through Redisent. It is highly advised to install phpredis pecl install redis +apt get install php5-redis # After installing it, enable it in your php.ini file vim /etc/php5/apache2/php.ini # add the following line: -extension=redis.so +extension=redis.so # To use the scheduler worker for scheduled tasks, do the following: cp -fa /var/www/MISP/INSTALL/setup/config.php /var/www/MISP/app/Plugin/CakeResque/Config/config.php @@ -118,6 +119,7 @@ cp /var/www/MISP/INSTALL/apache.misp /etc/apache2/sites-available/misp.conf # For more information, visit http://httpd.apache.org/docs/2.4/upgrading.html a2dissite default +# default can be called 000-default based on distribution, in which case run a2dissite 000-default a2ensite misp # Enable modules diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index 1f3af245e..ab756b9f7 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -665,4 +665,7 @@ INSERT INTO `template_element_texts` (`id`, `name`, `template_element_id`, `text (11, 'Persistence mechanism', 41, 'The following fields allow you to describe the persistence mechanism used by the malware'), (12, 'Indicators', 45, 'Just paste your list of indicators based on type into the appropriate field. All of the fields are optional, so inputting a list of IP addresses into the Network indicator field for example is sufficient to complete this template.'); - +INSERT INTO `tasks` (`id`, `type`, `timer`, `scheduled_time`, `job_id`, `description`, `next_execution_time`, `message`) VALUES +(1, 'cache_exports', 0, '12:00', 0, 'Generates export caches for every export type and for every organisation. This process is heavy, schedule so it might be a good idea to schedule this outside of working hours and before your daily automatic imports on connected services are scheduled.', 1391601600, 'Not scheduled yet.'), +(2, 'pull_all', 0, '12:00', 0, 'Initiates a full pull for all eligible instances.', 1391601600, 'Not scheduled yet.'), +(3, 'push_all', 0, '12:00', 0, 'Initiates a full push for all eligible instances.', 1391601600, 'Not scheduled yet.'); diff --git a/VERSION.json b/VERSION.json index 0c9d4a63f..661ef05e9 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1 +1 @@ -{"major":2, "minor":3, "hotfix":27} \ No newline at end of file +{"major":2, "minor":3, "hotfix":40} diff --git a/app/Console/Command/AdminShell.php b/app/Console/Command/AdminShell.php index 7f59c255b..9a145f092 100644 --- a/app/Console/Command/AdminShell.php +++ b/app/Console/Command/AdminShell.php @@ -5,18 +5,9 @@ class AdminShell extends AppShell public $uses = array('Event'); public function jobGenerateCorrelation() { + $jobId = $this->args[0]; $this->loadModel('Job'); - $this->Job->create(); - $data = array( - 'worker' => 'default', - 'job_type' => 'generate correlation', - 'job_input' => 'All attributes', - 'status' => 0, - 'retries' => 0, - 'message' => 'Job created.', - ); - $this->Job->save($data); - $jobID = $this->Job->id; + $this->Job->id = $jobId; $this->loadModel('Correlation'); $this->Correlation->deleteAll(array('id !=' => ''), false); $this->loadModel('Attribute'); @@ -26,7 +17,9 @@ class AdminShell extends AppShell // for all attributes.. $total = count($attributes); foreach ($attributes as $k => $attribute) { - $this->Job->saveField('progress', $k/$total*100); + if ($k > 0 && $k % 1000 == 0) { + $this->Job->saveField('progress', $k/$total*100); + } $this->Attribute->__afterSaveCorrelation($attribute['Attribute']); } $this->Job->saveField('progress', 100); diff --git a/app/Console/Command/EventShell.php b/app/Console/Command/EventShell.php index 7a8f16c47..ae031324a 100755 --- a/app/Console/Command/EventShell.php +++ b/app/Console/Command/EventShell.php @@ -328,6 +328,17 @@ class EventShell extends AppShell $task['Task']['next_execution_time'] = strtotime('+' . $task['Task']['timer'] . ' hours', $task['Task']['next_execution_time']); } $task['Task']['scheduled_time'] = $this->Task->breakTime($task['Task']['scheduled_time'], $task['Task']['timer']); + $task['Task']['scheduled_time'] = date('H:i', $task['Task']['next_execution_time']); + + // Now that we have figured out when the next execution should happen, it's time to enqueue it. + $process_id = CakeResque::enqueueAt( + $task['Task']['next_execution_time'], + 'default', + 'EventShell', + array('enqueueCaching', $task['Task']['next_execution_time']), + true + ); + $task['Task']['job_id'] = $process_id; $this->Task->save($task); } } diff --git a/app/Console/Command/ServerShell.php b/app/Console/Command/ServerShell.php index 146096138..c2a4c4865 100644 --- a/app/Console/Command/ServerShell.php +++ b/app/Console/Command/ServerShell.php @@ -18,7 +18,7 @@ class ServerShell extends AppShell $this->User->recursive = -1; $user = $this->User->read(null, $userId); $server = $this->Server->read(null, $serverId); - $result = $this->Server->pull($user['User'], null, $technique, $server, $jobId); + $result = $this->Server->pull($user['User'], $serverId, $technique, $server, $jobId); $this->Job->id = $jobId; $this->Job->save(array( 'id' => $jobId, @@ -53,20 +53,23 @@ class ServerShell extends AppShell $serverId = $this->args[0]; $technique = $this->args[1]; $jobId = $this->args[2]; + $userId = $this->args[3]; $this->Job->read(null, $jobId); $server = $this->Server->read(null, $serverId); App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); $HttpSocket = $syncTool->setupHttpSocket($server); - $result = $this->Server->push($serverId, 'full', $jobId, $HttpSocket); + $this->User->recursive = -1; + $user = $this->User->read(array('id', 'org', 'email'), $userId); + $result = $this->Server->push($serverId, 'full', $jobId, $HttpSocket, $user['User']['email']); $this->Job->save(array( 'id' => $jobId, 'message' => 'Job done.', 'progress' => 100, 'status' => 4 )); - if (isset($this->args[3])) { - $this->Task->id = $this->args[4]; + if (isset($this->args[4])) { + $this->Task->id = $this->args[5]; $this->Task->saveField('message', 'Job(s) started at ' . date('d/m/Y - H:i:s') . '.'); } } @@ -91,14 +94,14 @@ class ServerShell extends AppShell 'job_input' => 'Server: ' . $server['Server']['id'], 'retries' => 0, 'org' => $user['User']['org'], - 'process_id' => $this->Task->data['Task']['job_id'], + 'process_id' => 'Part of scheduled pull', 'message' => 'Pushing.', ); $this->Job->save($data); $jobId = $this->Job->id; App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); - $result = $this->Server->pull($user['User'], null, 'full', $server, $jobId); + $result = $this->Server->pull($user['User'], $server['Server']['id'], 'full', $server, $jobId); $this->Job->save(array( 'id' => $jobId, 'message' => 'Job done.', @@ -150,12 +153,14 @@ class ServerShell extends AppShell $timestamp = $this->args[0]; $taskId = $this->args[1]; $org = $this->args[2]; + $userId = $this->args[3]; $this->Task->id = $taskId; $task = $this->Task->read(null, $taskId); if ($timestamp != $task['Task']['next_execution_time']) { return; } - + $this->User->recursive = -1; + $user = $this->User->read(array('id', 'org', 'email'), $userId); $servers = $this->Server->find('all', array('recursive' => -1, 'conditions' => array('push' => 1))); $count = count($servers); foreach ($servers as $k => $server) { @@ -166,7 +171,7 @@ class ServerShell extends AppShell 'job_input' => 'Server: ' . $server['Server']['id'], 'retries' => 0, 'org' => $org, - 'process_id' => $this->Task->data['Task']['job_id'], + 'process_id' => 'Part of scheduled push', 'message' => 'Pushing.', ); $this->Job->save($data); @@ -174,7 +179,7 @@ class ServerShell extends AppShell App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); $HttpSocket = $syncTool->setupHttpSocket($server); - $result = $this->Server->push($server['Server']['id'], 'full', $jobId, $HttpSocket); + $result = $this->Server->push($server['Server']['id'], 'full', $jobId, $HttpSocket, $user['User']['email']); } $task['Task']['message'] = count($servers) . ' job(s) completed at ' . date('d/m/Y - H:i:s') . '.'; if ($task['Task']['timer'] > 0) { @@ -191,7 +196,7 @@ class ServerShell extends AppShell $task['Task']['next_execution_time'], 'default', 'ServerShell', - array('enqueuePush', $task['Task']['next_execution_time'], $taskId, $org), + array('enqueuePush', $task['Task']['next_execution_time'], $taskId, $org, $userId), true ); $task['Task']['job_id'] = $process_id; diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index c33173353..b9674b3ca 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -116,6 +116,7 @@ class AppController extends Controller { // instead of using checkAction(), like we normally do from controllers when trying to find out about a permission flag, we can use getActions() // getActions returns all the flags in a single SQL query if ($this->Auth->user()) { + //$this->_refreshAuth(); $this->set('mispVersion', $this->mispVersion); $role = $this->getActions(); $this->set('me', $this->Auth->user()); diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index 776c081da..58dfc30e0 100755 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -81,7 +81,7 @@ class AttributesController extends AppController { */ public function index() { $this->Attribute->recursive = 0; - $this->Attribute->contain = array('Event.id', 'Event.orgc', 'Event.org'); + $this->Attribute->contain = array('Event.id', 'Event.orgc', 'Event.org', 'Event.info'); $this->set('isSearch', 0); $this->set('attributes', $this->paginate()); $this->set('attrDescriptions', $this->Attribute->fieldDescriptions); @@ -939,7 +939,7 @@ class AttributesController extends AppController { // attachment will be deleted with the beforeDelete() function in the Model if ($this->Attribute->delete()) { // delete the attribute from remote servers - $this->__deleteAttributeFromServers($uuid); + //$this->__deleteAttributeFromServers($uuid); // We have just deleted the attribute, let's also check if there are any shadow attributes that were attached to it and delete them $this->loadModel('ShadowAttribute'); @@ -1088,12 +1088,14 @@ class AttributesController extends AppController { if ($this->request->is('post') && ($this->request->here == $fullAddress)) { $keyword = $this->request->data['Attribute']['keyword']; $keyword2 = $this->request->data['Attribute']['keyword2']; + $tags = $this->request->data['Attribute']['tags']; $org = $this->request->data['Attribute']['org']; $type = $this->request->data['Attribute']['type']; $ioc = $this->request->data['Attribute']['ioc']; $this->set('ioc', $ioc); $category = $this->request->data['Attribute']['category']; $this->set('keywordSearch', $keyword); + $this->set('tags', $tags); $keyWordText = null; $keyWordText2 = null; $keyWordText3 = null; @@ -1114,28 +1116,74 @@ class AttributesController extends AppController { $temp = array(); $temp2 = array(); foreach ($keywordArray as $keywordArrayElement) { - $saveWord = trim($keywordArrayElement); - $keywordArrayElement = '%' . trim($keywordArrayElement) . '%'; - if ($keywordArrayElement != '%%') { - if ($keywordArrayElement[1] == '!') { - if (preg_match('@^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$@', substr($saveWord, 2))) { - $cidrresults = $this->Cidr->CIDR($saveWord); - foreach ($cidrresults as $result) { - array_push($temp2, array('Attribute.value NOT LIKE' => $result)); + $saveWord = trim(strtolower($keywordArrayElement)); + if ($saveWord != '') { + $toInclude = true; + if ($saveWord[0] == '!') { + $toInclude = false; + $saveWord = substr($saveWord, 1); + } + + if (preg_match('@^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$@', $saveWord)) { + $cidrresults = $this->Cidr->CIDR($saveWord); + foreach ($cidrresults as $result) { + $result = strtolower($result); + if (strpos($result, '|')) { + $resultParts = explode('|', $result); + if (!toInclude) { + $temp2[] = array( + 'AND' => array( + 'LOWER(Attribute.value1) NOT LIKE' => $resultParts[0], + 'LOWER(Attribute.value2) NOT LIKE' => $resultParts[1], + )); + } else { + $temp[] = array( + 'AND' => array( + 'LOWER(Attribute.value1)' => $resultParts[0], + 'LOWER(Attribute.value2)' => $resultParts[1], + )); + } + } else { + if (!$toInclude) { + array_push($temp2, array('LOWER(Attribute.value1) NOT LIKE' => $result)); + array_push($temp2, array('LOWER(Attribute.value2) NOT LIKE' => $result)); + } else { + array_push($temp, array('LOWER(Attribute.value1) LIKE' => $result)); + array_push($temp, array('LOWER(Attribute.value2) LIKE' => $result)); + } } - } else { - array_push($temp2, array('Attribute.value NOT LIKE' => '%' . substr($keywordArrayElement, 2))); } } else { - if (preg_match('@^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$@', $saveWord)) { - $cidrresults = $this->Cidr->CIDR($saveWord); - foreach ($cidrresults as $result) { - array_push($temp, array('Attribute.value LIKE' => $result)); + if (strpos($saveWord, '|')) { + $resultParts = explode('|', $saveWord); + if (!$toInclude) { + $temp2[] = array( + 'AND' => array( + 'LOWER(Attribute.value1) NOT LIKE' => '%' . $resultParts[0], + 'LOWER(Attribute.value2) NOT LIKE' => $resultParts[1] . '%', + )); + } else { + $temp2[] = array( + 'AND' => array( + 'LOWER(Attribute.value1)' => '%' . $resultParts[0], + 'LOWER(Attribute.value2)' => $resultParts[1] . '%', + )); } } else { - array_push($temp, array('Attribute.value LIKE' => $keywordArrayElement)); + if (!$toInclude) { + array_push($temp2, array('LOWER(Attribute.value1) NOT LIKE' => '%' . $saveWord . '%')); + array_push($temp2, array('LOWER(Attribute.value2) NOT LIKE' => '%' . $saveWord . '%')); + } else { + array_push($temp, array('LOWER(Attribute.value1) LIKE' => '%' . $saveWord . '%')); + array_push($temp, array('LOWER(Attribute.value2) LIKE' => '%' . $saveWord . '%')); + } } } + if ($toInclude) { + array_push($temp, array('LOWER(Attribute.comment) LIKE' => '%' . $saveWord . '%')); + } else { + array_push($temp2, array('LOWER(Attribute.comment) NOT LIKE' => '%' . $saveWord . '%')); + } } if ($i == 1 && $saveWord != '') $keyWordText = $saveWord; else if (($i > 1 && $i < 10) && $saveWord != '') $keyWordText = $keyWordText . ', ' . $saveWord; @@ -1175,6 +1223,19 @@ class AttributesController extends AppController { $conditions['AND'][] = $temp; } } + if (!empty($tags)) { + $include = array(); + $exclude = array(); + $keywordArray = explode("\n", $tags); + foreach ($keywordArray as $tagname) { + $tagname = trim($tagname); + if (substr($tagname, 0, 1) === '!') $exclude[] = substr($tagname, 1); + else $include[] = $tagname; + } + $this->loadModel('Tag'); + if (!empty($include)) $conditions['AND'][] = array('OR' => array('Attribute.event_id' => $this->Tag->findTags($include))); + if (!empty($exclude)) $conditions['AND'][] = array('Attribute.event_id !=' => $this->Tag->findTags($exclude)); + } if ($type != 'ALL') { $conditions['Attribute.type ='] = $type; } @@ -1204,7 +1265,6 @@ class AttributesController extends AppController { $conditions['AND'][] = $temp; } } - if ($this->request->data['Attribute']['alternate']) { $events = $this->searchAlternate($conditions); $this->set('events', $events); @@ -1215,7 +1275,7 @@ class AttributesController extends AppController { 'limit' => 60, 'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 attributes? 'conditions' => $conditions, - 'contain' => array('Event.orgc', 'Event.id', 'Event.org', 'Event.user_id') + 'contain' => array('Event.orgc', 'Event.id', 'Event.org', 'Event.user_id', 'Event.info') ); if (!$this->_isSiteAdmin()) { // merge in private conditions @@ -1244,7 +1304,6 @@ class AttributesController extends AppController { } } $this->set('attributes', $attributes); - // and store into session $this->Session->write('paginate_conditions', $this->paginate); $this->Session->write('paginate_conditions_keyword', $keyword); @@ -1252,6 +1311,7 @@ class AttributesController extends AppController { $this->Session->write('paginate_conditions_org', $org); $this->Session->write('paginate_conditions_type', $type); $this->Session->write('paginate_conditions_ioc', $ioc); + $this->Session->write('paginate_conditions_tags', $tags); $this->Session->write('paginate_conditions_category', $category); $this->Session->write('search_find_idlist', $idList); $this->Session->write('search_find_attributeidlist', $attributeIdList); @@ -1278,17 +1338,18 @@ class AttributesController extends AppController { $this->set('attrDescriptions', $this->Attribute->fieldDescriptions); $this->set('typeDefinitions', $this->Attribute->typeDefinitions); $this->set('categoryDefinitions', $this->Attribute->categoryDefinitions); - // get from Session $keyword = $this->Session->read('paginate_conditions_keyword'); $keyword2 = $this->Session->read('paginate_conditions_keyword2'); $org = $this->Session->read('paginate_conditions_org'); $type = $this->Session->read('paginate_conditions_type'); $category = $this->Session->read('paginate_conditions_category'); + $tags = $this->Session->read('paginate_conditions_tags'); $this->set('keywordSearch', $keyword); $this->set('keywordSearch2', $keyword2); $this->set('orgSearch', $org); $this->set('typeSearch', $type); + $this->set('tags', $tags); $this->set('isSearch', 1); $this->set('categorySearch', $category); @@ -1662,7 +1723,11 @@ class AttributesController extends AppController { $this->__downloadAttachment($this->Attribute->data['Attribute']); } - public function text($key='download', $type="", $tags='') { + public function text($key='download', $type='all', $tags=false, $eventId=false, $allowNonIDS=false) { + if ($eventId === 'null' || $eventId == '0' || $eventId === 'false') $eventId = false; + if ($allowNonIDS === 'null' || $allowNonIDS === '0' || $allowNonIDS === 'false') $allowNonIDS = false; + if ($type === 'null' || $type === '0' || $type === 'false') $type = 'all'; + if ($tags === 'null' || $tags === '0' || $tags === 'false') $tags = false; if ($key != 'download') { // check if the key is valid -> search for users based on key $user = $this->checkAuthUser($key); @@ -1677,7 +1742,7 @@ class AttributesController extends AppController { $this->response->type('txt'); // set the content type $this->header('Content-Disposition: download; filename="misp.' . $type . '.txt"'); $this->layout = 'text/default'; - $attributes = $this->Attribute->text($this->_checkOrg(), $this->_isSiteAdmin(), $type, $tags); + $attributes = $this->Attribute->text($this->_checkOrg(), $this->_isSiteAdmin(), $type, $tags, $eventId, $allowNonIDS); $this->loadModel('Whitelist'); $attributes = $this->Whitelist->removeWhitelistedFromArray($attributes, true); $this->set('attributes', $attributes); @@ -1693,9 +1758,33 @@ class AttributesController extends AppController { public function generateCorrelation() { if (!self::_isSiteAdmin()) throw new NotFoundException(); - $k = $this->Attribute->generateCorrelation(); - $this->Session->setFlash(__('All done. ' . $k . ' attributes processed.')); - $this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration')); + if (!Configure::read('MISP.background_jobs')) { + $k = $this->Attribute->generateCorrelation(); + $this->Session->setFlash(__('All done. ' . $k . ' attributes processed.')); + $this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration')); + } else { + $job = ClassRegistry::init('Job'); + $job->create(); + $data = array( + 'worker' => 'default', + 'job_type' => 'generate correlation', + 'job_input' => 'All attributes', + 'status' => 0, + 'retries' => 0, + 'org' => 'ADMIN', + 'message' => 'Job created.', + ); + $job->save($data); + $jobId = $job->id; + $process_id = CakeResque::enqueue( + 'default', + 'AdminShell', + array('jobGenerateCorrelation', $jobId) + ); + $job->saveField('process_id', $process_id); + $this->Session->setFlash(__('Job queued. You can view the progress if you navigate to the active jobs view (administration -> jobs).')); + $this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration')); + } } public function fetchViewValue($id, $field = null) { @@ -1885,6 +1974,8 @@ class AttributesController extends AppController { 'recursive' => -1 )); $event['Event']['published'] = 0; + $date = new DateTime(); + $event['Event']['timestamp'] = $date->getTimestamp(); $this->Attribute->Event->save($event); } else { $message .= 'Update completed with some errors.'; diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 3287659b5..162f89142 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -170,6 +170,8 @@ class EventsController extends AppController { } $includeIDs = array_keys($includeIDs); $excludeIDs = array_keys($excludeIDs); + // return -1 as the only value in includedIDs if both arrays are empty. This will mean that no events will be shown if there was no hit + if (empty($includeIDs) && empty($excludeIDs)) $includeIDs[] = -1; return array($includeIDs, $excludeIDs); } @@ -2819,6 +2821,7 @@ class EventsController extends AppController { 'email-dst' => 'Payload delivery', 'text' => 'Other', ); + $this->set('typeList', array_keys($this->Event->Attribute->typeDefinitions)); $this->set('defaultCategories', $defaultCategories); $this->set('typeCategoryMapping', $typeCategoryMapping); $this->set('resultArray', $resultArray); @@ -2841,26 +2844,42 @@ class EventsController extends AppController { $failed = 0; foreach ($this->request->data['Attribute'] as $k => $attribute) { if ($attribute['save'] == '1') { - $this->Event->Attribute->create(); - $attribute['distribution'] = $event['Event']['distribution']; - $attribute['comment'] = 'Imported via the freetext import.'; - $attribute['event_id'] = $id; - if ($this->Event->Attribute->save($attribute)) { - $saved++; + if ($attribute['type'] == 'ip-src/ip-dst') { + $types = array('ip-src', 'ip-dst'); } else { - $failed++; + $types = array($attribute['type']); + } + foreach ($types as $type) { + $this->Event->Attribute->create(); + $attribute['type'] = $type; + $attribute['distribution'] = $event['Event']['distribution']; + if (empty($attribute['comment'])) $attribute['comment'] = 'Imported via the freetext import.'; + $attribute['event_id'] = $id; + if ($this->Event->Attribute->save($attribute)) { + $saved++; + } else { + $failed++; + } } } } - if ($saved > 0 && $event['Event']['published'] == 1) { + if ($saved > 0) { $event = $this->Event->find('first', array( 'conditions' => array('Event.id' => $id), 'recursive' => -1 )); - $event['Event']['published'] = 0; + if ($event['Event']['published'] == 1) { + $event['Event']['published'] = 0; + } + $date = new DateTime(); + $event['Event']['timestamp'] = $date->getTimestamp(); $this->Event->save($event); } - $this->Session->setFlash($saved . ' attributes created. ' . $failed . ' attributes could not be saved. This may be due to attributes with similar values already existing.'); + if ($failed > 0) { + $this->Session->setFlash($saved . ' attributes created. ' . $failed . ' attributes could not be saved. This may be due to attributes with similar values already existing.'); + } else { + $this->Session->setFlash($saved . ' attributes created.'); + } $this->redirect(array('controller' => 'events', 'action' => 'view', $id)); } else { throw new MethodNotAllowedException(); @@ -3022,4 +3041,87 @@ class EventsController extends AppController { $this->set('_serialize', 'data'); } } -} + + public function exportChoice($id) { + $event = $this->Event->find('first' ,array( + 'conditions' => array('id' => $id), + 'recursive' => -1, + 'fields' => array('distribution', 'orgc','id', 'published'), + )); + if (empty($event) || (!$this->_isSiteAdmin() && $event['Event']['orgc'] != $this->Auth->user('org') && $event['Event']['distribution'] < 1)) throw new NotFoundException('Event not found or you are not authorised to view it.'); + $exports = array( + 'xml' => array( + 'url' => '/events/xml/download/' . $id, + 'text' => 'MISP XML (metadata + all attributes)', + 'requiresPublished' => false, + 'checkbox' => true, + 'checkbox_text' => 'Encode Attachments', + 'checkbox_set' => '/true' + ), + 'json' => array( + 'url' => '/events/view/' . $id . 'json', + 'text' => 'MISP JSON (metadata + all attributes)', + 'requiresPublished' => false, + 'checkbox' => false, + ), + 'openIOC' => array( + 'url' => '/events/downloadOpenIOCEvent/' . $id, + 'text' => 'OpenIOC (all indicators marked to IDS)', + 'requiresPublished' => true, + 'checkbox' => false, + ), + 'csv' => array( + 'url' => '/events/csv/download/' . $id . '/1', + 'text' => 'CSV', + 'requiresPublished' => true, + 'checkbox' => true, + 'checkbox_text' => 'Include non-IDS marked attributes', + 'checkbox_set' => '/1' + ), + 'stix_xml' => array( + 'url' => '/events/stix/download/' . $id . '.xml', + 'text' => 'STIX XML (metadata + all attributes)', + 'requiresPublished' => true, + 'checkbox' => true, + 'checkbox_text' => 'Encode Attachments', + 'checkbox_set' => '/true' + ), + 'stix_json' => array( + 'url' => '/events/stix/download/' . $id . '.json', + 'text' => 'STIX JSON (metadata + all attributes)', + 'requiresPublished' => true, + 'checkbox' => true, + 'checkbox_text' => 'Encode Attachments', + 'checkbox_set' => '/true' + ), + 'suricata' => array( + 'url' => '/events/nids/suricata/download/' . $id, + 'text' => 'Download Suricata rules', + 'requiresPublished' => true, + 'checkbox' => false, + ), + 'snort' => array( + 'url' => '/events/nids/snort/download/' . $id, + 'text' => 'Download Snort rules', + 'requiresPublished' => true, + 'checkbox' => false, + ), + 'text' => array( + 'url' => '/attributes/text/download/all/false/' . $id, + 'text' => 'Export all attribute values as a text file', + 'requiresPublished' => true, + 'checkbox' => true, + 'checkbox_text' => 'Include non-IDS marked attributes', + 'checkbox_set' => '/true' + ), + ); + if ($event['Event']['published'] == 0) { + foreach ($exports as $k => $export) { + if ($export['requiresPublished']) unset($exports[$k]); + } + } + $this->set('exports', $exports); + $this->set('id', $id); + $this->render('ajax/exportChoice'); + } +} \ No newline at end of file diff --git a/app/Controller/JobsController.php b/app/Controller/JobsController.php index 493dc368c..2ed87788b 100644 --- a/app/Controller/JobsController.php +++ b/app/Controller/JobsController.php @@ -54,7 +54,7 @@ class JobsController extends AppController { } public function getGenerateCorrelationProgress($id) { - //if (!self::_isSiteAdmin()) throw new NotFoundException(); + if (!self::_isSiteAdmin()) throw new NotFoundException(); $progress = $this->Job->findById($id); if (!$progress) { $progress = 0; diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index 91089bf94..36ffe81d1 100755 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -233,7 +233,7 @@ class ServersController extends AppController { App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); $HttpSocket = $syncTool->setupHttpSocket($server); - $result = $this->Server->push($id, $technique, false, $HttpSocket); + $result = $this->Server->push($id, $technique, false, $HttpSocket, $this->Auth->user('email')); $this->set('successes', $result[0]); $this->set('fails', $result[1]); } else { @@ -253,7 +253,7 @@ class ServersController extends AppController { $process_id = CakeResque::enqueue( 'default', 'ServerShell', - array('push', $id, $technique, $jobId) + array('push', $id, $technique, $jobId, $this->Auth->user('id')) ); $this->Job->saveField('process_id', $process_id); $this->Session->setFlash('Push queued for background execution.'); diff --git a/app/Controller/ShadowAttributesController.php b/app/Controller/ShadowAttributesController.php index b7d50ce90..09bfcb2a0 100644 --- a/app/Controller/ShadowAttributesController.php +++ b/app/Controller/ShadowAttributesController.php @@ -160,6 +160,8 @@ class ShadowAttributesController extends AppController { $event['Event']['proposal_email_lock'] = 0; } $event['Event']['published'] = 0; + $date = new DateTime(); + $event['Event']['timestamp'] = $date->getTimestamp(); $this->autoRender = false; if ($this->Event->save($event, array('fieldList' => $fieldList))) { $this->Log = ClassRegistry::init('Log'); diff --git a/app/Controller/TasksController.php b/app/Controller/TasksController.php index 2017ad39c..a5c6a9333 100644 --- a/app/Controller/TasksController.php +++ b/app/Controller/TasksController.php @@ -99,7 +99,7 @@ class TasksController extends AppController { $timestamp, 'default', 'ServerShell', - array('enqueuePush', $timestamp, $id, $this->Auth->user('org')), + array('enqueuePush', $timestamp, $id, $this->Auth->user('org'), $this->Auth->user('id')), true ); $this->Task->id = $id; diff --git a/app/Controller/TemplatesController.php b/app/Controller/TemplatesController.php index 2cf9c9724..8117f5b88 100644 --- a/app/Controller/TemplatesController.php +++ b/app/Controller/TemplatesController.php @@ -351,6 +351,8 @@ class TemplatesController extends AppController { 'recursive' => -1 )); $event['Event']['published'] = 0; + $date = new DateTime(); + $event['Event']['timestamp'] = $date->getTimestamp(); $this->Event->save($event); if ($fails == 0) $this->Session->setFlash(__('Event populated, ' . $count . ' attributes successfully created.')); else $this->Session->setFlash(__('Event populated, but ' . $fails . ' attributes could not be saved.')); diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 0860e83b0..caf68704a 100755 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -783,7 +783,7 @@ class UsersController extends AppController { throw new MethodNotAllowedException(); } $this->User->recursive = 0; - $temp = $this->User->find('all', array('fields' => array('email', 'gpgkey'))); + $temp = $this->User->find('all', array('fields' => array('email', 'gpgkey'), 'order' => array('email ASC'))); $emails = array(); $gpgKeys = array(); // save all the emails of the users and set it for the dropdown list in the form @@ -850,7 +850,6 @@ class UsersController extends AppController { } } } - // If the recipient is a user, and the action to create a password, create it and squeeze it between the main message and the signature if ($this->request->data['User']['recipient'] == 1) { $recipients[0] = $emails[$this->request->data['User']['recipientEmailList']]; @@ -866,6 +865,7 @@ class UsersController extends AppController { require_once 'Crypt/GPG.php'; $i = 0; + $this->Log = ClassRegistry::init('Log'); foreach ($recipients as $recipient) { if (!empty($recipientGPG[$i])) { $gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'))); // , 'debug' => true @@ -885,7 +885,6 @@ class UsersController extends AppController { } else { $encryptedMessage = $message[$i]; } - // prepare the email $this->Email->from = Configure::read('MISP.email'); $this->Email->to = $recipients[$i]; @@ -897,6 +896,28 @@ class UsersController extends AppController { // send it $result = $this->Email->send(); + $this->Log->create(); + if ($result) { + $this->Log->save(array( + 'org' => $this->Auth->user('org'), + 'model' => 'User', + 'model_id' => $this->Auth->user('id'), + 'email' => $this->Auth->user('email'), + 'action' => 'admin_email', + 'title' => 'Admin email to ' . $recipients[$i] . ' sent, titled "' . $subject . '".', + 'change' => null, + )); + } else { + $this->Log->save(array( + 'org' => $this->Auth->user('org'), + 'model' => 'User', + 'model_id' => $this->Auth->user('id'), + 'email' => $this->Auth->user('email'), + 'action' => 'admin_email', + 'title' => 'Admin email to ' . $recipients[$i] . ' failed.', + 'change' => null, + )); + } // if sending successful and action was a password change, update the user's password. if ($result && $this->request->data['User']['action'] == '1') { diff --git a/app/Lib/Tools/ComplexTypeTool.php b/app/Lib/Tools/ComplexTypeTool.php index 0ac74f1ff..4d79d3d60 100644 --- a/app/Lib/Tools/ComplexTypeTool.php +++ b/app/Lib/Tools/ComplexTypeTool.php @@ -84,11 +84,11 @@ class ComplexTypeTool { if (strlen($input) == 64 && preg_match("#[0-9a-f]{64}$#", $input)) return array('types' => array('sha256'), 'to_ids' => true, 'default_type' => 'sha256'); // check for IP - if (filter_var($input, FILTER_VALIDATE_IP)) return array('types' => array('ip-dst', 'ip-src'), 'to_ids' => true, 'default_type' => 'ip-dst'); + if (filter_var($input, FILTER_VALIDATE_IP)) return array('types' => array('ip-dst', 'ip-src', 'ip-src/ip-dst'), 'to_ids' => true, 'default_type' => 'ip-dst'); if (strpos($input, '/')) { $temp = explode('/', $input); if (count($temp == 2)) { - if (filter_var($temp[0], FILTER_VALIDATE_IP)) return array('types' => array('ip-dst', 'ip-src'), 'to_ids' => true, 'default_type' => 'ip-dst'); + if (filter_var($temp[0], FILTER_VALIDATE_IP)) return array('types' => array('ip-dst', 'ip-src', 'ip-src/ip-dst'), 'to_ids' => true, 'default_type' => 'ip-dst'); } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 36676a9fe..9523516b5 100755 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -1256,9 +1256,11 @@ class Attribute extends AppModel { return $rules; } - public function text($org, $isSiteAdmin, $type, $tags = '') { + public function text($org, $isSiteAdmin, $type, $tags = false, $eventId = false, $allowNonIDS = false) { //restricting to non-private or same org if the user is not a site-admin. - $conditions['AND'] = array('Attribute.type' => $type, 'Attribute.to_ids =' => 1, 'Event.published =' => 1); + $conditions['AND'] = array(); + if ($allowNonIDS === false) $conditions['AND'] = array('Attribute.to_ids =' => 1, 'Event.published =' => 1); + if ($type !== 'all') $conditions['AND']['Attribute.type'] = $type; if (!$isSiteAdmin) { $temp = array(); $distribution = array(); @@ -1267,8 +1269,10 @@ class Attribute extends AppModel { $conditions['OR'] = $temp; } - // If we sent any tags along, load the associated tag names for each attribute - if ($tags !== '') { + if ($eventId !== false) { + $conditions['AND'][] = array('Event.id' => $eventId); + } elseif ($tags !== false) { + // If we sent any tags along, load the associated tag names for each attribute $tag = ClassRegistry::init('Tag'); $args = $this->dissectArgs($tags); $tagArray = $tag->fetchEventTagIds($args[0], $args[1]); diff --git a/app/Model/Event.php b/app/Model/Event.php index e97fa1cf3..a55831750 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -1367,7 +1367,7 @@ class Event extends AppModel { * * @return bool true if success */ - public function _add(&$data, $fromXml, $user, $or='', $passAlong = null, $fromPull = false, $jobId = null) { + public function _add(&$data, $fromXml, $user, $org='', $passAlong = null, $fromPull = false, $jobId = null) { if ($jobId) { App::import('Component','Auth'); } @@ -1378,7 +1378,11 @@ class Event extends AppModel { //if ($this->checkAction('perm_sync')) $data['Event']['org'] = Configure::read('MISP.org'); //else $data['Event']['org'] = $auth->user('org'); - $data['Event']['org'] = $user['org']; + if ($fromPull) { + $data['Event']['org'] = $org; + } else { + $data['Event']['org'] = $user['org']; + } // set these fields if the event is freshly created and not pushed from another instance. // Moved out of if (!$fromXML), since we might get a restful event without the orgc/timestamp set if (!isset ($data['Event']['orgc'])) $data['Event']['orgc'] = $data['Event']['org']; diff --git a/app/Model/Job.php b/app/Model/Job.php index 7f922fb5c..ce246d766 100644 --- a/app/Model/Job.php +++ b/app/Model/Job.php @@ -7,7 +7,7 @@ App::uses('AppModel', 'Model'); */ class Job extends AppModel { - public function cache($type, $isSiteAdmin, $org, $target, $jobOrg, $sid) { + public function cache($type, $isSiteAdmin, $org, $target, $jobOrg, $sid = null) { $extra = null; $extra2 = null; $shell = 'Event'; diff --git a/app/Model/Log.php b/app/Model/Log.php index ee1230473..b618a24f3 100755 --- a/app/Model/Log.php +++ b/app/Model/Log.php @@ -22,7 +22,8 @@ class Log extends AppModel { 'discard', 'pull', 'push', - 'blacklisted' + 'blacklisted', + 'admin_email' )), 'message' => 'Options : ...' ) diff --git a/app/Model/Server.php b/app/Model/Server.php index c35dc67a5..cd88414c1 100755 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -373,6 +373,14 @@ class Server extends AppModel { 'test' => 'testForTermsFile', 'type' => 'string' ), + 'showorgalternate' => array( + 'level' => 2, + 'description' => 'True enables the alternate org fields for the event index (source org and member org) instead of the traditional way of showing only an org field. This allows users to see if an event was uploaded by a member organisation on their MISP instance, or if it originated on an interconnected instance.', + 'value' => '', + 'errorMessage' => '', + 'test' => 'testBool', + 'type' => 'boolean' + ), ), 'GnuPG' => array( 'branch' => 1, @@ -462,6 +470,9 @@ class Server extends AppModel { App::import('Component','Auth'); $this->Auth = new AuthComponent(new ComponentCollection()); $this->Auth->login($user); + $email = "Scheduled job"; + } else { + $email = $user['email']; } $eventModel = ClassRegistry::init('Event'); App::uses('HttpSocket', 'Network/Http'); @@ -537,7 +548,6 @@ class Server extends AppModel { unset($event['Event']['Attribute']); $event['Event']['Attribute'][0] = $tmp; } - if (is_array($event['Event']['Attribute'])) { $size = is_array($event['Event']['Attribute']) ? count($event['Event']['Attribute']) : 0; for ($i = 0; $i < $size; $i++) { @@ -575,7 +585,7 @@ class Server extends AppModel { if (!$existingEvent) { // add data for newly imported events $passAlong = $server['Server']['url']; - $result = $eventModel->_add($event, $fromXml = true, $user, $server['Server']['organization'], $passAlong, true, $jobId); + $result = $eventModel->_add($event, $fromXml = true, $user, $server['Server']['org'], $passAlong, true, $jobId); if ($result) $successes[] = $eventId; else { $fails[$eventId] = 'Failed (partially?) because of validation errors: '. print_r($eventModel->validationErrors, true); @@ -646,14 +656,14 @@ class Server extends AppModel { 'model_id' => $id, 'email' => $user['email'], 'action' => 'pull', - 'title' => 'Pull from ' . $server['Server']['url'] . ' initiated by ' . $user['email'], + 'title' => 'Pull from ' . $server['Server']['url'] . ' initiated by ' . $email, 'change' => count($successes) . ' events and ' . count($pulledProposals) . ' proposals pulled or updated. ' . count($fails) . ' events failed or didn\'t need an update.' )); if (!isset($lastpulledid)) $lastpulledid = 0; return array($successes, $fails, $pulledProposals, $lastpulledid); } - public function push($id = null, $technique=false, $jobId = false, $HttpSocket) { + public function push($id = null, $technique=false, $jobId = false, $HttpSocket, $email = "Scheduled job") { if ($jobId) { $job = ClassRegistry::init('Job'); $job->read(null, $jobId); @@ -737,8 +747,9 @@ class Server extends AppModel { $this->Log->save(array( 'model' => 'Server', 'model_id' => $id, + 'email' => $email, 'action' => 'push', - 'title' => 'Push to ' . $url . '.', + 'title' => 'Push to ' . $url . ' initiated by ' . $email, 'change' => count($successes) . ' events pushed or updated. ' . count($fails) . ' events failed or didn\'t need an update.' )); if ($jobId) { diff --git a/app/Model/Tag.php b/app/Model/Tag.php index 453353e96..27767fc1d 100644 --- a/app/Model/Tag.php +++ b/app/Model/Tag.php @@ -87,7 +87,7 @@ class Tag extends AppModel { public function findTags($array) { $ids = array(); foreach ($array as $a) { - $conditions['OR'][] = array('name like' => '%' . $a . '%'); + $conditions['OR'][] = array('LOWER(name) like' => '%' . strtolower($a) . '%'); } $params = array( 'recursive' => 1, diff --git a/app/View/Attributes/add.ctp b/app/View/Attributes/add.ctp index 6350643fa..d082e77d2 100755 --- a/app/View/Attributes/add.ctp +++ b/app/View/Attributes/add.ctp @@ -57,7 +57,7 @@ ?> -
+ Form->create('Event', array('id' => 'test', 'url' => $baseurl . '/events/index'));?>