mirror of https://github.com/MISP/MISP
437 lines
16 KiB
PHP
Executable File
437 lines
16 KiB
PHP
Executable File
<?php
|
|
App::uses('AppModel', 'Model');
|
|
/**
|
|
* Event Model
|
|
*
|
|
* @property User $User
|
|
* @property Attribute $Attribute
|
|
*/
|
|
class Event extends AppModel {
|
|
/**
|
|
* Display field
|
|
*
|
|
* @var string
|
|
*/
|
|
public $displayField = 'id';
|
|
/**
|
|
* Description field
|
|
*
|
|
* @var array
|
|
*/
|
|
|
|
public $field_descriptions = array(
|
|
'risk' => array('desc' => 'Risk levels: *low* means mass-malware, *medium* means APT malware, *high* means sophisticated APT malware or 0-day attack', 'formdesc' => 'Risk levels:<br/>low: mass-malware<br/>medium: APT malware<br/>high: sophisticated APT malware or 0-day attack'),
|
|
'private' => array('desc' => 'This field tells if the event should be shared with other CyDefSIG servers'),
|
|
'classification' => array('desc' => 'Set the Traffic Light Protocol classification. <ol><li><em>TLP:AMBER</em>- Share only within the organization on a need-to-know basis</li><li><em>TLP:GREEN:NeedToKnow</em>- Share within your constituency on the need-to-know basis.</li><li><em>TLP:GREEN</em>- Share within your constituency.</li></ol>')
|
|
);
|
|
|
|
/**
|
|
* Validation rules
|
|
*
|
|
* @var array
|
|
*/
|
|
public $validate = array(
|
|
'org' => array(
|
|
'notempty' => array(
|
|
'rule' => array('notempty'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
//'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'date' => array(
|
|
'date' => array(
|
|
'rule' => array('date'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
'required' => true,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'risk' => array(
|
|
'rule' => array('inList', array('Undefined', 'Low','Medium','High')),
|
|
'message' => 'Options : Undefined, Low, Medium, High',
|
|
//'allowEmpty' => false,
|
|
'required' => true,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
'info' => array(
|
|
'notempty' => array(
|
|
'rule' => array('notempty'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
//'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'user_id' => array(
|
|
'numeric' => array(
|
|
'rule' => array('numeric'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
//'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'published' => array(
|
|
'boolean' => array(
|
|
'rule' => array('boolean'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
//'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'uuid' => array(
|
|
'uuid' => array(
|
|
'rule' => array('uuid'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
//'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
'private' => array(
|
|
'boolean' => array(
|
|
'rule' => array('boolean'),
|
|
//'message' => 'Your custom message here',
|
|
//'allowEmpty' => false,
|
|
'required' => false,
|
|
//'last' => false, // Stop validation after this rule
|
|
//'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
),
|
|
),
|
|
// 'classification' => array(
|
|
// 'rule' => array('inList', array('TLP:AMBER', 'TLP:GREEN:NeedToKnow', 'TLP:GREEN')),
|
|
// //'message' => 'Your custom message here',
|
|
// //'allowEmpty' => false,
|
|
// 'required' => true,
|
|
// //'last' => false, // Stop validation after this rule
|
|
// //'on' => 'create', // Limit validation to 'create' or 'update' operations
|
|
// ),
|
|
);
|
|
|
|
//The Associations below have been created with all possible keys, those that are not needed can be removed
|
|
|
|
/**
|
|
* belongsTo associations
|
|
*
|
|
* @var array
|
|
*/
|
|
public $belongsTo = array(
|
|
// 'Org' => array(
|
|
// 'className' => 'Org',
|
|
// 'foreignKey' => 'org',
|
|
// 'conditions' => '',
|
|
// 'fields' => '',
|
|
// 'order' => ''
|
|
// )
|
|
'User' => array(
|
|
'className' => 'User',
|
|
'foreignKey' => 'user_id',
|
|
'conditions' => '',
|
|
'fields' => '',
|
|
'order' => ''
|
|
)
|
|
);
|
|
|
|
/**
|
|
* hasMany associations
|
|
*
|
|
* @var array
|
|
*/
|
|
public $hasMany = array(
|
|
'Attribute' => array(
|
|
'className' => 'Attribute',
|
|
'foreignKey' => 'event_id',
|
|
'dependent' => true, // cascade deletes
|
|
'conditions' => '',
|
|
'fields' => '',
|
|
'order' => array('Attribute.category ASC', 'Attribute.type ASC'),
|
|
'limit' => '',
|
|
'offset' => '',
|
|
'exclusive' => '',
|
|
'finderQuery' => '',
|
|
'counterQuery' => ''
|
|
)
|
|
);
|
|
|
|
|
|
function beforeValidate() {
|
|
// generate UUID if it doesn't exist
|
|
if (empty($this->data['Event']['uuid']))
|
|
$this->data['Event']['uuid']= String::uuid();
|
|
}
|
|
|
|
public function isOwnedByOrg($eventid, $org) {
|
|
return $this->field('id', array('id' => $eventid, 'org' => $org)) === $eventid;
|
|
}
|
|
|
|
function getRelatedEvents() {
|
|
// FIXME rewrite this to use the getRelatedAttributes function from the Attributes Model.
|
|
// only this way the code will be consistent
|
|
|
|
// first get a list of related event_ids
|
|
// then do a single query to search for all the events with that id
|
|
$relatedEventIds = Array();
|
|
foreach ($this->data['Attribute'] as &$attribute ) {
|
|
if ($attribute['type'] == 'other')
|
|
continue; // sigs of type 'other' should not be matched against the others
|
|
$conditions = array('Attribute.value =' => $attribute['value'], 'Attribute.type =' => $attribute['type']);
|
|
$similar_attributes = $this->Attribute->find('all',array('conditions' => $conditions));
|
|
foreach ($similar_attributes as &$similar_attribute) {
|
|
if ($this->id == $similar_attribute['Attribute']['event_id'])
|
|
continue; // same as this event, not needed in the list
|
|
$relatedEventIds[] = $similar_attribute['Attribute']['event_id'];
|
|
}
|
|
}
|
|
$conditions = array("Event.id" => $relatedEventIds);
|
|
$relatedEvents= $this->find('all',
|
|
array('conditions' => $conditions,
|
|
'recursive' => 0,
|
|
'order' => 'Event.date DESC',
|
|
'fields' => 'Event.*'
|
|
)
|
|
);
|
|
return $relatedEvents;
|
|
}
|
|
|
|
|
|
/**
|
|
* Clean up an Event Array that was received by an XML request.
|
|
* The structure needs to be changed a little bit to be compatible with what CakePHP expects
|
|
*
|
|
* This function receives the reference of the variable, so no return is required as it directly
|
|
* modifies the original data.
|
|
*
|
|
* @param &$data The reference to the variable
|
|
*/
|
|
function cleanupEventArrayFromXML(&$data) {
|
|
// Workaround for different structure in XML/array than what CakePHP expects
|
|
if (is_array($data['Event']['Attribute'])) {
|
|
if (is_numeric(implode(array_keys($data['Event']['Attribute']), ''))) {
|
|
// normal array of multiple Attributes
|
|
$data['Attribute'] = $data['Event']['Attribute'];
|
|
} else {
|
|
// single attribute
|
|
$data['Attribute'][0] = $data['Event']['Attribute'];
|
|
}
|
|
}
|
|
unset($data['Event']['Attribute']);
|
|
|
|
|
|
return $data;
|
|
}
|
|
|
|
|
|
/**
|
|
* Uploads the event and the associated Attributes to another Server
|
|
* TODO move this to a component
|
|
*
|
|
* @return bool true if success, error message if failed
|
|
*/
|
|
function uploadEventToServer($event, $server, $HttpSocket=null) {
|
|
if (true ==$event['Event']['private']) // never upload private events
|
|
return "Event is private and non exportable";
|
|
|
|
$url = $server['Server']['url'];
|
|
$authkey = $server['Server']['authkey'];
|
|
if (null == $HttpSocket) {
|
|
App::uses('HttpSocket', 'Network/Http');
|
|
$HttpSocket = new HttpSocket();
|
|
}
|
|
$request = array(
|
|
'header' => array(
|
|
'Authorization' => $authkey,
|
|
'Accept' => 'application/xml',
|
|
'Content-Type' => 'application/xml',
|
|
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
|
|
)
|
|
);
|
|
$uri = $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()
|
|
$event['Event']['Attribute'] = $event['Attribute'];
|
|
unset($event['Attribute']);
|
|
|
|
// cleanup the array from things we do not want to expose
|
|
//unset($event['Event']['org']);
|
|
// remove value1 and value2 from the output
|
|
foreach($event['Event']['Attribute'] as $key => &$attribute) {
|
|
// do not keep attributes that are private
|
|
if ($attribute['private']) {
|
|
unset($event['Event']['Attribute'][$key]);
|
|
continue; // stop processing this
|
|
}
|
|
// remove value1 and value2 from the output
|
|
unset($attribute['value1']);
|
|
unset($attribute['value2']);
|
|
// also add the encoded attachment
|
|
if ($this->Attribute->typeIsAttachment($attribute['type'])) {
|
|
$encoded_file = $this->Attribute->base64EncodeAttachment($attribute);
|
|
$attribute['data'] = $encoded_file;
|
|
}
|
|
}
|
|
|
|
// add the 'Imported from' conform ServersController.php:177
|
|
// no need to remove lateron cause on pushing server Event is already saved.
|
|
$event['Event']['info'] .= "\n Published from ".Configure::read('CyDefSIG.baseurl');
|
|
if ($event['Event']['user_id'] != '0') $event['Event']['org'] = Configure::read('CyDefSIG.org'); // TODO
|
|
|
|
// display the XML to the user
|
|
$xmlArray['Event'][] = $event['Event'];
|
|
$xmlObject = Xml::fromArray($xmlArray, array('format' => 'tags'));
|
|
$eventsXml = $xmlObject->asXML();
|
|
// do a REST POST request with the server
|
|
$data = $eventsXml;
|
|
// 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);
|
|
if ($response->code == '200') { // 200 (OK) + entity-action-result
|
|
if ($response->isOk()) {
|
|
return true;
|
|
}
|
|
else {
|
|
try {
|
|
// parse the XML response and keep the reason why it failed
|
|
$xml_array = Xml::toArray(Xml::build($response->body));
|
|
} catch (XmlException $e) {
|
|
return true;
|
|
}
|
|
if (strpos($xml_array['response']['name'],"Event already exists")) { // strpos, so i can piggyback some value if needed.
|
|
return true;
|
|
} else {
|
|
return $xml_array['response']['name'];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the event and the associated Attributes from another Server
|
|
* TODO move this to a component
|
|
*
|
|
* @return bool true if success, error message if failed
|
|
*/
|
|
function deleteEventFromServer($uuid, $server, $HttpSocket=null) {
|
|
// TODO private and delete(?)
|
|
|
|
$url = $server['Server']['url'];
|
|
$authkey = $server['Server']['authkey'];
|
|
if (null == $HttpSocket) {
|
|
App::uses('HttpSocket', 'Network/Http');
|
|
$HttpSocket = new HttpSocket();
|
|
}
|
|
$request = array(
|
|
'header' => array(
|
|
'Authorization' => $authkey,
|
|
'Accept' => 'application/xml',
|
|
'Content-Type' => 'application/xml',
|
|
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
|
|
)
|
|
);
|
|
$uri = $url.'/events/0?uuid='.$uuid;
|
|
|
|
// 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->delete($uri, array(), $request);
|
|
// TODO REST, DELETE, some responce needed
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download a specific event from a Server
|
|
* TODO move this to a component
|
|
* @return array|NULL
|
|
*/
|
|
function downloadEventFromServer($event_id, $server, $HttpSocket=null) {
|
|
$url = $server['Server']['url'];
|
|
$authkey = $server['Server']['authkey'];
|
|
if (null == $HttpSocket) {
|
|
App::uses('HttpSocket', 'Network/Http');
|
|
$HttpSocket = new HttpSocket();
|
|
}
|
|
$request = array(
|
|
'header' => array(
|
|
'Authorization' => $authkey,
|
|
'Accept' => 'application/xml',
|
|
'Content-Type' => 'application/xml',
|
|
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
|
|
)
|
|
);
|
|
$uri = $url.'/events/'.$event_id;
|
|
// LATER validate HTTPS SSL certificate
|
|
$response = $HttpSocket->get($uri, $data='', $request);
|
|
if ($response->isOk()) {
|
|
$xml_array = Xml::toArray(Xml::build($response->body));
|
|
return $xml_array['response'];
|
|
}
|
|
else {
|
|
// TODO parse the XML response and keep the reason why it failed
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get an array of event_ids that are present on the remote server
|
|
* TODO move this to a component
|
|
* @return array of event_ids
|
|
*/
|
|
function getEventIdsFromServer($server, $HttpSocket=null) {
|
|
$url = $server['Server']['url'];
|
|
$authkey = $server['Server']['authkey'];
|
|
|
|
if (null == $HttpSocket) {
|
|
App::uses('HttpSocket', 'Network/Http');
|
|
$HttpSocket = new HttpSocket();
|
|
}
|
|
$request = array(
|
|
'header' => array(
|
|
'Authorization' => $authkey,
|
|
'Accept' => 'application/xml',
|
|
'Content-Type' => 'application/xml',
|
|
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
|
|
)
|
|
);
|
|
$uri = $url.'/events/index/sort:id/direction:desc/limit:999'; // LATER verify if events are missing because we only selected the last 999
|
|
$this->Dns = ClassRegistry::init('Dns');
|
|
if ($this->Dns->testipaddress(parse_url($uri, PHP_URL_HOST))) {
|
|
$response = $HttpSocket->get($uri, $data='', $request);
|
|
|
|
if ($response->isOk()) {
|
|
$xml = Xml::build($response->body);
|
|
$eventArray = Xml::toArray($xml);
|
|
$event_ids=array();
|
|
foreach ($eventArray['response']['Event'] as &$event) {
|
|
if (1 != $event['published']) continue; // do not keep non-published events
|
|
$event_ids[] = $event['id'];
|
|
}
|
|
return $event_ids;
|
|
}
|
|
}
|
|
// error, so return null
|
|
return null;
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|