From 94398b81920639afc5a40f37d830a0adf23607de Mon Sep 17 00:00:00 2001 From: Iglocska Date: Fri, 7 Aug 2015 16:10:40 +0200 Subject: [PATCH] Download all samples for an event ID via the API - as explained on the automation page - also, better error handling - all API calls that fail during authentication will now return a JSON/XML error message instead of redirecting to the login page --- VERSION.json | 2 +- app/Controller/AttributesController.php | 106 ++++++++++++++---------- app/Controller/EventsController.php | 22 ++++- app/View/Events/automation.ctp | 8 +- 4 files changed, 89 insertions(+), 49 deletions(-) diff --git a/VERSION.json b/VERSION.json index 8e6aedf3e..97e1072e5 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1 +1 @@ -{"major":2, "minor":3, "hotfix":105} \ No newline at end of file +{"major":2, "minor":3, "hotfix":106} \ No newline at end of file diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index a1951a82b..5001ef365 100755 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -2072,7 +2072,7 @@ class AttributesController extends AppController { } // download a sample by passing along an md5 - public function downloadSample($hash=false, $allSamples = false) { + public function downloadSample($hash=false, $allSamples=false, $eventID=false) { if (!$this->userRole['perm_auth']) throw new MethodNotAllowedException('This functionality requires API key access.'); //if (!$this->request->is('post')) throw new MethodNotAllowedException('Please POST the samples as described on the automation page.'); $isJson = false; @@ -2086,50 +2086,68 @@ class AttributesController extends AppController { throw new BadRequestException('This action is for the API only. Please refer to the automation page for information on how to use it.'); } if (!$hash && isset($data['request']['hash'])) $hash = $data['request']['hash']; - if (!$allSamples && isset($data['request']['hash'])) $allSamples = $data['request']['allSamples']; + if (!$allSamples && isset($data['request']['allSamples'])) $allSamples = $data['request']['allSamples']; + if (!$eventID && isset($data['request']['eventID'])) $eventID = $data['request']['eventID']; + if (!$eventID && !$hash) throw new MethodNotAllowedException('No hash or event ID received. You need to set at least one of the two.'); + if (!$hash) $allSamples = true; - $validTypes = $this->Attribute->resolveHashType($hash); + + $simpleFalse = array('hash', 'allSamples', 'eventID'); + foreach ($simpleFalse as $sF) { + if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false; + } + + // valid combinations of settings are: + // hash + // eventID + all samples + // hash + eventID + // hash + eventID + all samples + + if ($hash) $validTypes = $this->Attribute->resolveHashType($hash); $types = array(); - if ($allSamples) { - if (empty($validTypes)) { - $error = 'Invalid hash format (valid options are ' . implode(', ', array_keys($this->Attribute->hashTypes)) . ')'; - } - else { - foreach ($validTypes as $t) { - if ($t == 'md5') $types = array_merge($types, array('malware-sample', 'filename|md5', 'md5')); - else $types = array_merge($types, array('filename|' . $t, $t)); + if ($hash && $allSamples) { + if ($hash) { + debug($hash); + if (empty($validTypes)) { + $error = 'Invalid hash format (valid options are ' . implode(', ', array_keys($this->Attribute->hashTypes)) . ')'; + } + else { + foreach ($validTypes as $t) { + if ($t == 'md5') $types = array_merge($types, array('malware-sample', 'filename|md5', 'md5')); + else $types = array_merge($types, array('filename|' . $t, $t)); + } + } + if (empty($error)) { + $event_ids = $this->Attribute->find('list', array( + 'recursive' => -1, + 'contain' => array('Event'), + 'fields' => array('Event.id'), + 'conditions' => array( + 'OR' => array( + 'AND' => array( + 'LOWER(Attribute.value1) LIKE' => strtolower($hash), + 'Attribute.value2' => '', + ), + 'LOWER(Attribute.value2) LIKE' => strtolower($hash) + ) + ), + )); + $searchConditions = array( + 'AND' => array('Event.id' => array_values($event_ids)) + ); + if (empty($event_ids)) $error = 'No hits with the given parameters.'; + } + } else { + if (!in_array('md5', $validTypes)) $error = 'Only MD5 hashes can be used to fetch malware samples at this point in time.'; + if (empty($error)) { + $types = array('malware-sample', 'filename|md5'); + $searchConditions = array('AND' => array('LOWER(Attribute.value2) LIKE' => strtolower($hash))); } } - if (empty($error)) { - $event_ids = $this->Attribute->find('list', array( - 'recursive' => -1, - 'contain' => array('Event'), - 'fields' => array('Event.id'), - 'conditions' => array( - 'OR' => array( - 'AND' => array( - 'LOWER(Attribute.value1) LIKE' => strtolower($hash), - 'Attribute.value2' => '', - ), - 'LOWER(Attribute.value2) LIKE' => strtolower($hash) - ) - ), - )); - $searchConditions = array( - 'Event.id' => array_values($event_ids) - ); - if (empty($event_ids)) $error = 'No hits on the passed hash.'; - } - } else { - if (!in_array('md5', $validTypes)) $error = 'Only MD5 hashes can be used to fetch malware samples at this point in time.'; - if (empty($error)) { - $types = array('malware-sample', 'filename|md5'); - $searchConditions = array( - 'LOWER(Attribute.value2) LIKE' => strtolower($hash) - ); - } } + if (!empty($eventID)) $searchConditions['AND'][] = array('Event.id' => $eventID); + if (empty($error)) { $distributionConditions = array(); if (!$this->_isSiteAdmin()) { @@ -2155,7 +2173,7 @@ class AttributesController extends AppController { $distributionConditions, array('Attribute.type' => 'malware-sample') )))); - if (empty($attributes)) $error = 'No hits on the passed hash.'; + if (empty($attributes)) $error = 'No hits with the given parameters.'; $results = array(); foreach ($attributes as $attribute) { @@ -2175,15 +2193,15 @@ class AttributesController extends AppController { } } if ($error) { - $this->set('error', $error); - $this->set('_serialize', array('error')); + $this->set('message', $error); + $this->set('_serialize', array('message')); } else { $this->set('result', $results); $this->set('_serialize', array('result')); } } else { - $this->set('error', $error); - $this->set('_serialize', array('error')); + $this->set('message', $error); + $this->set('_serialize', array('message')); } } diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index c5a5229a8..548618e1f 100755 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -3328,6 +3328,8 @@ class EventsController extends AppController { } if (!isset($data['to_ids']) || !in_array($data['to_ids'], array('0', '1', 0, 1))) $data['to_ids'] = 1; + $successCount = 0; + $errors = array(); foreach ($data['files'] as $file) { $temp = $this->Event->Attribute->handleMaliciousBase64($data['event_id'], $file['filename'], $file['data'], array_keys($hashes)); if ($temp['success']) { @@ -3357,10 +3359,28 @@ class EventsController extends AppController { 'title' => 'Error: Failed to create attribute using the upload sample functionality', 'change' => 'There was an issue creating an attribute (' . $typeName . ': ' . $file['filename'] . '|' . $file[$hash] . '). ' . 'The validation errors were: ' . json_encode($this->Event->Attribute->validationErrors), )); - } + if ($typeName == 'malware-sample') $errors[] = array('filename' => $file['filename'], 'hash' => $file[$hash], 'error' => $this->Event->Attribute->validationErrors); + } else if ($typeName == 'malware-sample') $successCount++; } + } else { + $errors[] = array('filename' => $file['filename'], 'hash' => $file['hash'], 'error' => 'Failed to encrypt and compress the file.'); } } + if (!empty($errors)) { + $this->set('errors', $errors); + if ($successCount > 0) { + $this->set('name', 'Partial success'); + $this->set('message', 'Successfuly saved ' . $successCount . ' sample(s), but some samples could not be saved.'); + } else { + $this->set('name', 'Failed'); + $this->set('message', 'Failed to save any of the supplied samples.'); + } + $this->set('_serialize', array('name', 'message', 'errors')); + } else { + $this->set('name', 'Success'); + $this->set('message', 'Success, saved all attributes.'); + $this->set('_serialize', array('name', 'message')); + } $this->view($data['event_id']); $this->render('view'); } diff --git a/app/View/Events/automation.ctp b/app/View/Events/automation.ctp index 6ee0bef33..bb4d52ac2 100644 --- a/app/View/Events/automation.ctp +++ b/app/View/Events/automation.ctp @@ -314,22 +314,24 @@ For example, to get all IDS signature attributes of type md5 and sha256, but not

