A list of changes to the way attachments are uploaded, fixes #559, fixes #482

- new API for uploading malware samples
  - allows the upload of several files
  - can be used to populate a pre-existing event, or create a new event
  - expects a JSON or an XML object with the samples base64 encoded
- new way of storing malware samples
  - original filename not used any longer
  - samples are renamed to their md5 hashes
  - original filename preserved in a secondary txt file
- removed filename validation as it is no longer used for the command line execution
  - this allows unicode name files to be uploaded!
  - changed the UI attachment upload to reflect these changes
  - code more centralised and extendible
pull/542/merge
Iglocska 2015-08-04 02:00:21 +02:00
parent a21abb6b13
commit 7db6e9ac47
5 changed files with 356 additions and 226 deletions

View File

@ -1 +1 @@
{"major":2, "minor":3, "hotfix":102}
{"major":2, "minor":3, "hotfix":103}

View File

@ -111,17 +111,13 @@ class AttributesController extends AppController {
if ($this->request->is('ajax')) $this->autoRender = false;
$this->loadModel('Event');
$date = new DateTime();
// Give error if someone tried to submit a attribute with attachment or malware-sample type.
// TODO change behavior attachment options - this is bad ... it should rather by a messagebox or should be filtered out on the view level
if (isset($this->request->data['Attribute']['type']) && $this->Attribute->typeIsAttachment($this->request->data['Attribute']['type'])) {
$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['Attribute']['event_id']));
}
// remove the published flag from the event
$this->Event->recursive = -1;
if (isset($eventId)) $this->Event->read(null, $eventId);
else $this->Event->read(null, $this->request->data['Attribute']['event_id']);
if (isset($eventId)) {
$this->Event->read(null, $eventId);
$this->request->data['Attribute']['event_id'] = $eventId;
} else $this->Event->read(null, $this->request->data['Attribute']['event_id']);
if (!$this->_isSiteAdmin() && ($this->Event->data['Event']['orgc'] != $this->_checkOrg() || !$this->userRole['perm_modify'])) {
throw new UnauthorizedException('You do not have permission to do that.');
}
@ -211,13 +207,13 @@ class AttributesController extends AppController {
$this->request->data['Attribute']['timestamp'] = $date->getTimestamp();
}
}
if (isset($this->request->data['Attribute']['base64'])) $this->request->data['Attribute']['data'] = $this->request->data['Attribute']['base64'];
//
// single attribute
//
// create the attribute
$this->Attribute->create();
$savedId = $this->Attribute->getID();
if ($this->Attribute->save($this->request->data)) {
if ($this->_isRest() || $this->response->type() === 'application/json') {
@ -321,17 +317,15 @@ class AttributesController extends AppController {
$this->response->file($path . $file, array('download' => true, 'name' => $filename . '.' . $fileExt));
}
/**
* add_attachment method
*
* @return void
* @throws InternalErrorException
*/
/**
* add_attachment method
*
* @return void
* @throws InternalErrorException
*/
public function add_attachment($eventId = null) {
$sha256 = null;
$sha1 = null;
//$ssdeep = null;
if ($this->request->is('post')) {
$hashes = array('md5' => 'malware-sample', 'sha1' => 'filename|sha1', 'sha256' => 'filename|sha256');
$this->loadModel('Event');
$this->Event->id = $this->request->data['Attribute']['event_id'];
$this->Event->recursive = -1;
@ -344,7 +338,7 @@ class AttributesController extends AppController {
$filename = basename($this->request->data['Attribute']['value']['name']);
$tmpfile = new File($this->request->data['Attribute']['value']['tmp_name']);
if ((isset($this->request->data['Attribute']['value']['error']) && $this->request->data['Attribute']['value']['error'] == 0) ||
(!empty( $this->request->data['Attribute']['value']['tmp_name']) && $this->request->data['Attribute']['value']['tmp_name'] != 'none')
(!empty( $this->request->data['Attribute']['value']['tmp_name']) && $this->request->data['Attribute']['value']['tmp_name'] != 'none')
) {
if (!is_uploaded_file($tmpfile->path))
throw new InternalErrorException('PHP says file was not uploaded. Are you attacking me?');
@ -353,110 +347,67 @@ class AttributesController extends AppController {
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
}
// save the file-info in the database
$this->Attribute->create();
$fails = array();
$completeFail = false;
if ($this->request->data['Attribute']['malware']) {
$this->request->data['Attribute']['type'] = "malware-sample";
// Validate filename
if (!preg_match('@^[\w\-. ]+$@', $filename)) throw new Exception ('Filename not allowed');
$this->request->data['Attribute']['value'] = $filename . '|' . hash_file('md5', $tmpfile->path); // TODO gives problems with bigger files
$sha256 = (hash_file('sha256', $tmpfile->path));
$sha1 = (hash_file('sha1', $tmpfile->path));
$this->request->data['Attribute']['to_ids'] = 1; // LATER let user choose to send this to IDS
$result = $this->Event->Attribute->handleMaliciousBase64($this->request->data['Attribute']['event_id'], $filename, base64_encode($tmpfile->read()), array_keys($hashes));
if (!$result['success']) {
$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['Attribute']['event_id']));
}
foreach ($hashes as $hash => $typeName) {
if (!$result[$hash]) continue;
$attribute = array(
'Attribute' => array(
'value' => $filename . '|' . $result[$hash],
'category' => $this->request->data['Attribute']['category'],
'type' => $typeName,
'event_id' => $this->request->data['Attribute']['event_id'],
'to_ids' => 1
)
);
if ($hash == 'md5') $attribute['Attribute']['data'] = $result['data'];
$this->Attribute->create();
$r = $this->Attribute->save($attribute);
if ($r == false) $fails[] = array($typeName);
if (count($fails) == count($hashes)) $completeFail = true;
}
} else {
$this->request->data['Attribute']['type'] = "attachment";
// Validate filename
if (!preg_match('@^[\w\-. ]+$@', $filename)) throw new Exception ('Filename not allowed');
$this->request->data['Attribute']['value'] = $filename;
$this->request->data['Attribute']['to_ids'] = 0;
$attribute = array(
'Attribute' => array(
'value' => $filename,
'category' => $this->request->data['Attribute']['category'],
'type' => 'attachment',
'event_id' => $this->request->data['Attribute']['event_id'],
'data' => base64_encode($tmpfile->read()),
'to_ids' => 0
)
);
$this->Attribute->create();
$r = $this->Attribute->save($attribute);
if ($r == false) {
$fails[] = array('attachment');
$completeFail = true;
}
}
$this->request->data['Attribute']['uuid'] = String::uuid();
$this->request->data['Attribute']['batch_import'] = 0;
if ($this->Attribute->save($this->request->data)) {
// attribute saved correctly in the db
if (!$completeFail) {
// attribute(s) saved correctly in the db
// remove the published flag from the event
if (empty($fails)) $this->Session->setFlash(__('The attachment has been uploaded'));
else $this->Session->setFlash(__('The attachment has been uploaded, but some of the attributes could not be created. The failed attributes are: ' . implode(', ', $fails)));
$this->Event->id = $this->request->data['Attribute']['event_id'];
$this->Event->saveField('published', 0);
} 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['Attribute']['event_id']));
$this->Session->setFlash(__('The attachment could not be saved, please contact your administrator.'));
}
// 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
if (PHP_OS == 'WINNT') {
$rootDir = APP . "files" . DS . $this->request->data['Attribute']['event_id'];
} else {
$rootDir = APP . DS . "files" . DS . $this->request->data['Attribute']['event_id'];
}
$dir = new Folder($rootDir, true);
// move the file to the correct location
$destpath = $rootDir . DS . $this->Attribute->id; // id of the new attribute in the database
$file = new File ($destpath);
$zipfile = new File ($destpath . '.zip');
$fileInZip = new File($rootDir . DS . $filename); // FIXME do sanitization of the filename
if ($file->exists() || $zipfile->exists() || $fileInZip->exists()) {
// this should never happen as the attribute id should be unique
$this->Session->setFlash(__('Attachment with this name already exist in this event.', true), 'default', array(), 'error');
// remove the entry from the database
$this->Attribute->delete();
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
}
if (!move_uploaded_file($tmpfile->path, $file->path)) {
$this->Session->setFlash(__('Problem with uploading attachment. Cannot move it to its final location.', true), 'default', array(), 'error');
// remove the entry from the database
$this->Attribute->delete();
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
}
// zip and password protect the malware files
if ($this->request->data['Attribute']['malware']) {
// TODO check if CakePHP has no easy/safe wrapper to execute commands
$execRetval = '';
$execOutput = array();
rename($file->path, $fileInZip->path); // TODO check if no workaround exists for the current filtering mechanisms
if (PHP_OS == 'WINNT') {
exec("zip -j -P infected " . $zipfile->path . ' "' . $fileInZip->path . '"', $execOutput, $execRetval);
} else {
exec("zip -j -P infected " . $zipfile->path . ' "' . addslashes($fileInZip->path) . '"', $execOutput, $execRetval);
}
if ($execRetval != 0) { // not EXIT_SUCCESS
$this->Session->setFlash(__('Problem with zipping the attachment. Please report to administrator. ' . $execOutput, true), 'default', array(), 'error');
// remove the entry from the database
$this->Attribute->delete();
$fileInZip->delete();
$file->delete();
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
};
$fileInZip->delete(); // delete the original not-zipped-file
rename($zipfile->path, $file->path); // rename the .zip to .nothing
}
if ($this->request->data['Attribute']['malware']) {
$temp = $this->request->data;
$this->Attribute->create();
$temp['Attribute']['type'] = 'filename|sha256';
$temp['Attribute']['value'] = $filename . '|' .$sha256;
$temp['Attribute']['uuid'] = String::uuid();
$this->Attribute->save($temp, array('fieldlist' => array('value', 'type', 'category', 'event_id', 'distribution', 'to_ids', 'comment')));
$this->Attribute->create();
$temp['Attribute']['type'] = 'filename|sha1';
$temp['Attribute']['value'] = $filename . '|' .$sha1;
$temp['Attribute']['uuid'] = String::uuid();
$this->Attribute->save($temp, array('fieldlist' => array('value', 'type', 'category', 'event_id', 'distribution', 'to_ids', 'comment')));
}
// 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['Attribute']['event_id']));
$this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id']));
} else {
// set the event_id in the form
$this->request->data['Attribute']['event_id'] = $eventId;
}
// combobos for categories
$categories = $this->Attribute->validate['category']['rule'][1];
// just get them with attachments..
@ -477,14 +428,14 @@ class AttributesController extends AppController {
};
$categories = $this->_arrayToValuesIndexArray($selectedCategories);
$this->set('categories',$categories);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('zippedDefinitions', $this->Attribute->zippedDefinitions);
$this->set('uploadDefinitions', $this->Attribute->uploadDefinitions);
// combobox for distribution
$this->loadModel('Event');
$this->set('distributionDescriptions', $this->Attribute->distributionDescriptions);
@ -493,7 +444,8 @@ class AttributesController extends AppController {
$this->set('currentDist', $events['Event']['distribution']);
$this->set('published', $events['Event']['published']);
}
/**
* Imports the CSV threatConnect file to multiple attributes
* @param int $id The id of the event

View File

@ -1856,7 +1856,6 @@ class EventsController extends AppController {
foreach ($simpleFalse as $sF) {
if (${$sF} === 'null' || ${$sF} == '0' || ${$sF} === false || strtolower(${$sF}) === 'false') ${$sF} = false;
}
if ($from) $from = $this->Event->dateFieldCheck($from);
if ($to) $to = $this->Event->dateFieldCheck($to);
if ($tags) $tags = str_replace(';', ':', $tags);
@ -1879,10 +1878,10 @@ class EventsController extends AppController {
$user['User']['siteAdmin'] = $this->_isSiteAdmin();
}
$this->loadModel('Attribute');
$rules = $this->Attribute->hids($user['User']['siteAdmin'], $user['User']['org'], $type, $tags, $from, $to, $last);
$this->set('rules', $rules);
}
// csv function
// Usage: csv($key, $eventid) - key can be a valid auth key or the string 'download'. Download requires the user to be logged in interactively and will generate a .csv file
// $eventid can be one of 3 options: left empty it will get all the visible to_ids attributes,
@ -2599,7 +2598,7 @@ class EventsController extends AppController {
$final = $this->IOCExport->buildAll($event, $isMyEvent, $isSiteAdmin);
$this->set('final', $final);
}
public function create_dummy_event() {
if (!$this->_isSiteAdmin()) throw new MethodNotAllowedException('You don\'t have the privileges to access this.');
$date = new DateTime();
@ -3228,4 +3227,108 @@ class EventsController extends AppController {
$this->set('id', $id);
$this->render('ajax/exportChoice');
}
// API for pushing samples to MISP
// Either send it to an existing event, or let MISP create a new one automatically
public function upload_sample($event_id = null) {
$hashes = array('md5' => 'malware-sample', 'sha1' => 'filename|sha1', 'sha256' => 'filename|sha256');
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;
if ($this->response->type() === 'application/json') {
$isJson = true;
$data = $this->request->input('json_decode', true);
} elseif ($this->response->type() === 'application/xml') {
$data = $this->request->data;
} else {
throw new BadRequestException('Please POST the samples as described on the automation page.');
}
if (isset($data['request'])) $data = $data['request'];
if (isset($data['files'])) {
foreach ($data['files'] as $k => $file) {
if (!isset($file['filename']) || !isset($file['data'])) unset ($data['files'][$k]);
else $data['files'][$k]['md5'] = md5(base64_decode($file['data']));
}
}
if (empty($data['files'])) throw new BadRequestException('No samples received, or samples not in the correct format. Please refer to the API documentation on the automation page.');
if (!isset($data['distribution']) || !in_array($data['distribution'], array('0', '1', '2', '3'))) {
$data['distribution'] = '0';
}
if (isset($event_id)) $data['event_id'] = $event_id;
// check if the user has permission to create attributes for an event, if the event ID has been passed
// If not, create an event
if (isset($data['event_id']) && !empty($data['event_id']) && is_numeric($data['event_id'])) {
$conditons = array();
if (!$this->_isSiteAdmin()) {
$conditions = array('Event.orgc' => $this->Auth->user('org'));
if (!$this->userRole['perm_modify_org']) $conditions[] = array('Event.user_id' => $this->Auth->user('id'));
}
$event = $this->Event->find('first', array(
'recursive' => -1,
'conditions' => $conditions,
'fields' => array('id'),
));
if (empty($event)) throw new MethodNotFoundException('Event not found.');
$this->Event->id = $data['event_id'];
$this->Event->saveField('published', 0);
} else {
$this->Event->create();
$result = $this->Event->save(
array(
'info' => (isset($data['info']) && !empty($data['info'])) ? $data['info'] : 'Samples uploaded on ' . date('Y-m-d'),
'analysis' => '0',
'threat_level_id' => '4',
'distribution' => $data['distribution'],
'date' => date('Y-m-d'),
'orgc' => $this->Auth->user('org'),
'org' => $this->Auth->user('org'),
'user_id' => $this->Auth->user('id')
)
);
if (!$result) throw new BadRequestException('The creation of a new event with the supplied information has failed.');
$data['event_id'] = $this->Event->id;
}
if (!isset($data['to_ids']) || !in_array($data['to_ids'], array('0', '1', 0, 1))) $data['to_ids'] = 1;
if (isset($data['category'])) {
$categoryDefinitions = $this->Event->Attribute->categoryDefinitions;
$types = array();
foreach ($categodyDefinitions as $k => $v) {
if (in_array('malware-sample', $v['types']) && !in_array($k, $types)) $types[] = $k;
}
if (!in_array($data['category'], $types)) $data['category'] = 'Payload installation';
} else {
$data['category'] = 'Payload installation';
}
foreach ($data['files'] as $file) {
$temp = $this->Event->Attribute->handleMaliciousBase64($data['event_id'], $file['filename'], $file['data'], array_keys($hashes));
if ($temp['success']) {
foreach ($hashes as $hash => $typeName) {
if ($temp[$hash] == false) continue;
$file[$hash] = $temp[$hash];
$file['data'] = $temp['data'];
$this->Event->Attribute->create();
$attribute = array(
'value' => $file['filename'] . '|' . $file[$hash],
'distribution' => $data['distribution'],
'category' => $data['category'],
'type' => $typeName,
'event_id' => $data['event_id'],
'to_ids' => $data['to_ids']
);
if ($hash == 'md5') $attribute['data'] = $file['data'];
$this->Event->Attribute->save($attribute);
}
}
}
$this->view($data['event_id']);
$this->render('view');
}
}

View File

@ -1493,112 +1493,163 @@ class Attribute extends AppModel {
}
private function __resolveElementAttribute($element, $value) {
$attributes = array();
$results = array();
$errors=null;
if (!empty($value)) {
if ($element['batch']) {
$values = explode("\n", $value);
foreach ($values as $v) {
$v = trim($v);
$attributes[] = $this->__createAttribute($element, $v);
}
} else {
$attributes[] = $this->__createAttribute($element, trim($value));
}
foreach ($attributes as $att) {
if (isset($att['multi'])) {
foreach ($att['multi'] as $a) {
$results[] = $a;
}
} else {
$results[] = $att;
}
}
} else {
if ($element['mandatory']) $errors = 'This field is mandatory.';
}
return array('attributes' => $results, 'errors' => $errors);
}
private function __resolveElementAttribute($element, $value) {
$attributes = array();
$results = array();
$errors=null;
if (!empty($value)) {
if ($element['batch']) {
$values = explode("\n", $value);
foreach ($values as $v) {
$v = trim($v);
$attributes[] = $this->__createAttribute($element, $v);
}
} else {
$attributes[] = $this->__createAttribute($element, trim($value));
}
foreach ($attributes as $att) {
if (isset($att['multi'])) {
foreach ($att['multi'] as $a) {
$results[] = $a;
}
} else {
$results[] = $att;
}
}
} else {
if ($element['mandatory']) $errors = 'This field is mandatory.';
}
return array('attributes' => $results, 'errors' => $errors);
}
private function __resolveElementFile($element, $files) {
$attributes = array();
$errors = null;
$results = array();
$count = count($files);
$element['complex'] = false;
if ($element['malware']) {
$element['type'] = 'malware-sample';
$element['to_ids'] = true;
} else {
$element['type'] = 'attachment';
$element['to_ids'] = false;
}
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 {
$element['type'] = 'malware-sample';
$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()) {
private function __resolveElementFile($element, $files) {
$attributes = array();
$errors = null;
$results = array();
$count = count($files);
$element['complex'] = false;
if ($element['malware']) {
$element['type'] = 'malware-sample';
$element['to_ids'] = true;
} else {
$element['type'] = 'attachment';
$element['to_ids'] = false;
}
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 {
$element['type'] = 'malware-sample';
$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, 'files' => $files);
}
} else {
$content = $tmp_file->read();
$attributes[count($attributes) - 1]['data'] = $file['tmp_name'];
}
}
}
return array('attributes' => $attributes, 'errors' => $errors, 'files' => $files);
}
private function __createAttribute($element, $value) {
$attribute = array(
'comment' => $element['name'],
'to_ids' => $element['to_ids'],
'category' => $element['category'],
'value' => $value,
);
if ($element['complex']) {
App::uses('ComplexTypeTool', 'Tools');
$complexTypeTool = new ComplexTypeTool();
$result = $complexTypeTool->checkComplexRouter($value, ucfirst($element['type']));
if (isset($result['multi'])) {
$temp = $attribute;
$attribute = array();
foreach($result['multi'] as $k => $r) {
$attribute['multi'][] = $temp;
$attribute['multi'][$k]['type'] = $r['type'];
$attribute['multi'][$k]['value'] = $r['value'];
}
} else if ($result != false) {
$attribute['type'] = $result['type'];
$attribute['value'] = $result['value'];
} else {
return false;
}
} else {
$attribute['type'] = $element['type'];
}
return $attribute;
}
private function __createAttribute($element, $value) {
$attribute = array(
'comment' => $element['name'],
'to_ids' => $element['to_ids'],
'category' => $element['category'],
'value' => $value,
);
if ($element['complex']) {
App::uses('ComplexTypeTool', 'Tools');
$complexTypeTool = new ComplexTypeTool();
$result = $complexTypeTool->checkComplexRouter($value, ucfirst($element['type']));
if (isset($result['multi'])) {
$temp = $attribute;
$attribute = array();
foreach($result['multi'] as $k => $r) {
$attribute['multi'][] = $temp;
$attribute['multi'][$k]['type'] = $r['type'];
$attribute['multi'][$k]['value'] = $r['value'];
}
} else if ($result != false) {
$attribute['type'] = $result['type'];
$attribute['value'] = $result['value'];
} else {
return false;
}
} else {
$attribute['type'] = $element['type'];
}
return $attribute;
}
// get and converts the contents of a file passed along as a base64 encoded string with the original filename into a zip archive
// The zip archive is then passed back as a base64 encoded string along with the md5 hash and a flag whether the transaction was successful
// The archive is password protected using the "infected" password
// The contents of the archive will be the actual sample, named <md5> and the original filename in a text file named <md5>.filename.txt
public function handleMaliciousBase64($event_id, $original_filename, $base64, $hash_types) {
if (!is_numeric($event_id)) throw new Exception('Something went wrong. Received a non numeric event ID while trying to create a zip archive of an uploaded malware sample.');
$dir = new Folder(APP . "files" . DS . $event_id, true);
$tmpFile = new File($dir->path . DS . $this->generateRandomFileName(), true, 0600);
$tmpFile->write(base64_decode($base64));
$hashes = array();
foreach ($hash_types as $hash) {
$hashes[$hash] = $this->__hashRouter($hash, $tmpFile->path);
}
$contentsFile = new File($dir->path . DS . $hashes['md5']);
rename($tmpFile->path, $contentsFile->path);
$fileNameFile = new File($dir->path . DS . $hashes['md5'] . '.filename.txt');
$fileNameFile->write($original_filename);
$fileNameFile->close();
$zipFile = new File($dir->path . DS . $hashes['md5'] . '.zip');
exec('zip -j -P infected "' . addslashes($zipFile->path) . '" "' . addslashes($contentsFile->path) . '" "' . addslashes($fileNameFile->path) . '"', $execOutput, $execRetval);
if ($execRetval != 0) $result = array('success' => false);
else $result = array_merge(array('data' => base64_encode($zipFile->read()), 'success' => true), $hashes);
$fileNameFile->delete();
$zipFile->delete();
$contentsFile->delete();
return $result;
}
private function __hashRouter($hashType, $file) {
$validHashes = array('md5', 'sha1', 'sha256');
if (!in_array($hashType, $validHashes)) return false;
switch ($hashType) {
case 'md5':
case 'sha1':
case 'sha256':
return hash_file($hashType, $file);
break;
}
}
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;
}
}

View File

@ -311,6 +311,30 @@ For example, to get all IDS signature attributes of type md5 and sha256, but not
echo Configure::read('MISP.baseurl').'/attributes/downloadAttachment/download/[Attribute_id]';
?>
</pre>
<h3>Upload malware samples using the "Upload Sample" API</h3>
<pre>
<?php
echo Configure::read('MISP.baseurl').'/events/upload_sample/[Event_id]';
?>
</pre>
<p>This API will allow you to populate an event that you have modify rights to with malware samples (and all related hashes). Alternatively, if you do not supply an event ID, it will create a new event for you. <br />
The files have to be base64 encoded and POSTed as explained below. All samples will be zipped and password protected (with the password being "infected"). The hashes of the original file will be captured as additional attributes.<br />
The event ID is optional. MISP will accept either a JSON or an XML object posted to the above URL.</p>
<p><b>The general structure of the expected objects is as follows:</b></p>
<code>{"request": {"files": [{"filename": filename1, "data": base64encodedfile1}, {"filename": filename2, "data": base64encodedfile2}], "optional_parameter1", "optional_parameter2", "optional_parameter3"}}</code>
<br /><br />
<p><b>JSON:</b></p>
<code>{"request":{"files": [{"filename": "test1.txt", "data": "dGVzdA=="}, {"filename": "test2.txt", "data": "dGVzdDI="}], "distribution": 1, "info" : "test", "event_id": 15}}</code>
<br /><br />
<p><b>XML:</b></p>
<code><?php echo h("<request><files><filename>test3.txt</filename><data>dGVzdA==</data></files><files><filename>test4.txt</filename><data>dGVzdDI=</data></files><info>test</info><distribution>1</distribution><event_id>15</event_id></request>");?></code>
<br /><br />
<p><b>The following optional parameters are expected:</b></p>
<p><b>event_id</b>: The Event's ID is optional. It can be either supplied via the URL or the POSTed object, but the URL has priority if both are provided. Not supplying an event ID will cause MISP to create a single new event for all of the POSTed malware samples. You can define the default settings for the event, otherwise a set of default settings will be used.<br />
<b>distribution</b>: The distribution setting used for the attributes and for the newly created event, if relevant.<br />
<b>to_ids</b>: You can flag all attributes created during the transaction to be marked as "to_ids" or not.<br />
<b>category</b>: The category that will be assigned to the uploaded samples. Valid options are: Payload delivery, Artifacts dropped, Payload Installation, External Analysis.<br />
</div>
<?php
echo $this->element('side_menu', array('menuList' => 'event-collection', 'menuItem' => 'automation'));