Merge branch '2.4' into feature/tag_filter_rework

pull/2706/head
iglocska 2017-11-30 22:28:35 +01:00
commit 05a89f5e87
23 changed files with 412 additions and 93 deletions

View File

@ -513,14 +513,13 @@ class AppController extends Controller {
));
foreach ($attributes as $k => $attribute) {
if ($k > 0) {
$attribute['Attribute']['uuid'] = CakeText::uuid();
$this->Attribute->save($attribute);
$this->Attribute->delete($attribute['Attribute']['id']);
$counter++;
}
}
}
$this->Server->updateDatabase('makeAttributeUUIDsUnique');
$this->Session->setFlash('Done. Assigned new UUIDs to ' . $counter . ' attribute(s).');
$this->Session->setFlash('Done. Deleted ' . $counter . ' duplicate attribute(s).');
$this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration'));
}

View File

@ -2252,11 +2252,17 @@ class EventsController extends AppController {
'value', 'comment', 'to_ids', 'timestamp');
$requested_obj_attributes = array('uuid', 'name', 'meta-category');
if (isset($this->params['url']['attributes'])) {
$requested_attributes = explode(',', $this->params['url']['attributes']);
if (!isset($this->params['url']['obj_attributes'])) $requested_obj_attributes = array();
$requested_attributes = explode(',', $this->params['url']['attributes']);
}
if (isset($this->params['url']['obj_attributes'])) {
$requested_obj_attributes = explode(',', $this->params['url']['obj_attributes']);
$requested_obj_attributes = explode(',', $this->params['url']['obj_attributes']);
}
if (isset($data['request']['attributes'])) {
if (!isset($data['request']['obj_attributes'])) $requested_obj_attributes = array();
$requested_attributes = $data['request']['attributes'];
}
if (isset($data['request']['obj_attributes'])) $requested_obj_attributes = $data['request']['obj_attributes'];
if (isset($events)) {
foreach ($events as $eventid) {
$attributes = $this->Event->csv($user, $eventid, $ignore, $list, false, $category, $type, $includeContext, $enforceWarninglist);
@ -3317,6 +3323,7 @@ class EventsController extends AppController {
unset($distributions[4]);
}
$this->set('proposals', $event['Event']['orgc_id'] != $this->Auth->user('org_id') && !$this->_isSiteAdmin());
$this->set('distributions', $distributions);
$this->set('sgs', $sgs);
$this->set('event', $event);
@ -3459,22 +3466,30 @@ class EventsController extends AppController {
}
}
}
$emailResult = '';
$messageScope = $objectType == 'ShadowAttribute' ? 'proposals' : 'attributes';
if ($saved > 0) {
$event = $this->Event->find('first', array(
'conditions' => array('Event.id' => $id),
'recursive' => -1
));
if ($event['Event']['published'] == 1) {
$event['Event']['published'] = 0;
if ($objectType != 'ShadowAttribute') {
$event = $this->Event->find('first', array(
'conditions' => array('Event.id' => $id),
'recursive' => -1
));
if ($event['Event']['published'] == 1) {
$event['Event']['published'] = 0;
}
$date = new DateTime();
$event['Event']['timestamp'] = $date->getTimestamp();
$this->Event->save($event);
} else {
if (!$this->Event->ShadowAttribute->sendProposalAlertEmail($id)) {
$emailResult = " but sending out the alert e-mails has failed for at least one recipient";
}
}
$date = new DateTime();
$event['Event']['timestamp'] = $date->getTimestamp();
$this->Event->save($event);
}
if ($failed > 0) {
$this->Session->setFlash($saved . ' attributes created. ' . $failed . ' attributes could not be saved. This may be due to attributes with similar values already existing.');
$this->Session->setFlash($saved . ' ' . $messageScope . ' created' . $emailResult . '. ' . $failed . ' ' . $messageScope . ' could not be saved. This may be due to attributes with similar values already existing.');
} else {
$this->Session->setFlash($saved . ' attributes created.');
$this->Session->setFlash($saved . ' ' . $messageScope . ' created' . $emailResult . '.');
}
$this->redirect(array('controller' => 'events', 'action' => 'view', $id));
} else {

View File

@ -676,6 +676,13 @@ class ServersController extends AppController {
$this->set('priorities', $priorities);
$this->set('k', $id);
$this->layout = false;
$subGroup = 'general';
if ($pathToSetting[0] === 'Plugin') {
$subGroup = explode('_', $pathToSetting[1])[0];
}
$this->set('subGroup', $subGroup);
$this->render('/Elements/healthElements/settings_row');
}
@ -939,6 +946,9 @@ class ServersController extends AppController {
}
public function serverSettingsEdit($setting, $id = false, $forceSave = false) {
// invalidate config.php from php opcode cache
opcache_reset();
if (!$this->_isSiteAdmin()) throw new MethodNotAllowedException();
if (!isset($setting) || !isset($id)) throw new MethodNotAllowedException();
$this->set('id', $id);

View File

@ -55,7 +55,13 @@ class UsersController extends AppController {
$user['User']['pgp_status'] = isset($pgpDetails[2]) ? $pgpDetails[2] : 'OK';
$user['User']['fingerprint'] = !empty($pgpDetails[4]) ? $pgpDetails[4] : 'N/A';
}
$this->set('user', $user);
if ($this->_isRest()) {
unset($user['User']['server_id']);
$user['User']['password'] = '*****';
return $this->RestResponse->viewData(array('User' => $user['User']), $this->response->type());
} else {
$this->set('user', $user);
}
}
public function request_API(){
@ -709,7 +715,7 @@ class UsersController extends AppController {
$c = 0;
foreach ($fields as $field) {
if (isset($fieldsOldValues[$c]) && $fieldsOldValues[$c] != $fieldsNewValues[$c]) {
if ($field != 'confirm_password') {
if ($field != 'confirm_password' && $field != 'enable_password') {
$fieldsResultStr = $fieldsResultStr . ', ' . $field . ' (' . $fieldsOldValues[$c] . ') => (' . $fieldsNewValues[$c] . ')';
}
}
@ -862,9 +868,9 @@ class UsersController extends AppController {
$this->User->save($user['User'], true, array('id', 'last_login', 'current_login'));
if (empty($this->Auth->authenticate['Form']['passwordHasher']) && !empty($passwordToSave)) $this->User->saveField('password', $passwordToSave);
$this->User->Behaviors->enable('SysLogLogable.SysLogLogable');
// TODO removed the auto redirect for now, due to security concerns - will look more into this
// $this->redirect($this->Auth->redirectUrl());
$this->redirect(array('controller' => 'events', 'action' => 'index'));
// no state changes are ever done via GET requests, so it is safe to return to the original page:
$this->redirect($this->Auth->redirectUrl());
// $this->redirect(array('controller' => 'events', 'action' => 'index'));
} else {
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];

View File

@ -11,7 +11,8 @@ class ComplexTypeTool {
'/\(dot\)/' => '.',
'/\\\\\./' => '.',
'/\.+/' => '.',
'/\[hxxp:\/\/\]/' => 'http://'
'/\[hxxp:\/\/\]/' => 'http://',
'/\\\/' => ''
);
private $__tlds = array();
@ -89,6 +90,10 @@ class ComplexTypeTool {
return array_values($array);
}
private function __parse_row($row, $delimiter) {
$columns = str_getcsv($row, $delimiter);
return $columns;
}
/*
* parse a CSV file with the given settings
@ -100,7 +105,13 @@ class ComplexTypeTool {
*/
public function checkCSV($input, $settings = array()) {
$delimiter = !empty($settings['delimiter']) ? $settings['delimiter'] : ",";
$lines = explode("\n", $input);
$rows = str_getcsv($input, "\n");
$data = array();
foreach ($rows as $k => $row) {
if (empty($row[0]) || $row[0] === '#') continue;
$data[$k] = str_getcsv($row, $delimiter);
}
unset($rows);
unset($input);
$values = !empty($settings['value']) ? $settings['value'] : array();
if (!is_array($values)) {
@ -110,15 +121,8 @@ class ComplexTypeTool {
$values[$key] = intval($value);
}
$iocArray = array();
foreach ($lines as $linePos => $line) {
$line = trim($line);
if (empty($line) || $line[0] === "#") continue;
if ($delimiter === '\t') {
$elements = preg_split('/\t/', $line);
} else {
$elements = explode($delimiter, $line);
}
foreach ($elements as $elementPos => $element) {
foreach ($data as $rowPos => $row) {
foreach ($row as $elementPos => $element) {
if ((!empty($values) && in_array(($elementPos + 1), $values)) || empty($values)) {
$element = trim($element, " \t\n\r\0\x0B\"\'");
if (isset($settings['excluderegex']) && !empty($settings['excluderegex'])) {
@ -209,7 +213,7 @@ class ComplexTypeTool {
}
$inputRefanged = rtrim($inputRefanged, ".");
if (strpos($input, '@') !== false) {
if (filter_var($input, FILTER_VALIDATE_EMAIL)) return array('types' => array('email-src', 'email-dst', 'whois-registrant-email'), 'to_ids' => true, 'default_type' => 'email-src', 'value' => $input);
if (filter_var($input, FILTER_VALIDATE_EMAIL)) return array('types' => array('email-src', 'email-dst', 'target-email', 'whois-registrant-email'), 'to_ids' => true, 'default_type' => 'email-src', 'value' => $input);
}
// note down and remove the port if it's a url / domain name / hostname / ip
// input2 from here on is the variable containing the original input with the port removed. It is only used by url / domain name / hostname / ip

View File

@ -129,6 +129,11 @@ class PubSubTool {
return $this->__pushToRedis(':data:misp_json_' . $type, json_encode($data, JSON_PRETTY_PRINT));
}
public function publish($data, $type, $action = false) {
if (!empty($action)) $data['action'] = $action;
return $this->__pushToRedis(':data:misp_json_' . $type, json_encode($data, JSON_PRETTY_PRINT));
}
public function killService($settings = false) {
$redis = new Redis();
if ($this->checkIfRunning()) {

View File

@ -8,7 +8,12 @@ class SyncTool {
if (!empty($server)) {
if ($server['Server']['cert_file']) $params['ssl_cafile'] = APP . "files" . DS . "certs" . DS . $server['Server']['id'] . '.pem';
if ($server['Server']['client_cert_file']) $params['ssl_local_cert'] = APP . "files" . DS . "certs" . DS . $server['Server']['id'] . '_client.pem';
if ($server['Server']['self_signed']) $params['ssl_allow_self_signed'] = $server['Server']['self_signed'];
if ($server['Server']['self_signed']) {
$params['ssl_allow_self_signed'] = true;
$params['ssl_verify_peer_name'] = false;
if (!isset($server['Server']['cert_file']))
$params['ssl_verify_peer'] = false;
}
}
$HttpSocket = new HttpSocket($params);

View File

@ -64,7 +64,6 @@ class Attribute extends AppModel {
// skip Correlation for the following types
public $nonCorrelatingTypes = array(
'vulnerability',
'comment',
'http-method',
'aba-rtn',
@ -205,6 +204,8 @@ class Attribute extends AppModel {
'ip-dst|port' => array('desc' => 'IP destination and port number seperated by a |', 'default_category' => 'Network activity', 'to_ids' => 1),
'ip-src|port' => array('desc' => 'IP source and port number seperated by a |', 'default_category' => 'Network activity', 'to_ids' => 1),
'hostname|port' => array('desc' => 'Hostname and port number seperated by a |', 'default_category' => 'Network activity', 'to_ids' => 1),
'mac-address' => array('desc' => 'Mac address', 'default_category' => 'Network activity', 'to_ids' => 0),
'mac-eui-64' => array('desc' => 'Mac EUI-64 address', 'default_category' => 'Network activity', 'to_ids' => 0),
// verify IDS flag defaults for these
'email-dst-display-name' => array('desc' => 'Email destination display name', 'default_category' => 'Payload delivery', 'to_ids' => 0),
'email-src-display-name' => array('desc' => 'Email source display name', 'default_category' => 'Payload delivery', 'to_ids' => 0),
@ -269,7 +270,7 @@ class Attribute extends AppModel {
'Payload delivery' => array(
'desc' => 'Information about how the malware is delivered',
'formdesc' => 'Information about the way the malware payload is initially delivered, for example information about the email or web-site, vulnerability used, originating IP etc. Malware sample itself should be attached here.',
'types' => array('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha512/224', 'sha512/256', 'ssdeep', 'imphash', 'impfuzzy','authentihash', 'pehash', 'tlsh', 'filename', 'filename|md5', 'filename|sha1', 'filename|sha224', 'filename|sha256', 'filename|sha384', 'filename|sha512', 'filename|sha512/224', 'filename|sha512/256', 'filename|authentihash', 'filename|ssdeep', 'filename|tlsh', 'filename|imphash','filename|impfuzzy', 'filename|pehash', 'ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'hostname', 'domain', 'email-src', 'email-dst', 'email-subject', 'email-attachment', 'email-body', 'url', 'user-agent', 'AS', 'pattern-in-file', 'pattern-in-traffic', 'yara', 'sigma', 'attachment', 'malware-sample', 'link', 'malware-type', 'comment', 'text', 'hex', 'vulnerability', 'x509-fingerprint-sha1', 'other', 'hostname|port', 'email-dst-display-name', 'email-src-display-name', 'email-header', 'email-reply-to', 'email-x-mailer', 'email-mime-boundary', 'email-thread-index', 'email-message-id', 'mobile-application-id', 'whois-registrant-email')
'types' => array('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha512/224', 'sha512/256', 'ssdeep', 'imphash', 'impfuzzy','authentihash', 'pehash', 'tlsh', 'filename', 'filename|md5', 'filename|sha1', 'filename|sha224', 'filename|sha256', 'filename|sha384', 'filename|sha512', 'filename|sha512/224', 'filename|sha512/256', 'filename|authentihash', 'filename|ssdeep', 'filename|tlsh', 'filename|imphash','filename|impfuzzy', 'filename|pehash', 'mac-address', 'mac-eui-64', 'ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'hostname', 'domain', 'email-src', 'email-dst', 'email-subject', 'email-attachment', 'email-body', 'url', 'user-agent', 'AS', 'pattern-in-file', 'pattern-in-traffic', 'yara', 'sigma', 'attachment', 'malware-sample', 'link', 'malware-type', 'comment', 'text', 'hex', 'vulnerability', 'x509-fingerprint-sha1', 'other', 'hostname|port', 'email-dst-display-name', 'email-src-display-name', 'email-header', 'email-reply-to', 'email-x-mailer', 'email-mime-boundary', 'email-thread-index', 'email-message-id', 'mobile-application-id', 'whois-registrant-email')
),
'Artifacts dropped' => array(
'desc' => 'Any artifact (files, registry keys etc.) dropped by the malware or other modifications to the system',
@ -287,7 +288,7 @@ class Attribute extends AppModel {
),
'Network activity' => array(
'desc' => 'Information about network traffic generated by the malware',
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'email-dst', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-sha1', 'other', 'hex', 'cookie')
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'mac-address', 'mac-eui-64', 'email-dst', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-sha1', 'other', 'hex', 'cookie')
),
'Payload type' => array(
'desc' => 'Information about the final payload(s)',
@ -296,12 +297,12 @@ class Attribute extends AppModel {
),
'Attribution' => array(
'desc' => 'Identification of the group, organisation, or country behind the attack',
'types' => array('threat-actor', 'campaign-name', 'campaign-id', 'whois-registrant-phone', 'whois-registrant-email', 'whois-registrant-name', 'whois-registrar', 'whois-creation-date','comment', 'text', 'x509-fingerprint-sha1', 'other')
'types' => array('threat-actor', 'campaign-name', 'campaign-id', 'whois-registrant-phone', 'whois-registrant-email', 'whois-registrant-name', 'whois-registrar', 'whois-creation-date','comment', 'text', 'x509-fingerprint-sha1', 'other', 'dns-soa-email')
),
'External analysis' => array(
'desc' => 'Any other result from additional analysis of the malware like tools output',
'formdesc' => 'Any other result from additional analysis of the malware like tools output Examples: pdf-parser output, automated sandbox analysis, reverse engineering report.',
'types' => array('md5', 'sha1', 'sha256','filename', 'filename|md5', 'filename|sha1', 'filename|sha256', 'ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'hostname', 'domain', 'domain|ip', 'url', 'user-agent', 'regkey', 'regkey|value', 'AS', 'snort', 'pattern-in-file', 'pattern-in-traffic', 'pattern-in-memory', 'vulnerability', 'attachment', 'malware-sample', 'link', 'comment', 'text', 'x509-fingerprint-sha1', 'github-repository', 'other', 'cortex')
'types' => array('md5', 'sha1', 'sha256','filename', 'filename|md5', 'filename|sha1', 'filename|sha256', 'ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'mac-address', 'mac-eui-64', 'hostname', 'domain', 'domain|ip', 'url', 'user-agent', 'regkey', 'regkey|value', 'AS', 'snort', 'pattern-in-file', 'pattern-in-traffic', 'pattern-in-memory', 'vulnerability', 'attachment', 'malware-sample', 'link', 'comment', 'text', 'x509-fingerprint-sha1', 'github-repository', 'other', 'cortex')
),
'Financial fraud' => array(
'desc' => 'Financial Fraud indicators',
@ -347,6 +348,8 @@ class Attribute extends AppModel {
'filename' => 'Payload delivery',
'ip-src' => 'Network activity',
'ip-dst' => 'Network activity',
'mac-address' => 'Network activity',
'mac-eui-64' => 'Network activity',
'hostname' => 'Network activity',
'domain' => 'Network activity',
'url' => 'Network activity',
@ -357,7 +360,8 @@ class Attribute extends AppModel {
'hex' => 'Other',
'attachment' => 'External analysis',
'malware-sample' => 'Payload delivery',
'cortex' => 'External analysis'
'cortex' => 'External analysis',
'dns-soa-email' => 'Attribution'
);
// typeGroupings are a mapping to high level groups for attributes
@ -366,7 +370,7 @@ class Attribute extends AppModel {
// This helps generate quick filtering for the event view, but we may reuse this and enhance it in the future for other uses (such as the API?)
public $typeGroupings = array(
'file' => array('attachment', 'pattern-in-file', 'md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', 'sha512/224', 'sha512/256', 'ssdeep', 'imphash', 'impfuzzy','authentihash', 'pehash', 'tlsh', 'filename', 'filename|md5', 'filename|sha1', 'filename|sha224', 'filename|sha256', 'filename|sha384', 'filename|sha512', 'filename|sha512/224', 'filename|sha512/256', 'filename|authentihash', 'filename|ssdeep', 'filename|tlsh', 'filename|imphash', 'filename|pehash', 'malware-sample', 'x509-fingerprint-sha1'),
'network' => array('ip-src', 'ip-dst', 'ip-src|port', 'ip-dst|port', 'hostname', 'hostname|port', 'domain', 'domain|ip', 'email-dst', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-traffic', 'x509-fingerprint-sha1'),
'network' => array('ip-src', 'ip-dst', 'ip-src|port', 'ip-dst|port', 'mac-address', 'mac-eui-64', 'hostname', 'hostname|port', 'domain', 'domain|ip', 'email-dst', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-traffic', 'x509-fingerprint-sha1'),
'financial' => array('btc', 'iban', 'bic', 'bank-account-nr', 'aba-rtn', 'bin', 'cc-number', 'prtn', 'phone-number')
);
@ -900,6 +904,16 @@ class Attribute extends AppModel {
}
}
break;
case 'mac-address':
if (preg_match('/^([a-fA-F0-9]{2}[:|\-| |\.]?){6}$/', $value) == 1) {
$returnValue = true;
}
break;
case 'mac-eui-64':
if (preg_match('/^([a-fA-F0-9]{2}[:|\-| |\.]?){8}$/', $value) == 1) {
$returnValue = true;
}
break;
case 'hostname':
case 'domain':
if (preg_match("#^[A-Z0-9.\-_]+\.[A-Z0-9\-]{2,}$#i", $value)) {
@ -935,7 +949,7 @@ class Attribute extends AppModel {
case 'dns-soa-email':
case 'jabber-id':
// we don't use the native function to prevent issues with partial email addresses
if (preg_match("#^[A-Z0-9._&%$+-=~]*@[A-Z0-9.\-_]+\.[A-Z0-9\-]{2,}$#i", $value)) {
if (preg_match("#^.*\@.*\..*$#i", $value)) {
$returnValue = true;
} else {
$returnValue = 'Email address has an invalid format. Please double check the value or select type "other".';
@ -1206,6 +1220,11 @@ class Attribute extends AppModel {
}
return $parts[0] . '|' . $parts[1];
break;
case 'mac-address':
case 'mac-eui-64':
$value = str_replace(array('.', ':', '-', ' '), '', $value);
$value = wordwrap($value, 2, ':', true);
break;
case 'hostname|port':
$value = strtolower($value);
str_replace(':', '|', $value);
@ -2990,7 +3009,8 @@ class Attribute extends AppModel {
return true;
}
} else {
$attribute['timestamp'] = $date;
$date = new DateTime();
$attribute['timestamp'] = $date->getTimestamp();;
}
} else {
$this->create();
@ -2999,7 +3019,7 @@ class Attribute extends AppModel {
$this->create();
}
$attribute['event_id'] = $eventId;
if ($attribute['distribution'] == 4) {
if (isset($attribute['distribution']) && $attribute['distribution'] == 4) {
$attribute['sharing_group_id'] = $this->SharingGroup->captureSG($attribute['SharingGroup'], $user);
}
$fieldList = array(

View File

@ -425,10 +425,10 @@ class Event extends AppModel {
$this->Correlation = ClassRegistry::init('Correlation');
$db = $this->getDataSource();
if (isset($this->data['Event']['date'])) {
$this->Correlation->updateAll(array('Correlation.date' => $db->value($this->data['Event']['date'])), array('Correlation.event_id' => $db->value($this->data['Event']['id'])));
$this->Correlation->updateAll(array('Correlation.date' => $db->value($this->data['Event']['date'])), array('Correlation.event_id' => intval($this->data['Event']['id'])));
}
if (isset($this->data['Event']['info'])) {
$this->Correlation->updateAll(array('Correlation.info' => $db->value($this->data['Event']['info'])), array('Correlation.event_id' => $db->value($this->data['Event']['id'])));
$this->Correlation->updateAll(array('Correlation.info' => $db->value($this->data['Event']['info'])), array('Correlation.event_id' => intval($this->data['Event']['id'])));
}
}
if (empty($this->data['Event']['unpublishAction']) && empty($this->data['Event']['skip_zmq']) && Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {

View File

@ -3,7 +3,16 @@
App::uses('AppModel', 'Model');
class Log extends AppModel {
public $warningActions = array(
'warning',
'change_pw',
'login_fail',
'version_warning',
'auth_fail'
);
public $errorActions = array(
'error'
);
public $validate = array(
'action' => array(
'rule' => array('inList', array(
@ -94,6 +103,7 @@ class Log extends AppModel {
$this->data['Log'][$tf] = substr($this->data['Log'][$tf], 0, 65532) . '...';
}
}
$this->logData($this->data);
return true;
}
@ -223,4 +233,31 @@ class Log extends AppModel {
return $result;
}
}
function logData($data) {
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_user_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->publish($data, 'audit', 'log');
}
if (Configure::read('Security.syslog')) {
// write to syslogd as well
$syslog = new SysLog();
$action = 'info';
if (isset($data['Log']['action'])) {
if (in_array($data['Log']['action'], $this->errorActions)) {
$action = 'err';
}
if (in_array($data['Log']['action'], $this->warningActions)) {
$action = 'warning';
}
}
$entry = $data['Log']['action'];
if (!empty($data['Log']['description'])) {
$entry .= sprintf(' -- %s', $data['Log']['description']);
}
$syslog->write($action, $entry);
}
return true;
}
}

View File

@ -84,6 +84,8 @@ class Organisation extends AppModel{
parent::beforeValidate();
if (empty($this->data['Organisation']['uuid'])) {
$this->data['Organisation']['uuid'] = CakeText::uuid();
} else {
$this->data['Organisation']['uuid'] = trim($this->data['Organisation']['uuid']);
}
$date = date('Y-m-d H:i:s');
if (!empty($this->data['Organisation']['restricted_to_domain'])) {

View File

@ -952,6 +952,15 @@ class Server extends AppModel {
'type' => 'string',
'editable' => false,
),
'syslog' => array(
'level' => 0,
'description' => 'Enable this setting to pass all audit log entries directly to syslog. Keep in mind, this is verbose and will include user, organisation, event data.',
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'password_policy_length' => array(
'level' => 2,
'description' => 'Password length requirement. If it is not set or it is set to 0, then the default value is assumed (12).',
@ -1244,6 +1253,14 @@ class Server extends AppModel {
'test' => 'testBool',
'type' => 'boolean'
),
'ZeroMQ_audit_notifications_enable' => array(
'level' => 2,
'description' => 'Enables or disables the publishing of log entries to the ZMQ pubsub feed. Keep in mind, this can get pretty verbose depending on your logging settings.',
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean'
),
'Sightings_enable' => array(
'level' => 1,
'description' => 'Enables or disables the sighting functionality. When enabled, users can use the UI or the appropriate APIs to submit sightings data about indicators.',
@ -2204,6 +2221,9 @@ class Server extends AppModel {
}
public function serverSettingReadSingle($settingObject, $settingName, $leafKey) {
// invalidate config.php from php opcode cache
opcache_reset();
$setting = Configure::read($settingName);
$result = $this->__evaluateLeaf($settingObject, $leafKey, $setting);
$result['setting'] = $settingName;
@ -2800,7 +2820,7 @@ class Server extends AppModel {
try {
$response = $HttpSocket->get($uri, '', $request);
} catch (Exception $e) {
if ($response->code != '200') {
if (!isset($response) || $response->code != '200') {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
@ -2810,11 +2830,11 @@ class Server extends AppModel {
'email' => $user['email'],
'action' => 'error',
'user_id' => $user['id'],
'title' => 'Error: Connection to the server has failed.',
'title' => 'Error: Connection to the server has failed.' . isset($response->code) ? ' Returned response code: ' . $response->code : '',
));
}
}
if ($response->code != '200') {
if (!isset($response) || $response->code != '200') {
return 1;
}
$remoteVersion = json_decode($response->body, true);

View File

@ -176,7 +176,7 @@ class SharingGroup extends AppModel {
} else if ($scope == 'uuid') {
$sgs = $this->find('list', array(
'recursive' => -1,
'fields' => array('id', 'uuid'),
'fields' => array('SharingGroup.id', 'SharingGroup.uuid'),
'conditions' => $conditions,
));
return $sgs;

View File

@ -249,20 +249,10 @@ class SysLogLogableBehavior extends LogableBehavior {
}
}
$this->Log->create($logData);
$this->Log->save(null, array(
'validate' => false));
// write to syslogd as well
$syslog = new SysLog();
if (isset($logData['Log']['change'])) {
$syslog->write('notice', $logData['Log']['description'].' -- '.$logData['Log']['change']);
} else {
$syslog->write('notice', $logData['Log']['description']);
}
$this->Log->save(null, array('validate' => false));
}
function setup(Model $Model, $config = array()) {
if (!is_array($config)) {
$config = array();
}

View File

@ -8,7 +8,7 @@
if ($setting['type'] == 'boolean') $setting['value'] = ($setting['value'] === true ? 'true' : 'false');
if (isset($setting['options'])) $setting['value'] = $setting['options'][$setting['value']];
?>
<tr id ="<?php echo h($k); ?>_row">
<tr id ="<?php echo h("${subGroup}_$k"); ?>_row" class="subGroup_<?php echo h($subGroup);?>">
<?php
if (!empty($setting['redacted'])) {
$setting['value'] = '*****';
@ -17,10 +17,10 @@
<td class="short" style="<?php echo $bgColour; ?>"><?php echo h($priorities[$setting['level']]);?></td>
<td class="short" style="<?php echo $bgColour; ?>"><?php echo h($setting['setting']);?></td>
<?php if ((isset($setting['editable']) && !$setting['editable']) || $setting['level'] == 3): ?>
<td id="setting_<?php echo $k; ?>_passive" class="inline-field-solid" style="<?php echo $bgColour; ?>width:500px;"><?php echo nl2br(h($setting['value']));?></td>
<td id="setting_<?php echo h("${subGroup}_$k"); ?>_passive" class="inline-field-solid" style="<?php echo $bgColour; ?>width:500px;"><?php echo nl2br(h($setting['value']));?></td>
<?php else: ?>
<td id="setting_<?php echo $k; ?>_solid" class="inline-field-solid" ondblclick="serverSettingsActivateField('<?php echo $setting['setting'];?>', '<?php echo $k;?>')" style="<?php echo $bgColour; ?>width:500px;"><?php echo h($setting['value']);?></td>
<td id="setting_<?php echo $k; ?>_placeholder" class="short hidden inline-field-placeholder" style="<?php echo $bgColour; ?>width:500px;"></td>
<td id="setting_<?php echo h("${subGroup}_$k"); ?>_solid" class="inline-field-solid" ondblclick="serverSettingsActivateField('<?php echo $setting['setting'];?>', '<?php echo $k;?>')" style="<?php echo $bgColour; ?>width:500px;"><?php echo h($setting['value']);?></td>
<td id="setting_<?php echo h("${subGroup}_$k"); ?>_placeholder" class="short hidden inline-field-placeholder" style="<?php echo $bgColour; ?>width:500px;"></td>
<?php endif; ?>
<td style="<?php echo $bgColour; ?>"><?php echo h($setting['description']);?></td>
<td class="short" style="<?php echo $bgColour; ?>"><?php if (isset($setting['error']) && $setting['level'] != 3) echo h($setting['errorMessage']); ?></td>

View File

@ -1,6 +1,7 @@
<div class="index">
<h2><?php echo h($title);?></h2>
<p>Below you can see the attributes that are to be created. Make sure that the categories and the types are correct, often several options will be offered based on an inconclusive automatic resolution. </p>
<?php $scope = !empty($proposals) ? 'proposals' : 'attributes'; ?>
<p>Below you can see the <?php echo $scope; ?> that are to be created. Make sure that the categories and the types are correct, often several options will be offered based on an inconclusive automatic resolution. </p>
<?php
$instanceDefault = 5;
if (!empty(Configure::read('MISP.default_attribute_distribution'))) {
@ -194,7 +195,7 @@
?>
</table>
<span>
<button class="btn btn-primary" style="float:left;" onClick="freetextImportResultsSubmit('<?php echo h($event['Event']['id']); ?>', '<?php echo count($resultArray); ?>', '<?php echo h($type); ?>');">Submit</button>
<button class="btn btn-primary" style="float:left;" onClick="freetextImportResultsSubmit('<?php echo h($event['Event']['id']); ?>', '<?php echo count($resultArray); ?>', '<?php echo h($type); ?>');">Submit <?php echo $scope; ?></button>
<span style="float:right">
<?php
if (!empty($optionsRearranged)):

View File

@ -36,6 +36,7 @@
echo $this->fetch('script');
echo $this->Html->script('jquery'); // Include jQuery library
echo $this->Html->script('misp-touch'); // touch interface support
?>
</head>

@ -1 +1 @@
Subproject commit b046eb4ba77ac6f01f99e7c0b62a1d7e85a66e39
Subproject commit b83616d520dbfaca6f312a3ff54a53aedc8dc5d5

View File

@ -74,9 +74,9 @@ def main(args):
command = r.lpop(namespace + ":command")
if command is not None:
handleCommand(command)
topics = ["misp_json", "misp_json_event", "misp_json_attribute", "misp_json_sighting",
topics = ["misp_json", "misp_json_event", "misp_json_attribute", "misp_json_sighting",
"misp_json_organisation", "misp_json_user", "misp_json_conversation",
"misp_json_object", "misp_json_object_reference"]
"misp_json_object", "misp_json_object_reference", "misp_json_audit"]
message_received = False
for topic in topics:
data = r.lpop(namespace + ":data:" + topic)
@ -84,7 +84,7 @@ def main(args):
pubMessage(topic, data, socket)
message_received = True
if (message_received == False):
time.sleep(1)
time.sleep(0.2)
if ((int(time.time()) - start_time) % 10 == 0):
status_entry = int(((int(time.time()) - start_time)/10) % 5)
status_message = {

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, json, os, datetime, re
import sys, json, os, datetime, re, base64
import pymisp
from stix2 import *
from misp2stix2_dictionaries import *
@ -28,12 +28,10 @@ non_indicator_attributes = ['text', 'comment', 'other', 'link', 'target-user', '
'target-machine', 'target-org', 'target-location', 'target-external',
'vulnerability', 'attachment']
noChangesTypes = ['', '']
def saveFile(args, pathname, package):
def saveFile(args, package):
filename = args[1] + '.out'
with open(filename, 'w') as f:
f.write(str(package))
f.write(json.dumps(package, cls=base.STIXJSONEncoder))
# converts timestamp to the format used by STIX
def getDateFromTimestamp(timestamp):
@ -57,7 +55,7 @@ def readAttributes(event, identity, object_refs, external_refs):
if attr_type in non_indicator_attributes:
if attr_type == "link":
handleLink(attribute, external_refs)
elif attr_type in ('text', 'comment', 'other'):
elif attr_type in ('text', 'comment', 'other') or attr_type not in mispTypesMapping:
addCustomObject(object_refs, attributes, attribute, identity)
else:
handleNonIndicatorAttribute(object_refs, attributes, attribute, identity)
@ -72,7 +70,13 @@ def readAttributes(event, identity, object_refs, external_refs):
addObservedData(object_refs, attributes, attribute, identity)
else:
addCustomObject(object_refs, attributes, attribute, identity)
if event.Galaxy:
galaxy = False
try:
if event.Galaxy:
galaxy = True
except:
pass
if galaxy:
galaxies = event.Galaxy
for galaxy in galaxies:
galaxyType = galaxy['type']
@ -88,7 +92,13 @@ def readAttributes(event, identity, object_refs, external_refs):
addThreatActor(object_refs, attributes, galaxy, identity)
elif galaxyType in ['rat', 'exploit-kit'] or 'tool' in galaxyType:
addTool(object_refs, attributes, galaxy, identity)
if event.Object:
objct = False
try:
if event.Object:
objct = True
except:
pass
if objct:
for obj in event.Object:
to_ids = False
for obj_attr in obj.Attribute:
@ -366,18 +376,23 @@ def defineObservableObject(attr_type, attr_val):
if '|' in attr_type:
_, attr_type2 = attr_type.split('|')
attr_val1, attr_val2 = attr_val.split('|')
object1 = observed_object['1']
if '|ip' in attr_type:
object1 = observed_object['1']
addr_type = defineAddressType(attr_val2)
object0['value'] = attr_val1
object1['type'] = addr_type
object1['value'] = attr_val2
elif 'ip-' in attr_type:
object1 = observed_object['1']
addr_type = defineAddressType(attr_val2)
prot_type = addr_type.split('-')[0]
object1['protocols'].append(prot_type)
object0['type'] = addr_type
object0['value'] = attr_val1
object1['dst_port'] = attr_val2
object1['protocols'].append(defineProtocols[attr_val2] if attr_val2 in defineProtocols else 'tcp')
elif 'hostname' in attr_type:
object1 = observed_object['1']
object0['value'] = attr_val1
object1['dst_port'] = attr_val2
elif 'regkey' in attr_type:
@ -394,11 +409,16 @@ def defineObservableObject(attr_type, attr_val):
if 'x509' in attr_type:
object0['hashes']['sha1'] = attr_val
return observed_object
elif attr_type == 'attachment':
payload = attr_val.encode()
object0['payload_bin'] = base64.b64encode(payload)
elif 'ip-' in attr_type:
addr_type = defineAddressType(attr_val)
object0['type'] = addr_type
elif attr_type == 'port':
object0['protocols'].append(defineProtocols[attr_val] if attr_val in defineProtocols else 'tcp')
for obj_attr in object0:
if obj_attr in ('name', 'value', 'body', 'subject', 'dst_port', 'key'):
if obj_attr in ('name', 'value', 'body', 'subject', 'dst_port', 'key', 'display_name'):
object0[obj_attr] = attr_val
if 'hashes' in obj_attr:
object0[obj_attr] = {attr_type: attr_val}
@ -503,13 +523,18 @@ def defineObservableObjectIpPort(obj_name, obj_attr):
attr_type = attr.type
if attr_type == 'ip-dst':
attr_val = attr.value
obj['0']['type'] = defineAddressType(attr_val)
addr_type = defineAddressType(attr_val)
obj['0']['type'] = addr_type
obj['0']['value'] = attr_val
prot_type = addr_type.split('-')[0]
obj['1']['protocols'].append(prot_type)
elif attr_type in ('text', 'datetime'):
obj_relation = attr.object_relation
if obj_name not in objectTypes[attr_type]:
continue
obj['1'][objectTypes[attr_type][obj_name][obj_relation]] = attr.value
elif 'port' in attr_type:
obj['1']['protocols'].append(defineProtocols[attr_val] if attr_val in defineProtocols else 'tcp')
else:
obj['1'][objectTypes[attr_type][attr.object_relation]] = attr.value
return obj
@ -582,6 +607,13 @@ def getRegistryKeyInfo(obj_attr):
return reg_attr
def definePattern(attr_type, attr_val):
if "'" in attr_val:
sQuoteTmp = attr_val.replace('\'', '##APOSTROPHE##')
attr_val = sQuoteTmp
if '"' in attr_val:
dQuoteTmp = attr_val.replace('"', '##QUOTE##')
#tmp = attr_val.replace('\'', '')
attr_val = dQuoteTmp
if '|' in attr_type:
attr_type1, attr_type2 = attr_type.split('|')
attr_val1, attr_val2 = attr_val.split('|')
@ -673,27 +705,26 @@ def eventReport(event, identity, object_refs, external_refs):
labels.append(tag['name'])
args_report = {'type': "report", 'id': "report--{}".format(event.uuid), 'created_by_ref': identity,
'name': name, 'published': timestamp}
'name': name, 'published': timestamp, 'object_refs': object_refs}
if labels:
args_report['labels'] = labels
else:
args_report['labels'] = ['threat-report']
if object_refs:
args_report['object_refs'] = object_refs
if external_refs:
args_report['external_references'] = external_refs
report = Report(**args_report)
return report
def generateEventPackage(event, SDOs):
#return SDOs
bundle_id = event.uuid
bundle_args = {'type': "bundle", 'spec_version': "2.0", 'id': "bundle--{}".format(bundle_id), 'objects': SDOs}
bundle = Bundle(**bundle_args)
return bundle
def main(args):
pathname = os.path.dirname(sys.argv[0])
pathname = os.path.dirname(args[0])
if len(sys.argv) > 3:
namespace[0] = sys.argv[3]
if len(sys.argv) > 4:
@ -712,7 +743,7 @@ def main(args):
for attribute in attributes:
SDOs.append(attribute)
stix_package = generateEventPackage(misp, SDOs)
saveFile(args, pathname, stix_package)
saveFile(args, stix_package)
print(1)
if __name__ == "__main__":

View File

@ -90,20 +90,24 @@ mispTypesMapping = {
'pattern': 'file:name = \'{0}\' AND file:hashes.\'tlsh\' = \'{1}\''},
'x509-fingerprint-sha1': {'observable': {'0': {'type': 'x509-certificate', 'hashes': {'sha1': ''}}},
'pattern': 'x509-certificate:hashes = \'{0}\''},
'port': {'observable': {'0': {'type': 'network-traffic', 'dst_port': ''}},
'port': {'observable': {'0': {'type': 'network-traffic', 'dst_port': '', 'protpocols': []}},
'pattern': 'network-traffic:dst_port = \'{0}\''},
'ip-dst|port': {'observable': {'0': {'type': '', 'value': ''}, '1': {'type': 'network-traffic', 'dst_ref': '0', 'dst_port': ''}},
'ip-dst|port': {'observable': {'0': {'type': '', 'value': ''}, '1': {'type': 'network-traffic', 'dst_ref': '0', 'dst_port': '', 'protocols': []}},
'pattern': 'network-traffic:dst_port = \'{1}\' AND network-traffic:dst_ref.type = \'{2}\' AND network-traffic:dst_ref.value = \'{0}\''},
'ip-src|port': {'observable': {'0': {'type': '', 'value': ''}, '1': {'type': 'network-traffic', 'src_ref': '0', 'dst_port': ''}},
'ip-src|port': {'observable': {'0': {'type': '', 'value': ''}, '1': {'type': 'network-traffic', 'src_ref': '0', 'dst_port': '', 'protocols': []}},
'pattern': 'network-traffic:src_port = \'{1}\' AND network-traffic:src_ref.type = \'{2}\' AND network-traffic:src_ref.value = \'{0}\''},
'hostname|port': {'observable': {'0': {'type': 'domain-name', 'value': ''}, '1': {'type': 'traffic-network', 'dst_ref': '0', 'dst_port': ''}},
'hostname|port': {'observable': {'0': {'type': 'domain-name', 'value': ''}, '1': {'type': 'network-traffic', 'dst_ref': '0', 'dst_port': '', 'protocols': []}},
'pattern': 'domain-name:value = \'{0}\' AND network-traffic:dst_port = \'{1}\''},
'email-dst-display-name': {'observable': {'0': {'type': 'email-addr', 'display_name': ''}},
'pattern': 'email-addr:display_name = \'{0}\''},
'email-src-display-name': {'observable': {'0': {'type': 'email-addr', 'display_name': ''}},
'pattern': 'email-addr:display_name = \'{0}\''},
'email-reply-to': {'observable': {'0': {'type': 'email-addr', 'value': ''}},
'pattern': 'email-addr:value = \'{0}\''}
'pattern': 'email-addr:value = \'{0}\''},
'attachment': {'observable': {'0': {'type': 'artifact', 'payload_bin': ''}},
'pattern': 'artifact:payload_bin = \'{0}\''},
'mac-address': {'observable': {'0': {'type': 'mac-addr', 'value': ''}},
'pattern': 'mac-addr:value = \'{0}\''}
}
objectsMapping = {'domain-ip': {'pattern': 'domain-name:{0} = \'{1}\' AND '},
@ -158,3 +162,5 @@ objectTypes = {'text': {'x509': {'subject': 'subject', 'issuer': 'issuer', 'pubk
'domain-ip': 'resolves_to_refs[*].value'},
'reg-datatype': 'datatype', 'reg-data': 'data', 'reg-name': 'name', 'reg-key': 'key'
}
defineProtocols = {'80': 'http', '443': 'https'}

View File

@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 CIRCL Computer Incident Response Center Luxembourg (smile gie)
# Copyright (C) 2017 Christian Studer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, json, os, time
import pymisp
eventTypes = {"ipv4-addr": {"src": "ip-src", "dst": "ip-dst", "value": "address_value"},
"ipv6-addr": {"src": "ip-src", "dst": "ip-dst", "value": "address_value"},
"URIObjectType": {"type": "url", "value": "value"},
"FileObjectType": {"type": "filename", "value": "file_name"},
"to": {"type": "email-dst", "value": "address_value"},
"from": {"type": "email-src", "value": "value"},
"subject": {"type": "email-subject", "value": "value"},
"user_agent": "user-agent"}
def loadEvent(args, pathname):
try:
filename = '{}/tmp/{}'.format(pathname, args[1])
tempFile = open(filename, 'r')
if filename.endswith('.json'):
event = json.loads(tempFile.read())
return event
except:
print(json.dumps({'success': 0, 'message': 'The temporary STIX export file could not be read'}))
sys.exit(1)
def getTimestampfromDate(date):
dt = date.split("+")[0]
return int(time.mktime(time.strptime(dt, "%Y-%m-%dT%H:%M:%S")))
def buildMispDict(stixEvent):
mispDict = {}
stixTimestamp = stixEvent.get("timestamp")
date = stixTimestamp.split("T")[0]
mispDict["date"] = date
timestamp = getTimestampfromDate(stixTimestamp)
mispDict["timestamp"] = timestamp
mispDict["info"] = stixEvent["stix_header"].get("title")
event = stixEvent["incidents"][0]
orgSource = event["information_source"]["identity"]["name"]
orgReporter = event["reporter"]["identity"]["name"]
indicators = event["related_indicators"]["indicators"]
mispDict["Attribute"] = []
for indic in indicators:
attribute = {}
indicator = indic.get("indicator")
timestamp = indicator.get("timestamp").split("+")[0]
attribute["timestamp"] = getTimestampfromDate(timestamp)
observable = indicator.get("observable")
properties = observable["object"]["properties"]
try:
cat = properties.get("category")
if "ip" in cat:
if properties.get("is_source"):
attr_type = "src"
else:
attr_type = "dst"
typeVal = eventTypes[cat][attr_type]
value = eventTypes[cat]["value"]
valueVal = properties[value]["value"]
except:
cat = properties.get("xsi:type")
if cat == 'EmailMessageObjectType':
header = properties["header"]
emailType = list(header)[0]
typeVal = eventTypes[emailType]["type"]
value = eventTypes[emailType]["value"]
headerVal = header[emailType]
if emailType == "to":
headerVal = headerVal[0]
elif emailType == "from":
headerVal = headerVal["address_value"]
valueVal = headerVal.get(value)
elif cat == "FileObjectType" and "hashes" in properties:
hashes = properties["hashes"][0]
typeVal = hashes["type"].get("value").lower()
valueVal = hashes["simple_hash_value"].get("value")
elif cat == "HTTPSessionObjectType":
http = properties["http_request_response"][0]
httpAttr = http["http_client_request"]["http_request_header"]["parsed_header"]
attrVal = list(httpAttr)[0]
valueVal = httpAttr.get(attrVal)
typeVal = eventTypes[attrVal]
else:
value = eventTypes[cat]["value"]
typeVal = eventTypes[cat]["type"]
valueVal = properties[value]["value"]
attribute["type"] = typeVal
attribute["value"] = valueVal
attribute["category"] = indic.get("relationship")
#print(attribute)
mispDict["Attribute"].append(attribute)
return mispDict
def saveFile(args, pathname, misp):
filename = "{}/tmp/{}.in".format(pathname, args[1])
eventDict = misp.to_dict(with_timestamp=True)
print(eventDict)
with open(filename, 'w') as f:
f.write(json.dumps(eventDict))
def main(args):
pathname = os.path.dirname(args[0])
stixEvent = loadEvent(args, pathname)
stixEvent = stixEvent["package"]
mispDict = buildMispDict(stixEvent)
#print(mispDict)
misp = pymisp.MISPEvent(None, False)
misp.from_dict(**mispDict)
saveFile(args, pathname, misp)
print(1)
if __name__ == "__main__":
main(sys.argv)

View File

@ -0,0 +1,39 @@
/**
* support for touch devices without the need
*/
$(document).ready(function() {
var touchStartTime = 0;
var touchTarget = null;
document.body.addEventListener('touchstart', function(ev) {
if (touchStartTime == 0) {
touchStartTime = (new Date()).getTime();
touchTarget = ev.target;
}
}, false);
document.body.addEventListener('touchcancel', function(ev) {
// iphone only
touchStartTime = 0;
touchTarget = null;
}, false);
document.body.addEventListener('touchend', function(ev) {
var touchEndTime = (new Date()).getTime();
var canTrigger = (touchStartTime > 0 && (touchEndTime - touchStartTime) > 500 && touchTarget == ev.target);
// reset the variables BEFORE calling the event handler
// so that our code works even if the handler dies
touchStartTime = 0;
touchTarget = null;
if (canTrigger) {
var newEvent = new MouseEvent('dblclick', ev);
try {
ev.target.dispatchEvent(newEvent);
} catch (e) {
// don't care, this was complimentary event anyway
}
}
}, false);
});