Download malware sample by hash

You can also download samples by knowing its MD5 hash. Simply pass the hash along as a JSON/XML object or in the URL (with the URL having overruling the passed objects) to receive a JSON/XML object back with the zipped sample base64 encoded along with some contextual information.

You can also use this API to get all samples from events that contain the passed hash. For this functionality, just pass the "allSamples" flag along. Note that if you are getting all samples from matching events, you can use all supported hash types () for the lookup.

+

You can also get all the samples from an event with a given event ID, by passing along the eventID parameter. Make sure that either an event ID or a hash is passed along, otherwise an error message will be returned. Also, if no hash is set, the allSamples flag will get set automatically.

 
 

POST message payload (XML):

-7c12772809c1c0c3deda6103b10fdfa01"); ?> +7c12772809c1c0c3deda6103b10fdfa0113"); ?>

POST message payload (json):

-{"request": {"hash": "7c12772809c1c0c3deda6103b10fdfa0", "allSamples": 1}} +{"request": {"hash": "7c12772809c1c0c3deda6103b10fdfa0", "allSamples": 1, "eventID": 13}}

A quick description of all the parameters in the passed object:

hash: A hash in MD5 format. If allSamples is set, this can be any one of the following:
allSamples: If set, it will return all samples from events that have a match for the hash provided above.
+eventID: If set, it will only fetch data from the given event ID.

Upload malware samples using the "Upload Sample" API