diff --git a/.gitignore b/.gitignore index e83c4920b..a2e10cfc1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /app/tmp/cache/views/myapp* /app/files/* !/app/files/empty +/app/tmp/files/* +!/app/tmp/files/empty /app/webroot/img/logo.png /app/Config/bootstrap.php /app/Config/database.php diff --git a/app/Controller/TemplatesController.php b/app/Controller/TemplatesController.php index fc47b2429..61c967fc8 100644 --- a/app/Controller/TemplatesController.php +++ b/app/Controller/TemplatesController.php @@ -1,6 +1,8 @@ Security->unlockedActions = array('saveElementSorting', 'populateEventFromTemplate'); + $this->Security->unlockedActions = array('saveElementSorting', 'populateEventFromTemplate', 'uploadFile', 'deleteTemporaryFile'); } public function fetchFormFromTemplate($id) { @@ -275,20 +277,22 @@ class TemplatesController extends AppController { $this->set('template_id', $template_id); $this->set('event_id', $event_id); if ($this->request->is('post')) { + $errors = array(); + $this->set('template', $this->request->data); $result = $this->Event->Attribute->checkTemplateAttributes($template, $this->request->data, $event_id, $event['Event']['distribution']); - $errors = $result['errors']; - $attributes = $result['attributes']; - - if (empty($errors) && empty($this->request->data['Template']['modify'])) { - $this->set('template', $this->request->data); - $this->set('attributes', $attributes); - $this->set('distributionLevels', $this->Event->distributionLevels); - $this->render('populate_event_from_template_attributes'); - } else { - $this->set('template', $this->request->data); - $this->set('errors', $errors); + if (isset($this->request->data['Template']['modify']) || !empty($result['errors'])) { + $fileArray = $this->request->data['Template']['fileArray']; + $this->set('fileArray', $fileArray); + $this->set('errors', $result['errors']); $this->set('templateData', $template); $this->set('validTypeGroups', $this->Event->Attribute->validTypeGroups); + } else { + $this->set('errors', $result['errors']); + $this->set('attributes', $result['attributes']); + $fileArray = $this->request->data['Template']['fileArray']; + $this->set('fileArray', $fileArray); + $this->set('distributionLevels', $this->Event->distributionLevels); + $this->render('populate_event_from_template_attributes'); } } else { $this->set('templateData', $template); @@ -303,17 +307,42 @@ class TemplatesController extends AppController { 'conditions' => array('id' => $event_id), 'recursive' => -1, 'fields' => array('id', 'orgc', 'distribution'), + 'contain' => 'EventTag', )); if (empty($event)) throw new MethodNotAllowedException('Event not found or you are not authorised to edit it.'); if (!$this->_isSiteAdmin()) { if ($event['Event']['orgc'] != $this->Auth->user('org')) throw new MethodNotAllowedException('Event not found or you are not authorised to edit it.'); } + + $template = $this->Template->find('first', array( + 'id' => $template_id, + 'recursive' => -1, + 'contain' => 'TemplateTag', + 'fields' => 'id', + )); + + foreach ($template['TemplateTag'] as $tag) { + $exists = false; + foreach ($event['EventTag'] as $eventTag) { + if ($eventTag['tag_id'] == $tag['tag_id']) $exists = true; + } + if (!$exists) { + $this->Event->EventTag->create(); + $this->Event->EventTag->save(array('event_id' => $event_id, 'tag_id' => $tag['tag_id'])); + } + } if (isset($this->request->data['Template']['attributes'])) { $attributes = unserialize($this->request->data['Template']['attributes']); $this->loadModel('Attribute'); $fails = 0; - foreach($attributes as $k => $attribute) { + foreach($attributes as $k => &$attribute) { + if (isset($attribute['data'])) { + $file = new File(APP . 'tmp/files/' . $attribute['data']); + $content = $file->read(); + $attribute['data'] = base64_encode($content); + $file->delete(); + } $this->Attribute->create(); if (!$this->Attribute->save(array('Attribute' => $attribute))) $fails++; } @@ -328,4 +357,65 @@ class TemplatesController extends AppController { throw new MethodNotAllowedException(); } } + + public function uploadFile($elementId, $batch) { + $this->layout = 'iframe'; + $this->set('batch', $batch); + $this->set('element_id', $elementId); + if ($this->request->is('get')) { + $this->set('element_id', $elementId); + } else if ($this->request->is('post')) { + $fileArray = array(); + $filenames = array(); + $tmp_names = array(); + $element_ids = array(); + $result = array(); + $added = 0; + $failed = 0; + // filename checks + foreach ($this->request->data['Template']['file'] as $k => $file) { + if ($file['size'] > 0 && $file['error'] == 0) { + if (preg_match('@^[\w\-. ]+$@', $file['name'])) { + $fn = $this->Template->generateRandomFileName(); + move_uploaded_file($file['tmp_name'], APP . 'tmp/files/' . $fn); + $filenames[] =$file['name']; + $fileArray[] = array('filename' => $file['name'], 'tmp_name' => $fn, 'element_id' => $elementId); + $added++; + } else $failed++; + } else $failed ++; + } + $result = $added . ' files uploaded.'; + if ($failed) { + $result .= ' ' . $failed . ' files either failed to upload, or were empty.'; + $this->set('upload_error', true); + } else { + $this->set('upload_error', false); + } + + $this->set('result', $result); + $this->set('filenames', $filenames); + $this->set('fileArray', json_encode($fileArray)); + } + } + + private function __combineArrays($array, $array2) { + foreach ($array2 as $element) { + if (!in_array($element, $array)) { + $array[] = $element; + } + } + return $array; + } + + public function deleteTemporaryFile($filename) { + if (!$this->request->is('post')) throw new MethodNotAllowedException('This action is restricted to accepting POST requests only.'); + //if (!$this->request->is('ajax')) throw new MethodNotAllowedException('This action is only accessible through AJAX.'); + $this->autoRender = false; + if (preg_match('/^[a-zA-Z0-9]{12}$/', $filename)) { + $file = new File(APP . 'tmp/files/' . $filename); + if ($file->exists()) { + $file->delete(); + } + } + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 20d216426..43f30e611 100755 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -1361,23 +1361,27 @@ class Attribute extends AppModel { } - public function checkTemplateAttributes($template, $data, $event_id, $distribution) { + public function checkTemplateAttributes($template, &$data, $event_id, $distribution) { $result = array(); $errors = array(); $attributes = array(); + $files = array(); + $savedFiles = array(); + if (isset($data['Template']['fileArray'])) $fileArray = json_decode($data['Template']['fileArray'], true); foreach ($template['TemplateElement'] as $element) { if ($element['element_definition'] == 'attribute') { $result = $this->__resolveElementAttribute($element['TemplateElementAttribute'][0], $data['Template']['value_' . $element['id']]); } else if ($element['element_definition'] == 'file') { $temp = array(); - foreach ($data['Template']['file_' . $element['id']] as $fileArray) { - foreach ($fileArray as $k => $file) { - if (($file['name'] != '' && $file['size'] > 0) || ($k + 1 == count($fileArray))) { - $temp[] = $file; - } - } + if (isset($fileArray)) { + foreach ($fileArray as $fileArrayElement) { + if ($fileArrayElement['element_id'] == $element['id']) { + $temp[] = $fileArrayElement; + } + } } $result = $this->__resolveElementFile($element['TemplateElementFile'][0], $temp); + if ($element['TemplateElementFile'][0]['mandatory'] && empty($temp) && empty($errors[$element['id']])) $errors[$element['id']] = 'Error: This field is mandatory.'; } if ($element['element_definition'] == 'file' || $element['element_definition'] == 'attribute') { if ($result['errors']) { @@ -1431,11 +1435,11 @@ class Attribute extends AppModel { return array('attributes' => $results, 'errors' => $errors); } - private function __resolveElementFile($element, $value) { + private function __resolveElementFile($element, $files) { $attributes = array(); $errors = null; $results = array(); - $count = count($value); + $count = count($files); $element['complex'] = false; if ($element['malware']) { $element['type'] = 'malware-sample'; @@ -1444,48 +1448,41 @@ class Attribute extends AppModel { $element['type'] = 'attachment'; $element['to_ids'] = false; } - if ($count == 1 && $value[0]['size'] == 0) { - if ($element['mandatory']) $errors = 'This field is mandatory.'; - } else { - if ($count > 1) unset($value[$count-1]); - foreach ($value as $v) { - if (!($v['size'] > 0 && $v['error'] == 0)) { - $errors = 'File upload failed or the file was empty.'; - } else if (!preg_match('@^[\w\-. ]+$@', $v['name'])) { - $errors = 'Filename not allowed.'; - } else { - if ($element['malware']) { - $malwareName = $v['name'] . '|' . hash_file('md5', $v['tmp_name']); - $attributes[] = $this->__createAttribute($element, $malwareName); - $file = new File($v['tmp_name']); - if (!$file->exists()) { - $errors = 'File cannot be read.'; - } else { - $content = $file->read(); - $attributes[count($attributes) - 1]['data'] = base64_encode($content); - $element['type'] = 'filename|sha256'; - $sha256 = $v['name'] . '|' . (hash_file('sha256', $v['tmp_name'])); - $attributes[] = $this->__createAttribute($element, $sha256); - $element['type'] = 'filename|sha1'; - $sha1 = $v['name'] . '|' . (hash_file('sha1', $v['tmp_name'])); - $attributes[] = $this->__createAttribute($element, $sha1); - } - } else { - $attributes[] = $this->__createAttribute($element, $v['name']); - $file = new File($v['tmp_name']); - if (!$file->exists()) { - $errors = 'File cannot be read.'; - } else { - $content = $file->read(); - $attributes[count($attributes) - 1]['data'] = base64_encode($content); - } - } - } - } + foreach ($files as $file) { + if (!preg_match('@^[\w\-. ]+$@', $file['filename'])) { + $errors = 'Filename not allowed.'; + continue; + } + if ($element['malware']) { + $malwareName = $file['filename'] . '|' . hash_file('md5', APP . 'tmp/files/' . $file['tmp_name']); + $tmp_file = new File(APP . 'tmp/files/' . $file['tmp_name']); + if (!$tmp_file->exists()) { + $errors = 'File cannot be read.'; + } else { + $attributes[] = $this->__createAttribute($element, $malwareName); + $content = $tmp_file->read(); + $attributes[count($attributes) - 1]['data'] = $file['tmp_name']; + $element['type'] = 'filename|sha256'; + $sha256 = $file['filename'] . '|' . (hash_file('sha256', APP . 'tmp/files/' . $file['tmp_name'])); + $attributes[] = $this->__createAttribute($element, $sha256); + $element['type'] = 'filename|sha1'; + $sha1 = $file['filename'] . '|' . (hash_file('sha1', APP . 'tmp/files/' . $file['tmp_name'])); + $attributes[] = $this->__createAttribute($element, $sha1); + } + } else { + $attributes[] = $this->__createAttribute($element, $file['filename']); + $tmp_file = new File(APP . 'tmp/files/' . $file['tmp_name']); + if (!$tmp_file->exists()) { + $errors = 'File cannot be read.'; + } else { + $content = $tmp_file->read(); + $attributes[count($attributes) - 1]['data'] = $file['tmp_name']; + } + } } - return array('attributes' => $attributes, 'errors' => $errors); + return array('attributes' => $attributes, 'errors' => $errors, 'files' => $files); } - + private function __createAttribute($element, $value) { $attribute = array( 'comment' => $element['name'], diff --git a/app/Model/Event.php b/app/Model/Event.php index ad01ea5aa..9ad0460a4 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -1150,45 +1150,46 @@ class Event extends AppModel { // But only do this if it is allowed in the bootstrap.php file. // if ($eventIsPrivate) { - $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => "", 'User.org =' => $event['Event']['org']); + $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => "", 'User.org =' => $event['Event']['org']); } else { - $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => ""); + $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => ""); } - if ('false' == Configure::read('GnuPG.onlyencrypted')) { - $alertUsers = $this->User->find('all', array( - 'conditions' => $conditions, - 'recursive' => 0, - )); - $max = count($alertUsers); - foreach ($alertUsers as $k => &$user) { - // prepare the the unencrypted email - $Email = new CakeEmail(); - $Email->from(Configure::read('MISP.email')); - $Email->to($user['User']['email']); - $Email->subject("[" . Configure::read('MISP.org') . " " . Configure::read('MISP.name') . "] Event " . $id . " - " . $subject . $event['ThreatLevel']['name'] . " - TLP Amber"); - $Email->emailFormat('text'); // both text or html - // send it - $Email->send($bodySigned); - $Email->reset(); - if ($processId) { - $this->Job->id = $processId; - $this->Job->saveField('progress', $k / $max * 50); - } + if ('false' == Configure::read('GnuPG.onlyencrypted')) { + $alertUsers = $this->User->find('all', array( + 'conditions' => $conditions, + 'recursive' => 0, + )); + $max = count($alertUsers); + foreach ($alertUsers as $k => &$user) { + // prepare the the unencrypted email + $Email = new CakeEmail(); + $Email->from(Configure::read('MISP.email')); + $Email->to($user['User']['email']); + $Email->subject("[" . Configure::read('MISP.org') . " " . Configure::read('MISP.name') . "] Event " . $id . " - " . $subject . $event['ThreatLevel']['name'] . " - TLP Amber"); + $Email->emailFormat('text'); // both text or html + // send it + $Email->send($bodySigned); + $Email->reset(); + if ($processId) { + $this->Job->id = $processId; + $this->Job->saveField('progress', $k / $max * 50); } } - // - // Build a list of the recipients that wish to receive encrypted mails. - // - if ($eventIsPrivate) { - $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => "", 'User.org =' => $event['Event']['org']); - } else { - $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => ""); - } - $alertUsers = $this->User->find('all', array( - 'conditions' => $conditions, - 'recursive' => 0, - ) - ); + } + // + // Build a list of the recipients that wish to receive encrypted mails. + // + if ($eventIsPrivate) { + $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => "", 'User.org =' => $event['Event']['org']); + } else { + $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => ""); + } + $alertUsers = $this->User->find('all', array( + 'conditions' => $conditions, + 'recursive' => 0, + ) + ); + $max = count($alertUsers); // encrypt the mail for each user and send it separately foreach ($alertUsers as $k => &$user) { // send the email diff --git a/app/Model/Template.php b/app/Model/Template.php index 1afa4c9aa..d9b08f2e2 100644 --- a/app/Model/Template.php +++ b/app/Model/Template.php @@ -65,4 +65,15 @@ class Template extends AppModel { return false; } } + + public function generateRandomFileName() { + $length = 12; + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charLen = strlen($characters) - 1; + $fn = ''; + for ($p = 0; $p < $length; $p++) { + $fn .= $characters[rand(0, $charLen)]; + } + return $fn; + } } diff --git a/app/View/Elements/templateElements/populateTemplateFile.ctp b/app/View/Elements/templateElements/populateTemplateFile.ctp index d412ab73b..1ff5787de 100644 --- a/app/View/Elements/templateElements/populateTemplateFile.ctp +++ b/app/View/Elements/templateElements/populateTemplateFile.ctp @@ -1,25 +1,30 @@ -