From 7b1673d212d1d76e3dd5f35561979be94b12acc3 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Sun, 25 Mar 2012 15:56:29 +0200 Subject: [PATCH] md5 and sha1 hashes now automatically lowercase cleaned up some code and fixed some vulnerabilities --- app/Controller/EventsController.php | 172 +++++++++++------------- app/Controller/SignaturesController.php | 135 ++++++++----------- app/Model/Signature.php | 71 ++++++---- 3 files changed, 181 insertions(+), 197 deletions(-) diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 4294c9d38..5aced8420 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -13,8 +13,8 @@ class EventsController extends AppController { * Components * * @var array - */ - + */ + public $components = array('Security', 'Email'); public $paginate = array( 'limit' => 50, @@ -22,19 +22,19 @@ class EventsController extends AppController { 'Event.date' => 'DESC' ) ); - + function beforeFilter() { // what pages are allowed for non-logged-in users $this->Auth->allow('xml'); $this->Auth->allow('nids'); $this->Auth->allow('text'); - + // These variables are required for every view $this->set('me', $this->Auth->user()); $this->set('isAdmin', $this->_isAdmin()); } - + public function isAuthorized($user) { // Admins can access everything if (parent::isAuthorized($user)) { @@ -45,10 +45,10 @@ class EventsController extends AppController { $eventid = $this->request->params['pass'][0]; return $this->Event->isOwnedByOrg($eventid, $this->Auth->user('org')); } - // the other pages are allowed by logged in users + // the other pages are allowed by logged in users return true; } - + /** * index method * @@ -77,14 +77,14 @@ class EventsController extends AppController { } $this->set('event', $this->Event->read(null, $id)); $this->set('relatedEvents', $this->Event->getRelatedEvents()); - + $related_signatures = array(); $this->loadModel('Signature'); foreach ($this->Event->data['Signature'] as $signature) { $related_signatures[$signature['id']] = $this->Signature->getRelatedSignatures($signature); } $this->set('relatedSignatures', $related_signatures); - + $this->set('categories', $this->Signature->validate['category']['rule'][1]); } @@ -124,12 +124,8 @@ class EventsController extends AppController { if (!$this->Event->exists()) { throw new NotFoundException(__('Invalid event')); } -// Replaced by isAuthorized -// // only edit own events -// $old_event = $this->Event->read(null, $id); -// if (!$this->_isAdmin() && $this->Auth->user('org') != $old_event['Event']['org']) { -// throw new UnauthorizedException('You are only allowed to edit events of your own organisation.'); -// } + // only edit own events verified by isAuthorized + if ($this->request->is('post') || $this->request->is('put')) { // always force the user and org, but do not force it for admins if (!$this->_isAdmin()) { @@ -141,7 +137,7 @@ class EventsController extends AppController { } // we probably also want to remove the alerted flag $this->request->data['Event']['alerted'] = 0; - + // say what fields are to be updated $fieldList=array('user_id', 'org', 'date', 'risk', 'info', 'alerted', 'private'); if ($this->Event->save($this->request->data, true, $fieldList)) { @@ -153,7 +149,7 @@ class EventsController extends AppController { } else { $this->request->data = $this->Event->read(null, $id); } - + // combobox for types $risks = $this->Event->validate['risk']['rule'][1]; $risks = $this->_arrayToValuesIndexArray($risks); @@ -174,12 +170,8 @@ class EventsController extends AppController { if (!$this->Event->exists()) { throw new NotFoundException(__('Invalid event')); } -// Replaced by isAuthorized -// // only edit own events -// $this->Event->read(); -// if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Event->data['Event']['org']) { -// throw new UnauthorizedException('You are only allowed to edit your own events.'); -// } + // only edit own events verified by isAuthorized + if ($this->Event->delete()) { $this->Session->setFlash(__('Event deleted')); $this->redirect(array('action' => 'index')); @@ -197,25 +189,21 @@ class EventsController extends AppController { if (!$this->Event->exists()) { throw new NotFoundException(__('Invalid event')); } - + // only allow form submit CSRF protection. if ($this->request->is('post') || $this->request->is('put')) { $this->Event->id = $id; $this->Event->read(); - -// Replaced by isAuthorized -// // only allow alert for own events or admins -// if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Event->data['Event']['org']) { -// throw new UnauthorizedException('You are only allowed to finish events of your own organisation.'); -// } - + + // only allow alert for own events verified by isAuthorized + // fetch the event and build the body if (1 == $this->Event->data['Event']['alerted']) { $this->Session->setFlash(__('Everyone has already been alerted for this event. To alert again, first edit this event.', true), 'default', array(), 'error'); $this->redirect(array('action' => 'view', $id)); } - + // The mail body, Sanitize::html() is NOT needed as we are sending plain-text mails. $body = ""; $appendlen = 20; @@ -230,7 +218,7 @@ class EventsController extends AppController { if (!empty($relatedEvents)) { foreach ($relatedEvents as $relatedEvent){ $body .= 'Related to : '.Configure::read('CyDefSIG.baseurl').'/events/view/'.$relatedEvent['Event']['id'].' ('.$relatedEvent['Event']['date'].')'."\n" ; - + } } $body .= 'Info : '."\n"; @@ -238,7 +226,7 @@ class EventsController extends AppController { $body .= "\n"; $body .= 'Attributes :'."\n"; $body_temp_other = ""; - + if (isset($this->Event->data['Signature'])) { foreach ($this->Event->data['Signature'] as $signature){ $line = '- '.$signature['type'].str_repeat(' ', $appendlen - 2 - strlen( $signature['type'])).': '.$signature['value']."\n"; @@ -249,15 +237,15 @@ class EventsController extends AppController { } $body .= "\n"; $body .= $body_temp_other; // append the 'other' attribute types to the bottom. - + // sign the body require_once 'Crypt/GPG.php'; $gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'))); $gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); $body_signed = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR); - + $this->loadModel('User'); - + // // Build a list of the recipients that get a non-encrypted mail // But only do this if it is allowed in the bootstrap.php file. @@ -286,7 +274,7 @@ class EventsController extends AppController { // to reset the email fields using the reset method of the Email component. $this->Email->reset(); } - + // // Build a list of the recipients that wish to receive encrypted mails. // @@ -304,34 +292,34 @@ class EventsController extends AppController { $this->Email->subject = "[CyDefSIG] Event ".$id." - ".$this->Event->data['Event']['risk']." - TLP Amber"; $this->Email->template = 'body'; $this->Email->sendAs = 'text'; // both text or html - + // import the key of the user into the keyring - // this is not really necessary, but it enables us to find + // this is not really necessary, but it enables us to find // the correct key-id even if it is not the same as the emailaddress $key_import_output = $gpg->importKey($user['User']['gpgkey']); // say what key should be used to encrypt $gpg = new Crypt_GPG(); $gpg->addEncryptKey($key_import_output['fingerprint']); // use the key that was given in the import - + $body_enc_sig = $gpg->encrypt($body_signed, true); - + $this->set('body', $body_enc_sig); $this->Email->send(); // If you wish to send multiple emails using a loop, you'll need // to reset the email fields using the reset method of the Email component. $this->Email->reset(); } - + // update the DB to set the alerted flag $this->Event->saveField('alerted', 1); - + // redirect to the view event page $this->Session->setFlash(__('Email sent to all participants.', true)); $this->redirect(array('action' => 'view', $id)); } } - - + + /** * Send out an contact email to the person who posted the event. * Users with a GPG key will get the mail encrypted, other users will get the mail unencrypted @@ -341,12 +329,12 @@ class EventsController extends AppController { if (!$this->Event->exists()) { throw new NotFoundException(__('Invalid event')); } - + // User has filled in his contact form, send out the email. if ($this->request->is('post') || $this->request->is('put')) { $message = $this->request->data['Event']['message']; if ($this->_sendContactEmail($id, $message)) { - // LATER when a user is deleted this will create problems. + // LATER when a user is deleted this will create problems. // LATER send the email to all the people who are in the org that created the event // redirect to the view event page $this->Session->setFlash(__('Email sent to the reporter.', true)); @@ -360,8 +348,8 @@ class EventsController extends AppController { $this->data = $this->Event->read(null, $id); } } - - + + /** * * Sends out an email with the request to be contacted about a specific event. @@ -391,7 +379,7 @@ class EventsController extends AppController { $body .="\n"; $body .="\n"; $body .="The event is the following: \n"; - + // print the event in mail-format // LATER place event-to-email-layout in a function $appendlen = 20; @@ -406,7 +394,7 @@ class EventsController extends AppController { if (!empty($relatedEvents)) { foreach ($relatedEvents as $relatedEvent){ $body .= 'Related to : '.Configure::read('CyDefSIG.baseurl').'/events/view/'.$relatedEvent['Event']['id'].' ('.$relatedEvent['Event']['date'].')'."\n" ; - + } } $body .= 'Info : '."\n"; @@ -430,7 +418,7 @@ class EventsController extends AppController { $gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'))); $gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password')); $body_signed = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR); - + if (!empty($reporter['gpgkey'])) { // import the key of the user into the keyring // this isn't really necessary, but it gives it the fingerprint necessary for the next step @@ -438,7 +426,7 @@ class EventsController extends AppController { // say what key should be used to encrypt $gpg = new Crypt_GPG(); $gpg->addEncryptKey($key_import_output['fingerprint']); // use the key that was given in the import - + $body_enc_sig = $gpg->encrypt($body_signed, true); } else { $body_enc_sig = $body_signed; @@ -453,7 +441,7 @@ class EventsController extends AppController { $this->Email->template = 'body'; $this->Email->sendAs = 'text'; // both text or html $this->set('body', $body_enc_sig); - + // Add the GPG key of the user as attachment // LATER sign the attached GPG key if (!empty($me_user['gpgkey'])) { @@ -467,28 +455,28 @@ class EventsController extends AppController { 'gpgkey.asc' => $tmpfname ); } - + // send it $result = $this->Email->send(); - + // remove the temporary gpg file if (!empty($me_user['gpgkey'])) unlink($tmpfname); - + return $result; } - - + + public function export() { // Simply display a static view - + // generate the list of Attribute types $this->loadModel('Signature'); $this->set('sig_types', $this->Signature->validate['type']['rule'][1]); - + } - - + + public function xml($key) { // FIXME implement XML output // check if the key is valid -> search for users based on key @@ -502,7 +490,7 @@ class EventsController extends AppController { $this->header('Content-Type: text/xml'); // set the content type $this->layout = 'xml/default'; // $this->header('Content-Disposition: attachment; filename="cydefsig.xml"'); - + $conditions = array("Event.alerted" => 1); $fields = array('Event.id', 'Event.date', 'Event.risk', 'Event.info'); if ('true' == Configure::read('CyDefSIG.showorg')) { @@ -516,14 +504,14 @@ class EventsController extends AppController { // 'contain' => $contain ); $results = $this->Event->find('all', $params); - - + + /* $xml = Xml::build(''); */ - + $myXmlOriginal = 'value'; $xml = Xml::build($myXmlOriginal); $xml->root->addChild('young', 'new value'); - + // foreach ($results as $result) { // debug($result); // $xml->CyDefSIG->addChild('f', 'b'); @@ -534,8 +522,8 @@ class EventsController extends AppController { // debug($xml->saveXML()); } - - + + public function nids($key) { // check if the key is valid -> search for users based on key $this->loadModel('User'); @@ -548,24 +536,24 @@ class EventsController extends AppController { $this->header('Content-Type: text/plain'); // set the content type $this->header('Content-Disposition: attachment; filename="cydefsig.rules"'); $this->layout = 'text/default'; - + $rules= array(); - + // find events that are published $events = $this->Event->findAllByAlerted(1); $classtype = 'targeted-attack'; - + foreach ($events as $event) { # proto src_ip src_port direction dst_ip dst_port msg rule_content tag sid rev $rule_format_msg = 'msg: "CyDefSIG %s, Event '.$event['Event']['id'].', '.$event['Event']['risk'].'"'; $rule_format_reference = 'reference:url,'.Configure::read('CyDefSIG.baseurl').'/events/view/'.$event['Event']['id']; $rule_format = 'alert %s %s %s %s %s %s ('.$rule_format_msg.'; %s %s classtype:'.$classtype.'; sid:%d; rev:%d; '.$rule_format_reference.';) '; - + $sid = $user['User']['nids_sid']+($event['Event']['id']*100); // LATER this will cause issues with events containing more than 99 attributes //debug($event); foreach ($event['Signature'] as $signature) { if (0 == $signature['to_ids']) continue; // attribute is not to be exported to IDS. // LATER filter out to_ids=0 in the query - + $sid++; switch ($signature['type']) { // LATER test all the snort signatures @@ -714,7 +702,7 @@ class EventsController extends AppController { break; case 'snort': $tmp_rule = $signature['value']; - + // rebuild the rule by overwriting the different keywords using preg_replace() // sid - '/sid\s*:\s*[0-9]+\s*;/' // rev - '/rev\s*:\s*[0-9]+\s*;/' @@ -733,7 +721,7 @@ class EventsController extends AppController { if (null == $tmp_rule ) break; // don't output the rule on error with the regex $tmp_rule = preg_replace('/reference\s*:\s*.+;/', $rule_format_reference.';', $tmp_rule, -1, $replace_count['reference']); if (null == $tmp_rule ) break; // don't output the rule on error with the regex - + // some values were not replaced, so we need to add them ourselves, and insert them in the rule $extra_for_rule=""; if (0 == $replace_count['sid']) { @@ -748,15 +736,15 @@ class EventsController extends AppController { $extra_for_rule .= $rule_format_reference.';'; } $tmp_rule = preg_replace('/;\s*\)/', '; '.$extra_for_rule.')', $tmp_rule); - + // finally the rule is cleaned up and can be outputed $rules[] = $tmp_rule; - + // TODO test using lots of snort rules. default: break; } - + } } @@ -768,10 +756,10 @@ class EventsController extends AppController { print "#\n"; $this->set('rules', $rules); - + } - - + + public function text($key, $type="") { // check if the key is valid -> search for users based on key $this->loadModel('User'); @@ -780,10 +768,10 @@ class EventsController extends AppController { if (empty($user)) { throw new UnauthorizedException('Incorrect authentication key'); } - + $this->header('Content-Type: text/plain'); // set the content type $this->layout = 'text/default'; - + $this->loadModel('Signature'); $params = array( 'conditions' => array('Signature.type' => $type), //array of conditions @@ -793,11 +781,11 @@ class EventsController extends AppController { 'group' => array('Signature.value'), //fields to GROUP BY ); $signatures = $this->Signature->find('all', $params); - + $this->set('signatures', $signatures); } - - + + /** * // LATER move _dnsNameToRawFormat($name) function to a better place * Converts a DNS name to a raw format usable in NIDS like Snort. @@ -823,7 +811,7 @@ class EventsController extends AppController { // and append |00| to terminate the name return $rawName; } - - - + + + } diff --git a/app/Controller/SignaturesController.php b/app/Controller/SignaturesController.php index 04d492324..6d05139a0 100644 --- a/app/Controller/SignaturesController.php +++ b/app/Controller/SignaturesController.php @@ -11,18 +11,18 @@ App::uses('File', 'Utility'); class SignaturesController extends AppController { public $components = array('Security'); - + function beforeFilter() { - // permit reuse of CSRF tokens on the search page. + // permit reuse of CSRF tokens on the search page. if ('search' == $this->request->params['action']) { $this->Security->csrfUseOnce = false; } - + // These variables are required for every view $this->set('me', $this->Auth->user()); $this->set('isAdmin', $this->_isAdmin()); } - + public function isAuthorized($user) { // Admins can access everything @@ -43,7 +43,7 @@ class SignaturesController extends AppController { // the other pages are allowed by logged in users return true; } - + /** * index method * @@ -62,13 +62,7 @@ class SignaturesController extends AppController { public function add($event_id = null) { if ($this->request->is('post')) { $this->loadModel('Event'); -// Replaced by isAuthorized -// // only own signatures -// $this->Event->recursive = 0; -// $event = $this->Event->findById($this->request->data['Signature']['event_id']); -// if (!$this->_isAdmin() && $this->Auth->user('org') != $event['Event']['org']) { -// throw new UnauthorizedException('You can only add signatures for your own organisation.'); -// } + // only own signatures verified by isAuthorized // Give error if someone tried to submit a signature with attachment or malware-sample type. // FIXME this is bad ... it should rather by a messagebox or should be filtered out on the view level @@ -77,26 +71,26 @@ class SignaturesController extends AppController { $this->Session->setFlash(__('Attribute has not been added: attachments are added by "Add attachment" button', true), 'default', array(), 'error'); $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id'])); } - + // remove the alerted flag from the event $this->Event->id = $this->request->data['Signature']['event_id']; $this->Event->saveField('alerted', 0); - + // // multiple signatures in batch import // if ($this->request->data['Signature']['batch_import'] == 1) { // make array from value field $signatures = explode("\n", $this->request->data['Signature']['value']); - + $fails = ""; // will be used to keep a list of the lines that failed or succeeded $successes = ""; foreach ($signatures as $key => $signature) { $signature = trim($signature); if (strlen($signature) == 0 ) continue; // don't do anything for empty lines - + $this->Signature->create(); $this->request->data['Signature']['value'] = $signature; // set the value as the content of the single line $this->request->data['Signature']['uuid'] = String::uuid(); @@ -105,7 +99,7 @@ class SignaturesController extends AppController { } else { $fails .= " ".($key+1); } - + } // we added all the signatures, if ($fails) { @@ -116,11 +110,11 @@ class SignaturesController extends AppController { // list the ones that succeeded $this->Session->setFlash(__('The lines'.$successes.' have been saved', true)); } - + $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id'])); - + } - + else { // // single signature @@ -128,7 +122,7 @@ class SignaturesController extends AppController { // create the signature $this->Signature->create(); $this->request->data['Signature']['uuid'] = String::uuid(); - + if ($this->Signature->save($this->request->data)) { // inform the user and redirect $this->Session->setFlash(__('The attribute has been saved')); @@ -141,7 +135,7 @@ class SignaturesController extends AppController { // set the event_id in the form $this->request->data['Signature']['event_id'] = $event_id; } - + // combobox for types $types = $this->Signature->validate['type']['rule'][1]; $types = $this->_arrayToValuesIndexArray($types); @@ -151,14 +145,14 @@ class SignaturesController extends AppController { $categories = $this->_arrayToValuesIndexArray($categories); $this->set('categories',compact('categories')); } - - + + public function download($id = null) { $this->Signature->id = $id; if (!$this->Signature->exists()) { throw new NotFoundException(__('Invalid signature')); } - + $this->Signature->read(); $file = new File(APP.DS."files".DS.$this->Signature->data['Signature']['event_id'].DS.$this->Signature->data['Signature']['id']); $filename = ''; @@ -170,18 +164,18 @@ class SignaturesController extends AppController { } else { throw new NotFoundException(__('Signature not an attachment or malware-sample')); } - + $file_ext = explode(".", $filename); $this->viewClass = 'Media'; $params = array( 'id' => $file->path, - 'name' => $filename, + 'name' => $filename, 'download' => true, 'path' => DS ); $this->set($params); } - + /** * add_attachment method * @@ -190,16 +184,11 @@ class SignaturesController extends AppController { public function add_attachment($event_id = null) { if ($this->request->is('post')) { $this->loadModel('Event'); -// // Replaced by isAuthorized -// // // only own signatures -// // $this->Event->recursive = 0; -// // $event = $this->Event->findById($this->request->data['Signature']['event_id']); -// // if (!$this->_isAdmin() && $this->Auth->user('org') != $event['Event']['org']) { -// // throw new UnauthorizedException('You can only add signatures for your own organisation.'); -// // } + // only own signatures verified by isAuthorized // Check if there were problems with the file upload - $filename = Sanitize::HTML($this->request->data['Signature']['value']['name']); + // only keep the last part of the filename, this should prevent directory attacks + $filename = basename($this->request->data['Signature']['value']['name']); $tmpfile = new File($this->request->data['Signature']['value']['tmp_name']); if ((isset($this->request->data['Signature']['value']['error']) && $this->request->data['Signature']['value']['error'] == 0) || (!empty( $this->request->data['Signature']['value']['tmp_name']) && $this->request->data['Signature']['value']['tmp_name'] != 'none') @@ -210,11 +199,11 @@ class SignaturesController extends AppController { $this->Session->setFlash(__('There was a problem to upload the file.', true), 'default', array(), 'error'); $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id'])); } - + // remove the alerted flag from the event $this->Event->id = $this->request->data['Signature']['event_id']; $this->Event->saveField('alerted', 0); - + // save the file-info in the database $this->Signature->create(); if($this->request->data['Signature']['malware']) { @@ -228,14 +217,14 @@ class SignaturesController extends AppController { $this->request->data['Signature']['uuid'] = String::uuid(); $this->request->data['Signature']['to_ids'] = 0; // LATER permit user to send this to IDS $this->request->data['Signature']['batch_import'] = 0; - + if ($this->Signature->save($this->request->data)) { // signature saved correctly in the db } else { $this->Session->setFlash(__('The attribute could not be saved. Did you already upload this file?')); $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id'])); } - + // no errors in file upload, entry already in db, now move the file where needed and zip it if required. // no sanitization is required on the filename, path or type as we save // create directory structure @@ -245,8 +234,8 @@ class SignaturesController extends AppController { $destpath = $root_dir.DS.$this->Signature->id; // id of the new signature in the database $file = new File ($destpath); $zipfile = new File ($destpath.'.zip'); - $file_in_zip = new File($root_dir.DS.$filename); // FIXME do sanitization of the filename - + $file_in_zip = new File($root_dir.DS.$filename); // FIXME do sanitization of the filename + if($file->exists() || $zipfile->exists() || $file_in_zip->exists()) { // this should never happen as the signature id should be unique $this->Session->setFlash(__('Attachment with this name already exist in this event.', true), 'default', array(), 'error'); @@ -265,10 +254,10 @@ class SignaturesController extends AppController { if($this->request->data['Signature']['malware']) { // TODO check if CakePHP has no easy/safe wrapper to execute commands $exec_retval = ''; $exec_output = array(); - rename($file->path, $file_in_zip->path); // FIXME prevent any attacks + rename($file->path, $file_in_zip->path); // TODO check if no workaround exists for the current filtering mechanisms exec("zip -j -P infected ".$zipfile->path.' "'.addslashes($file_in_zip->path).'"', $exec_output, $exec_retval); - if($exec_retval != 0) { // not EXIT_SUCCESS - $this->Session->setFlash(__('Problem with zipping the attachment. Please report to administrator. ', true), 'default', array(), 'error'); + if($exec_retval != 0) { // not EXIT_SUCCESS + $this->Session->setFlash(__('Problem with zipping the attachment. Please report to administrator. '.$exec_output, true), 'default', array(), 'error'); // remove the entry from the database $this->Signature->delete(); $file_in_zip->delete(); @@ -278,16 +267,16 @@ class SignaturesController extends AppController { $file_in_zip->delete(); // delete the original not-zipped-file rename($zipfile->path, $file->path); // rename the .zip to .nothing } - + // everything is done, now redirect to event view $this->Session->setFlash(__('The attachment has been uploaded')); $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Signature']['event_id'])); - + } else { // set the event_id in the form $this->request->data['Signature']['event_id'] = $event_id; } - + // combobos for categories $categories = $this->Signature->validate['category']['rule'][1]; $categories = $this->_arrayToValuesIndexArray($categories); @@ -305,12 +294,7 @@ class SignaturesController extends AppController { if (!$this->Signature->exists()) { throw new NotFoundException(__('Invalid signature')); } -// Replaced by isAuthorized -// // only own signatures -// $this->Signature->read(); -// if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Signature->data['Event']['org']) { -// throw new UnauthorizedException('You can only edit signatures from your own organisation.'); -// } + // only own signatures verified by isAuthorized $this->Signature->read(); $event_id = $this->Signature->data['Signature']['event_id']; @@ -323,15 +307,13 @@ class SignaturesController extends AppController { } else { $this->set('attachment', false); } - - debug($this->Signature->data); + if ($this->request->is('post') || $this->request->is('put')) { - - if ($this->Signature->save($this->request->data)) { - // TODO should I check here if it's possible to change the event_id or org ? + // say what fields are to be updated + $fieldList=array('category', 'type', 'value', 'to_ids'); + if ($this->Signature->save($this->request->data, true, $fieldList)) { $this->Session->setFlash(__('The attribute has been saved')); - - debug($this->request->data); + $this->redirect(array('controller' => 'events', 'action' => 'view', $event_id)); } else { $this->Session->setFlash(__('The attribute could not be saved. Please, try again.')); @@ -365,60 +347,55 @@ class SignaturesController extends AppController { if (!$this->Signature->exists()) { throw new NotFoundException(__('Invalid attribute')); } -// Replaced by isAuthorized -// // only own signatures -// $this->Signature->read(); -// if (!$this->_isAdmin() && $this->Auth->user('org') != $this->Signature->data['Event']['org']) { -// throw new UnauthorizedException('You can only delete signatures from your own organisation.'); -// } - + // only own signatures verified by isAuthorized + // attachment will be deleted with the beforeDelete() function in the Model if ($this->Signature->delete()) { $this->Session->setFlash(__('Attribute deleted')); } else { $this->Session->setFlash(__('Attribute was not deleted')); } - + $this->redirect($this->referer()); } - - - + + + public function search() { if ($this->request->is('post')) { $keyword = $this->request->data['Signature']['keyword']; $type = $this->request->data['Signature']['type']; $category = $this->request->data['Signature']['category']; - + // search the db $conditions = array(); if($keyword) { $conditions['Signature.value LIKE'] = '%'.$keyword.'%'; - } + } if($type != 'ALL') { $conditions['Signature.type ='] = $type; - } + } if($category != 'ALL') { $conditions['Signature.category ='] = $category; - } + } $this->Signature->recursive = 0; $this->paginate = array( 'conditions' => $conditions ); $this->set('signatures', $this->paginate()); - + // set the same view as the index page $this->render('index'); } else { // no search keyword is given, show the search form - + // adding filtering by category and type // combobox for types $types = array('ALL'); $types = array_merge($types, $this->Signature->validate['type']['rule'][1]); $types = $this->_arrayToValuesIndexArray($types); $this->set('types',compact('types')); - + // combobox for categories $categories = array('ALL'); $categories = array_merge($categories, $this->Signature->validate['category']['rule'][1]); diff --git a/app/Model/Signature.php b/app/Model/Signature.php index bd0e77a23..92215f79a 100644 --- a/app/Model/Signature.php +++ b/app/Model/Signature.php @@ -14,7 +14,7 @@ class Signature extends AppModel { * @var string */ public $displayField = 'value'; - + var $order = array("Signature.event_id" => "DESC", "Signature.type" => "ASC"); /** * Validation rules @@ -60,7 +60,7 @@ class Signature extends AppModel { 'required' => true, //'last' => false, // Stop validation after this rule //'on' => 'create', // Limit validation to 'create' or 'update' operations - + ), 'category' => array( 'rule' => array('inList', array('Payload delivery', @@ -153,17 +153,17 @@ class Signature extends AppModel { 'order' => '' ) ); - - + + function beforeSave() { // increment the revision number if (empty($this->data['Signature']['revision'])) $this->data['Signature']['revision'] = 0; $this->data['Signature']['revision'] = 1 + $this->data['Signature']['revision'] ; - + // always return true after a beforeSave() return true; } - + function beforeDelete() { // delete attachments from the disk if('attachment' == $this->data['Signature']['type'] || @@ -179,38 +179,56 @@ class Signature extends AppModel { } } } - + + function beforeValidate() { + // remove leading and trailing blanks + $this->data['Signature']['value'] = trim($this->data['Signature']['value']); + + switch($this->data['Signature']['type']) { + // lowercase these things + case 'md5': + case 'sha1': + $this->data['Signature']['value'] = strtolower($this->data['Signature']['value']); + break; + } + + // always return true, otherwise the object cannot be saved + return true; + } + function validateSignatureValue ($fields) { $value = $fields['value']; $event_id = $this->data['Signature']['event_id']; $type = $this->data['Signature']['type']; $to_ids = $this->data['Signature']['to_ids']; $category = $this->data['Signature']['category']; - + // check if the signature already exists in the same event + $conditions = array('Signature.event_id' => $event_id, + 'Signature.type' => $type, + 'Signature.category' => $category, + 'Signature.value' => $value + ); + if (isset($this->data['Signature']['id'])) + $conditions['Signature.id !='] = $this->data['Signature']['id']; + $params = array('recursive' => 0, - 'conditions' => array('Signature.event_id' => $event_id, - 'Signature.type' => $type, - 'Signature.to_ids' => $to_ids, - 'Signature.category' => $category, - 'Signature.value' => $value), + 'conditions' => $conditions, ); if (0 != $this->find('count', $params) ) return 'Attribute already exists for this event.'; - - + // check data validation switch($this->data['Signature']['type']) { - // FIXME lowercase hashes case 'md5': if (preg_match("#^[0-9a-f]{32}$#", $value)) return true; - return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.'; + return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.'; break; case 'sha1': if (preg_match("#^[0-9a-f]{40}$#", $value)) return true; - return 'Checksum has invalid lenght or format. Please double check the value or select "other" for a type.'; + return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.'; break; case 'filename': // no newline @@ -220,7 +238,8 @@ class Signature extends AppModel { case 'filename|md5': // no newline if (!preg_match("#^.*|[0-9a-f]{32}$#", $value)) - return true; + return true; + return 'Checksum has invalid length or format. Please double check the value or select "other" for a type.'; break; case 'ip-src': $parts = explode("/", $value); @@ -307,19 +326,19 @@ class Signature extends AppModel { return true; break; } - + // default action is to return false return true; - + } - - + + public function isOwnedByOrg($signatureid, $org) { $this->id = $signatureid; $this->read(); return $this->data['Event']['org'] === $org; } - + function getRelatedSignatures($signature) { // LATER there should be a list of types/categories included here as some are not eligible (AV detection category // or "other" type could be excluded) @@ -329,12 +348,12 @@ class Signature extends AppModel { 'Signature.type =' => $signature['type'], ); // $fields = array('Event.*'); $fields = array('Signature.*'); - + $similar_events = $this->find('all',array('conditions' => $conditions, 'fields' => $fields, 'order' => 'Signature.event_id DESC', ) ); return $similar_events; } - + }