public function restSearch($key = 'download', $value = false, $type = false, $category = false, $org = false, $tags = false, $searchall = false, $from = false, $to = false, $last = false, $eventid = false) { if ($key != 'download') { $user = $this->checkAuthUser($key); } else { if (!$this->Auth->user()) { throw new UnauthorizedException('You are not authorized. Please send the Authorization header with your auth key along with an Accept header for application/xml.'); } $user = $this->checkAuthUser($this->Auth->user('authkey')); } if (!$user) { throw new UnauthorizedException('This authentication key is not authorized to be used for exports. Contact your administrator.'); } $value = str_replace('|', '/', $value); // request handler for POSTed queries. If the request is a post, the parameters (apart from the key) will be ignored and replaced by the terms defined in the posted json or xml object. // The correct format for both is a "request" root element, as shown by the examples below: // For Json: {"request":{"value": "7.7.7.7&&1.1.1.1","type":"ip-src"}} // For XML: <request><value>7.7.7.7&&1.1.1.1</value><type>ip-src</type></request> // the response type is used to determine the parsing method (xml/json) if ($this->request->is('post')) { if ($this->response->type() === 'application/json') { $data = $this->request->input('json_decode', true); } elseif ($this->response->type() === 'application/xml') { $data = $this->request->data; } else { throw new BadRequestException('Either specify the search terms in the url, or POST a json array / xml (with the root element being "request" and specify the correct headers based on content type.'); } $paramArray = array('value', 'type', 'category', 'org', 'tags', 'searchall', 'from', 'to', 'last', 'eventid'); foreach ($paramArray as $p) { if (isset($data['request'][$p])) { ${$p} = $data['request'][$p]; } else { ${$p} = null; } } } $simpleFalse = array('value', 'type', 'category', 'org', 'tags', 'searchall', 'from', 'to', 'last', 'eventid'); foreach ($simpleFalse as $sF) { if (!is_array(${$sF}) && (${$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); } if ($last) { $last = $this->Event->resolveTimeDelta($last); } if ($searchall === 'true') { $searchall = "1"; } $conditions['AND'] = array(); $subcondition = array(); $this->loadModel('Attribute'); // add the values as specified in the 2nd parameter to the conditions $values = explode('&&', $value); if (isset($searchall) && ($searchall == 1 || $searchall === true || $searchall == 'true')) { $eventIds = $this->__quickFilter($value); } else { $parameters = array('value', 'type', 'category', 'org', 'eventid'); foreach ($parameters as $k => $param) { if (isset(${$parameters[$k]})) { if (is_array(${$parameters[$k]})) { $elements = ${$parameters[$k]}; } else { $elements = explode('&&', ${$parameters[$k]}); } foreach ($elements as $v) { if (substr($v, 0, 1) == '!') { if ($parameters[$k] === 'value' && 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($v, 1))) { $cidrresults = $this->Cidr->CIDR(substr($v, 1)); foreach ($cidrresults as $result) { $subcondition['AND'][] = array('Attribute.value NOT LIKE' => $result); } } else { if ($parameters[$k] === 'org') { $subcondition['AND'][] = array('Event.' . $parameters[$k] . ' NOT LIKE' => '%' . substr($v, 1) . '%'); } elseif ($parameters[$k] === 'eventid') { $subcondition['AND'][] = array('Attribute.event_id !=' => substr($v, 1)); } else { $subcondition['AND'][] = array('Attribute.' . $parameters[$k] . ' NOT LIKE' => '%' . substr($v, 1) . '%'); } } } else { if ($parameters[$k] === 'value' && 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($v, 1))) { $cidrresults = $this->Cidr->CIDR($v); foreach ($cidrresults as $result) { $subcondition['OR'][] = array('Attribute.value LIKE' => $result); } } else { if ($parameters[$k] === 'org') { $subcondition['OR'][] = array('Event.' . $parameters[$k] . ' LIKE' => '%' . $v . '%'); } elseif ($parameters[$k] === 'eventid') { $subcondition['OR'][] = array('Attribute.event_id' => $v); } else { $subcondition['OR'][] = array('Attribute.' . $parameters[$k] . ' LIKE' => '%' . $v . '%'); } } } } array_push($conditions['AND'], $subcondition); $subcondition = array(); } } // If we are looking for an attribute, we want to retrieve some extra data about the event to be able to check for the permissions. if (!$user['User']['siteAdmin']) { $temp = array(); $temp['AND'] = array('Event.distribution >' => 0, 'Attribute.distribution >' => 0, Configure::read('MISP.unpublishedprivate') ? array('Event.published =' => 1) : array()); $subcondition['OR'][] = $temp; $subcondition['OR'][] = array('Event.org' => $user['User']['org']); array_push($conditions['AND'], $subcondition); } // If we sent any tags along, load the associated tag names for each attribute if ($tags) { $args = $this->Event->Attribute->dissectArgs($tags); $this->loadModel('Tag'); $tagArray = $this->Tag->fetchEventTagIds($args[0], $args[1]); $temp = array(); foreach ($tagArray[0] as $accepted) { $temp['OR'][] = array('Event.id' => $accepted); } $conditions['AND'][] = $temp; $temp = array(); foreach ($tagArray[1] as $rejected) { $temp['AND'][] = array('Event.id !=' => $rejected); } $conditions['AND'][] = $temp; } if ($from) { $conditions['AND'][] = array('Event.date >=' => $from); } if ($to) { $conditions['AND'][] = array('Event.date <=' => $to); } if ($last) { $conditions['AND'][] = array('Event.publish_timestamp >=' => $last); } $params = array('conditions' => $conditions, 'fields' => array('DISTINCT(Attribute.event_id)')); $attributes = $this->Attribute->find('all', $params); $eventIds = array(); foreach ($attributes as $attribute) { if (!in_array($attribute['Attribute']['event_id'], $eventIds)) { $eventIds[] = $attribute['Attribute']['event_id']; } } } if (!empty($eventIds)) { $this->loadModel('Whitelist'); if ((!isset($this->request->params['ext']) || $this->request->params['ext'] !== 'json') && $this->response->type() !== 'application/json') { App::uses('XMLConverterTool', 'Tools'); $converter = new XMLConverterTool(); $final = ""; $final .= '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<response>' . PHP_EOL; foreach ($eventIds as $currentEventId) { $result = $this->__fetchEvent($currentEventId, null, $user['User']['org'], true); $result = $this->Whitelist->removeWhitelistedFromArray($result, false); $final .= $converter->event2XML($result[0]) . PHP_EOL; } $final .= '</response>' . PHP_EOL; $final_filename = "misp.search.events.results.xml"; $this->response->body($final); $this->response->type('xml'); $this->response->download($final_filename); } else { App::uses('JSONConverterTool', 'Tools'); $converter = new JSONConverterTool(); $temp = array(); $final = '{"response":['; foreach ($eventIds as $k => $currentEventId) { $result = $this->__fetchEvent($currentEventId, null, $user['User']['org'], true); $final .= $converter->event2JSON($result[0]); if ($k < count($eventIds) - 1) { $final .= ','; } } $final .= ']}'; $final_filename = "misp.search.events.results.json"; $this->response->body($final); $this->response->type('json'); $this->response->download($final_filename); } } else { throw new NotFoundException('No matches.'); } return $this->response; }
/** * Uploads the event and the associated Attributes to another Server * TODO move this to a component * * @return bool true if success, false or error message if failed */ public function restfullEventToServer($event, $server, $urlPath, &$newLocation, &$newTextBody, $HttpSocket = null) { if ($event['Event']['distribution'] < 2) { // never upload private events return 403; //"Event is private and non exportable"; } $url = $server['Server']['url']; $authkey = $server['Server']['authkey']; if (null == $HttpSocket) { App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); $HttpSocket = $syncTool->setupHttpSocket($server); } $request = array('header' => array('Authorization' => $authkey, 'Accept' => 'application/xml', 'Content-Type' => 'application/xml')); $uri = isset($urlPath) ? $urlPath : $url . '/events'; // LATER try to do this using a separate EventsController and renderAs() function $xmlArray = array(); // rearrange things to be compatible with the Xml::fromArray() if (isset($event['Attribute'])) { $event['Event']['Attribute'] = $event['Attribute']; unset($event['Attribute']); } if (isset($event['ShadowAttribute'])) { unset($event['ShadowAttribute']); } // cleanup the array from things we do not want to expose //unset($event['Event']['org']); // remove value1 and value2 from the output if (isset($event['Event']['Attribute'])) { foreach ($event['Event']['Attribute'] as $key => &$attribute) { // do not keep attributes that are private, nor cluster if ($attribute['distribution'] < 2) { unset($event['Event']['Attribute'][$key]); continue; // stop processing this } // Distribution, correct Connected Community to Community in Attribute if ($attribute['distribution'] == 2) { $attribute['distribution'] = 1; } // remove value1 and value2 from the output unset($attribute['value1']); unset($attribute['value2']); // also add the encoded attachment if ($this->Attribute->typeIsAttachment($attribute['type'])) { $encodedFile = $this->Attribute->base64EncodeAttachment($attribute); $attribute['data'] = $encodedFile; } // Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push // Basically, if the attribute count differed between two instances, and the instance with the lower attribute // count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it // solves the issue and a new attribute is always created. unset($attribute['id']); } } else { return 403; } // If we ran out of attributes, or we never had any to begin with, we want to prevent the event from being pushed. // It should show up the same way as if the event was not exportable if (count($event['Event']['Attribute']) == 0) { return 403; } // Distribution, correct All to Community in Event if ($event['Event']['distribution'] == 2) { $event['Event']['distribution'] = 1; } // display the XML to the user $xmlArray['Event'][] = $event['Event']; App::uses('XMLConverterTool', 'Tools'); $converter = new XMLConverterTool(); $data = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . $converter->event2XML($event) . PHP_EOL; // LATER validate HTTPS SSL certificate $this->Dns = ClassRegistry::init('Dns'); if ($this->Dns->testipaddress(parse_url($uri, PHP_URL_HOST))) { // TODO NETWORK for now do not know how to catch the following.. // TODO NETWORK No route to host $response = $HttpSocket->post($uri, $data, $request); switch ($response->code) { case '200': // 200 (OK) + entity-action-result if ($response->isOk()) { $newTextBody = $response->body(); $newLocation = null; return true; //return isset($urlPath) ? $response->body() : true; } else { try { // parse the XML response and keep the reason why it failed $xmlArray = Xml::toArray(Xml::build($response->body)); } catch (XmlException $e) { return true; // TODO should be false } if (strpos($xmlArray['response']['name'], "Event already exists")) { // strpos, so i can piggyback some value if needed. return true; } else { return $xmlArray['response']['name']; } } break; case '302': // Found $newLocation = $response->headers['Location']; $newTextBody = $response->body(); return true; //return isset($urlPath) ? $response->body() : $response->headers['Location']; break; case '404': // Not Found $newLocation = $response->headers['Location']; $newTextBody = $response->body(); return 404; break; case '405': return 405; break; case '403': // Not authorised return 403; break; } } }