Merge remote-tracking branch 'origin/develop' into feature-workflows-2

pull/8530/head
Sami Mokaddem 2022-07-07 09:09:39 +02:00
commit 99a76812bc
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
23 changed files with 833 additions and 471 deletions

View File

@ -40,6 +40,9 @@ class EventShell extends AppShell
'event_id' => ['help' => __('Event ID'), 'required' => true],
'user_id' => ['help' => __('User ID'), 'required' => true],
],
'options' => [
'send' => ['help' => __('Send email to given user'), 'boolean' => true],
],
],
]);
$parser->addSubcommand('duplicateTags', [
@ -607,6 +610,7 @@ class EventShell extends AppShell
public function testEventNotificationEmail()
{
list($eventId, $userId) = $this->args;
$send = $this->param('send');
$user = $this->getUser($userId);
$eventForUser = $this->Event->fetchEvent($user, [
@ -626,10 +630,16 @@ class EventShell extends AppShell
App::uses('SendEmail', 'Tools');
App::uses('GpgTool', 'Tools');
$sendEmail = new SendEmail(GpgTool::initializeGpg());
$sendEmail->setTransport('Debug');
if (!$send) {
$sendEmail->setTransport('Debug');
}
$result = $sendEmail->sendToUser(['User' => $user], null, $emailTemplate);
echo $result['contents']['headers'] . "\n\n" . $result['contents']['message'] . "\n";
if ($send) {
var_dump($result);
} else {
echo $result['contents']['headers'] . "\n\n" . $result['contents']['message'] . "\n";
}
}
/**

View File

@ -4,6 +4,7 @@ App::uses('Controller', 'Controller');
App::uses('File', 'Utility');
App::uses('RequestRearrangeTool', 'Tools');
App::uses('BlowfishConstantPasswordHasher', 'Controller/Component/Auth');
App::uses('BetterCakeEventManager', 'Tools');
/**
* Application Controller
@ -1259,17 +1260,17 @@ class AppController extends Controller
]);
}
}
/** @var TmpFileTool $final */
$final = $model->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
if ($renderView) {
$this->layout = false;
$final = json_decode($final->intoString(), true);
foreach ($final as $key => $data) {
$this->set($key, $data);
}
$this->set($final);
$this->render('/Events/module_views/' . $renderView);
} else {
$filename = $this->RestSearch->getFilename($filters, $scope, $responseType);
return $this->RestResponse->viewData($final, $responseType, false, true, $filename, array('X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType));
$headers = ['X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType];
return $this->RestResponse->viewData($final, $responseType, false, true, $filename, $headers);
}
}
@ -1443,6 +1444,16 @@ class AppController extends Controller
return parent::_getViewObject();
}
public function getEventManager()
{
if (empty($this->_eventManager)) {
$this->_eventManager = new BetterCakeEventManager();
$this->_eventManager->attach($this->Components);
$this->_eventManager->attach($this);
}
return $this->_eventManager;
}
/**
* Close session without writing changes to them and return current user.
* @return array
@ -1462,16 +1473,7 @@ class AppController extends Controller
protected function _jsonDecode($dataToDecode)
{
try {
if (defined('JSON_THROW_ON_ERROR')) {
// JSON_THROW_ON_ERROR is supported since PHP 7.3
return json_decode($dataToDecode, true, 512, JSON_THROW_ON_ERROR);
} else {
$decoded = json_decode($dataToDecode, true);
if ($decoded === null) {
throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error());
}
return $decoded;
}
return JsonTool::decode($dataToDecode);
} catch (Exception $e) {
throw new HttpException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.', 405, $e);
}

View File

@ -738,6 +738,7 @@ class ACLComponent extends Component
'verifyGPG' => array(),
'view' => array('*'),
'getGpgPublicKey' => array('*'),
'unsubscribe' => ['*'],
),
'userSettings' => array(
'index' => array('*'),
@ -1001,6 +1002,8 @@ class ACLComponent extends Component
private function __findAllFunctions()
{
$functionsToIgnore = ['beforeFilter', 'afterFilter', 'beforeRender', 'getEventManager'];
$functionFinder = '/function[\s\n]+(\S+)[\s\n]*\(/';
$dir = new Folder(APP . 'Controller');
$files = $dir->find('.*\.php');
@ -1011,11 +1014,11 @@ class ACLComponent extends Component
$controllerName = '*';
}
$functionArray = array();
$fileContents = file_get_contents(APP . 'Controller' . DS . $file);
$fileContents = FileAccessTool::readFromFile(APP . 'Controller' . DS . $file);
$fileContents = preg_replace('/\/\*[^\*]+?\*\//', '', $fileContents);
preg_match_all($functionFinder, $fileContents, $functionArray);
foreach ($functionArray[1] as $function) {
if ($function[0] !== '_' && $function !== 'beforeFilter' && $function !== 'afterFilter' && $function !== 'beforeRender') {
if ($function[0] !== '_' && !in_array($function, $functionsToIgnore, true)) {
$results[$controllerName][] = $function;
}
}
@ -1036,8 +1039,7 @@ class ACLComponent extends Component
$missing = array();
foreach ($results as $controller => $functions) {
foreach ($functions as $function) {
if (!isset(self::ACL_LIST[$controller])
|| !in_array($function, array_keys(self::ACL_LIST[$controller]))) {
if (!isset(self::ACL_LIST[$controller]) || !in_array($function, array_keys(self::ACL_LIST[$controller]))) {
$missing[$controller][] = $function;
}
}

View File

@ -1,8 +1,21 @@
<?php
App::uses('BlowfishPasswordHasher', 'Controller/Component/Auth');
App::uses('AbstractPasswordHasher', 'Controller/Component/Auth');
class BlowfishConstantPasswordHasher extends BlowfishPasswordHasher
class BlowfishConstantPasswordHasher extends AbstractPasswordHasher
{
/**
* @param string $password
* @return string
*/
public function hash($password)
{
$hash = password_hash($password, PASSWORD_BCRYPT);
if ($hash === false) {
throw new RuntimeException('Could not generate hashed password');
}
return $hash;
}
/**
* @param string $password
* @param string $hashedPassword
@ -10,6 +23,6 @@ class BlowfishConstantPasswordHasher extends BlowfishPasswordHasher
*/
public function check($password, $hashedPassword)
{
return hash_equals($hashedPassword, Security::hash($password, 'blowfish', $hashedPassword));
return password_verify($password, $hashedPassword);
}
}

View File

@ -619,7 +619,7 @@ class EventsController extends AppController
if (empty($usersToMatch)) {
$nothing = true;
} else {
$this->paginate['conditions']['AND'][] = ['Event.user_id' => array_unique($usersToMatch)];
$this->paginate['conditions']['AND'][] = ['Event.user_id' => array_unique($usersToMatch, SORT_REGULAR)];
}
}
break;
@ -3328,20 +3328,40 @@ class EventsController extends AppController
public function restSearchExport($id = null, $returnFormat = null)
{
if (is_null($returnFormat)) {
if (is_numeric($id)) {
$idList = [$id];
} else {
$idList = $this->_jsonDecode($id);
}
if ($returnFormat === null) {
$exportFormats = [
'attack' => __('Attack matrix'),
'attack-sightings' => __('Attack matrix by sightings'),
'context' => __('Aggregated context data'),
'context-markdown' => __('Aggregated context data as Markdown'),
'csv' => __('CSV'),
'hashes' => __('Hashes'),
'hosts' => __('Hosts file'),
'json' => __('MISP JSON'),
'netfilter' => __('Netfilter'),
'opendata' => __('Open data'),
'openioc' => __('OpenIOC'),
'rpz' => __('RPZ'),
'snort' => __('Snort rules'),
'stix' => __('STIX 1 XML'),
'stix-json' => __('STIX 1 JSON'),
'stix2' => __('STIX 2'),
'suricata' => __('Suricata rules'),
'text' => __('Text file'),
'xml' => __('MISP XML'),
'yara' => __('YARA rules'),
'yara-json' => __('YARA rules (JSON)'),
];
$idList = is_numeric($id) ? [$id] : $this->_jsonDecode($id);
if (empty($idList)) {
throw new NotFoundException(__('Invalid input.'));
}
$this->set('idList', $idList);
$this->set('exportFormats', array_keys($this->Event->validFormats));
$this->set('exportFormats', $exportFormats);
$this->render('ajax/eventRestSearchExportConfirmationForm');
} else {
$returnFormat = empty($this->Event->validFormats[$returnFormat]) ? 'json' : $returnFormat;
$returnFormat = !isset($this->Event->validFormats[$returnFormat]) ? 'json' : $returnFormat;
$idList = $id;
if (!is_array($idList)) {
if (is_numeric($idList) || Validation::uuid($idList)) {
@ -3354,19 +3374,17 @@ class EventsController extends AppController
throw new NotFoundException(__('Invalid input.'));
}
$filters = [
'eventid' => $idList
'eventid' => $idList,
'published' => [true, false], // fetch published and unpublished events
];
$elementCounter = 0;
$renderView = false;
$validFormat = $this->Event->validFormats[$returnFormat];
$responseType = $validFormat[0];
$responseType = $this->Event->validFormats[$returnFormat][0];
$final = $this->Event->restSearch($this->Auth->user(), $returnFormat, $filters, false, false, $elementCounter, $renderView);
if (!empty($renderView) && !empty($final)) {
if ($renderView) {
$final = json_decode($final->intoString(), true);
foreach ($final as $key => $data) {
$this->set($key, $data);
}
$this->set($final);
$this->set('responseType', $responseType);
$this->set('returnFormat', $returnFormat);
$this->set('renderView', $renderView);

View File

@ -1922,7 +1922,7 @@ class ServersController extends AppController
$dbVersion = $this->AdminSetting->getSetting('db_version');
$updateProgress = $this->Server->getUpdateProgress();
$updateProgress['db_version'] = $dbVersion;
$maxUpdateNumber = max(array_keys($this->Server->db_changes));
$maxUpdateNumber = max(array_keys(Server::DB_CHANGES));
$updateProgress['complete_update_remaining'] = max($maxUpdateNumber - $dbVersion, 0);
$updateProgress['update_locked'] = $this->Server->isUpdateLocked();
$updateProgress['lock_remaining_time'] = $this->Server->getLockRemainingTime();

View File

@ -118,6 +118,24 @@ class UsersController extends AppController
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Something went wrong, please try again later.')), 'status'=>200, 'type' => 'json'));
}
public function unsubscribe($code)
{
$user = $this->Auth->user();
if (!hash_equals($this->User->unsubscribeCode($user), rtrim($code, '.'))) {
$this->Flash->error(__('Invalid unsubscribe code.'));
$this->redirect(['action' => 'view', 'me']);
}
if ($user['autoalert']) {
$this->User->updateField($this->Auth->user(), 'autoalert', false);
$this->Flash->success(__('Successfully unsubscribed from event alert.'));
} else {
$this->Flash->info(__('Already unsubscribed from event alert.'));
}
$this->redirect(['action' => 'view', 'me']);
}
public function edit()
{
$currentUser = $this->User->find('first', array(

View File

@ -6,16 +6,16 @@ class HashesExport
'flatten' => 1
);
public $validTypes = array(
const VALID_TYPES = array(
'simple' => array(
'md5', 'sha1', 'sha256', 'sha224', 'sha512', 'sha512/224', 'sha512/256', 'ssdeep', 'imphash', 'tlsh',
'x509-fingerprint-sha1', 'x509-fingerprint-md5', 'x509-fingerprint-sha256', 'pehash', 'authentihash',
'impfuzzy'
'md5', 'sha1', 'sha256', 'sha224', 'sha384', 'sha512', 'sha512/224', 'sha512/256', 'sha3-224', 'sha3-256',
'sha3-384', 'sha3-512', 'ssdeep', 'imphash', 'tlsh', 'x509-fingerprint-sha1', 'x509-fingerprint-md5',
'x509-fingerprint-sha256', 'pehash', 'authentihash', 'impfuzzy'
),
'composite' => array(
'malware-sample', 'filename|md5', 'filename|sha1', 'filename|sha256', 'filename|sha224', 'filename|sha512',
'filename|sha512/224', 'filename|sha512/256', 'filename|ssdeep', 'filename|imphash', 'filename|tlsh',
'x509-fingerprint-sha1', 'x509-fingerprint-md5', 'x509-fingerprint-sha256', 'filename|pehash',
'filename|sha512/224', 'filename|sha512/256', 'filename|sha3-224', 'filename|sha3-256', 'filename|sha3-384',
'filename|sha3-512', 'filename|ssdeep', 'filename|imphash', 'filename|tlsh', 'filename|pehash',
'filename|authentihash', 'filename|impfuzzy'
)
);
@ -23,18 +23,17 @@ class HashesExport
public function handler($data, $options = array())
{
if ($options['scope'] === 'Attribute') {
if (in_array($data['Attribute']['type'], $this->validTypes['composite'])) {
if (in_array($data['Attribute']['type'], self::VALID_TYPES['composite'], true)) {
return explode('|', $data['Attribute']['value'])[1];
} else if (in_array($data['Attribute']['type'], $this->validTypes['simple'])) {
} else if (in_array($data['Attribute']['type'], self::VALID_TYPES['simple'], true)) {
return $data['Attribute']['value'];
}
}
if ($options['scope'] === 'Event') {
} else if ($options['scope'] === 'Event') {
$result = array();
foreach ($data['Attribute'] as $attribute) {
if (in_array($attribute['type'], $this->validTypes['composite'])) {
if (in_array($attribute['type'], self::VALID_TYPES['composite'], true)) {
$result[] = explode('|', $attribute['value'])[1];
} else if (in_array($attribute['type'], $this->validTypes['simple'])) {
} else if (in_array($attribute['type'], self::VALID_TYPES['simple'], true)) {
$result[] = $attribute['value'];
}
}

View File

@ -2,7 +2,7 @@
class JsonExport
{
public $non_restrictive_export = true;
public $non_restrictive_export = true;
/**
* @param $data
@ -11,17 +11,17 @@ class JsonExport
*/
public function handler($data, $options = array())
{
if ($options['scope'] === 'Attribute') {
return $this->__attributeHandler($data, $options);
} else if($options['scope'] === 'Event') {
return $this->__eventHandler($data, $options);
} else if($options['scope'] === 'Object') {
if ($options['scope'] === 'Attribute') {
return $this->__attributeHandler($data, $options);
} else if ($options['scope'] === 'Event') {
return $this->__eventHandler($data, $options);
} else if ($options['scope'] === 'Object') {
return $this->__objectHandler($data, $options);
} else if($options['scope'] === 'Sighting') {
return $this->__sightingsHandler($data, $options);
} else if($options['scope'] === 'GalaxyCluster') {
return $this->__galaxyClusterHandler($data, $options);
}
} else if ($options['scope'] === 'Sighting') {
return $this->__sightingsHandler($data, $options);
} else if ($options['scope'] === 'GalaxyCluster') {
return $this->__galaxyClusterHandler($data, $options);
}
}
/**
@ -29,66 +29,68 @@ class JsonExport
* @param array $options
* @return Generator
*/
private function __eventHandler($event, $options = array())
private function __eventHandler($event, $options = array())
{
App::uses('JSONConverterTool', 'Tools');
return JSONConverterTool::streamConvert($event);
}
private function __objectHandler($object, $options = array()) {
App::uses('JSONConverterTool', 'Tools');
return json_encode(JSONConverterTool::convertObject($object, false, true));
return JSONConverterTool::streamConvert($event);
}
private function __attributeHandler($attribute, $options = array())
{
$attribute = array_merge($attribute['Attribute'], $attribute);
unset($attribute['Attribute']);
if (isset($attribute['Object']) && empty($attribute['Object']['id'])) {
unset($attribute['Object']);
}
$tagTypes = array('AttributeTag', 'EventTag');
foreach($tagTypes as $tagType) {
if (isset($attribute[$tagType])) {
foreach ($attribute[$tagType] as $tk => $tag) {
if ($tagType === 'EventTag') {
$attribute[$tagType][$tk]['Tag']['inherited'] = 1;
}
$attribute['Tag'][] = $attribute[$tagType][$tk]['Tag'];
}
unset($attribute[$tagType]);
}
}
unset($attribute['value1']);
unset($attribute['value2']);
return json_encode($attribute);
}
private function __objectHandler($object, $options = array())
{
App::uses('JSONConverterTool', 'Tools');
return JsonTool::encode(JSONConverterTool::convertObject($object, false, true));
}
private function __attributeHandler($attribute, $options = array())
{
$attribute = array_merge($attribute['Attribute'], $attribute);
unset($attribute['Attribute']);
if (isset($attribute['Object']) && empty($attribute['Object']['id'])) {
unset($attribute['Object']);
}
$tagTypes = array('AttributeTag', 'EventTag');
foreach ($tagTypes as $tagType) {
if (isset($attribute[$tagType])) {
foreach ($attribute[$tagType] as $tag) {
if ($tagType === 'EventTag') {
$tag['Tag']['inherited'] = 1;
}
$attribute['Tag'][] = $tag['Tag'];
}
unset($attribute[$tagType]);
}
}
unset($attribute['value1']);
unset($attribute['value2']);
return JsonTool::encode($attribute);
}
private function __sightingsHandler($sighting, $options = array())
{
return json_encode($sighting);
return JsonTool::encode($sighting);
}
private function __galaxyClusterHandler($cluster, $options = array())
{
return json_encode($cluster);
return JsonTool::encode($cluster);
}
public function header($options = array())
{
if ($options['scope'] === 'Attribute') {
return '{"response": {"Attribute": [';
} else {
return '{"response": [';
}
if ($options['scope'] === 'Attribute') {
return '{"response": {"Attribute": [';
} else {
return '{"response": [';
}
}
public function footer($options = array())
{
if ($options['scope'] === 'Attribute') {
return ']}}' . PHP_EOL;
} else {
return ']}' . PHP_EOL;
}
if ($options['scope'] === 'Attribute') {
return ']}}' . PHP_EOL;
} else {
return ']}' . PHP_EOL;
}
}
public function separator()

View File

@ -7,84 +7,125 @@ class NidsExport
public $classtype = 'trojan-activity';
public $format = ""; // suricata (default), snort
public $supportedObjects = array('network-connection', 'ddos');
public $checkWhitelist = true;
public $checkWhitelist = true;
public $additional_params = array(
'contain' => array(
'Event' => array(
'fields' => array('threat_level_id')
)
),
'flatten' => 1
);
public $additional_params = array(
'contain' => array(
'Event' => array(
'fields' => array('threat_level_id')
)
),
public function handler($data, $options = array())
{
$continue = empty($format);
$this->checkWhitelist = false;
if ($options['scope'] === 'Attribute') {
$this->export(
array($data),
$options['user']['nids_sid'],
$options['returnFormat'],
$continue
);
} else if ($options['scope'] === 'Event') {
if (!empty($data['EventTag'])) {
$data['Event']['EventTag'] = $data['EventTag'];
}
if (!empty($data['Attribute'])) {
$this->__convertFromEventFormat($data['Attribute'], $data, $options, $continue);
}
if (!empty($data['Object'])) {
foreach ($data['Object'] as $object) {
$this->__convertFromEventFormat($object['Attribute'], $data, $options, $continue);
}
}
}
return '';
}
);
private function __convertFromEventFormat($attributes, $event, $options = array(), $continue = false) {
$rearranged = array();
foreach ($attributes as $attribute) {
$attributeTag = array();
if (!empty($attribute['AttributeTag'])) {
$attributeTag = $attribute['AttributeTag'];
unset($attribute['AttributeTag']);
}
$rearranged[] = array(
'Attribute' => $attribute,
'AttributeTag' => $attributeTag,
'Event' => $event['Event']
);
}
$this->export(
$rearranged,
$options['user']['nids_sid'],
$options['returnFormat'],
$continue
);
return true;
public function handler($data, $options = array())
{
$continue = empty($format);
$this->checkWhitelist = false;
if ($options['scope'] === 'Attribute') {
$this->export(
array($data),
$options['user']['nids_sid'],
$options['returnFormat'],
$continue
);
} else if ($options['scope'] === 'Event') {
if (!empty($data['EventTag'])) {
$data['Event']['EventTag'] = $data['EventTag'];
}
if (!empty($data['Attribute'])) {
$this->__convertFromEventFormat($data['Attribute'], $data, $options, $continue);
}
if (!empty($data['Object'])) {
$this->__convertFromEventFormatObject($data['Object'], $data, $options, $continue);
}
}
return '';
}
}
private function __convertFromEventFormat($attributes, $event, $options = array(), $continue = false) {
public function header($options = array())
{
$this->explain();
return '';
}
$rearranged = array();
foreach ($attributes as $attribute) {
$attributeTag = array();
if (!empty($attribute['AttributeTag'])) {
$attributeTag = $attribute['AttributeTag'];
unset($attribute['AttributeTag']);
}
$rearranged[] = array(
'Attribute' => $attribute,
'AttributeTag' => $attributeTag,
'Event' => $event['Event']
);
}
$this->export(
$rearranged,
$options['user']['nids_sid'],
$options['returnFormat'],
$continue
);
return true;
public function footer()
{
return implode ("\n", $this->rules);
}
}
public function separator()
{
return '';
}
private function __convertFromEventFormatObject($objects, $event, $options = array(), $continue = false) {
$rearranged = array();
foreach ($objects as $object) {
if(in_array($object['name'], $this->supportedObjects)){
$objectTag = array();
foreach($object['Attribute'] as $attribute) {
if (!empty($attribute['AttributeTag'])) {
$objectTag = array_merge($objectTag, $attribute['AttributeTag']);
unset($attribute['AttributeTag']);
}
}
$rearranged[] = array(
'Attribute' => $object, // Using 'Attribute' instead of 'Object' to comply with function export
'AttributeTag' => $objectTag, // Using 'AttributeTag' instead of 'ObjectTag' to comply with function export
'Event' => $event['Event']
);
} else { // In case no custom export exists for the object, the approach falls back to the attribute case
$this->__convertFromEventFormat($object['Attribute'], $event, $options, $continue);
}
}
$this->export(
$rearranged,
$options['user']['nids_sid'],
$options['returnFormat'],
$continue
);
return true;
}
public function header($options = array())
{
$this->explain();
return '';
}
public function footer()
{
return implode ("\n", $this->rules);
}
public function separator()
{
return '';
}
public function explain()
{
@ -93,7 +134,7 @@ class NidsExport
$this->rules[] = '# These NIDS rules contain some variables that need to exist in your configuration.';
$this->rules[] = '# Make sure you have set:';
$this->rules[] = '#';
$this->rules[] = '# $HOME_NET - Your internal network range';
$this->rules[] = '# $HOME_NET - Your internal network range';
$this->rules[] = '# $EXTERNAL_NET - The network considered as outside';
$this->rules[] = '# $SMTP_SERVERS - All your internal SMTP servers';
$this->rules[] = '# $HTTP_PORTS - The ports used to contain HTTP traffic (not required with suricata export)';
@ -106,10 +147,10 @@ class NidsExport
public function export($items, $startSid, $format="suricata", $continue = false)
{
$this->format = $format;
if ($this->checkWhitelist && !isset($this->Whitelist)) {
$this->Whitelist = ClassRegistry::init('Whitelist');
$this->whitelist = $this->Whitelist->getBlockedValues();
}
if ($this->checkWhitelist && !isset($this->Whitelist)) {
$this->Whitelist = ClassRegistry::init('Whitelist');
$this->whitelist = $this->Whitelist->getBlockedValues();
}
// output a short explanation
if (!$continue) {
@ -119,20 +160,20 @@ class NidsExport
foreach ($items as $item) {
// retrieve all tags for this item to add them to the msg
$tagsArray = [];
if (!empty($item['AttributeTag'])) {
foreach ($item['AttributeTag'] as $tag_attr) {
if (array_key_exists('name', $tag_attr['Tag'])) {
array_push($tagsArray, $tag_attr['Tag']['name']);
}
}
}
if (!empty($item['Event']['EventTag'])) {
foreach ($item['Event']['EventTag'] as $tag_event) {
if (array_key_exists('name', $tag_event['Tag'])) {
array_push($tagsArray, $tag_event['Tag']['name']);
}
}
}
if (!empty($item['AttributeTag'])) {
foreach ($item['AttributeTag'] as $tag_attr) {
if (array_key_exists('name', $tag_attr['Tag'])) {
array_push($tagsArray, $tag_attr['Tag']['name']);
}
}
}
if (!empty($item['Event']['EventTag'])) {
foreach ($item['Event']['EventTag'] as $tag_event) {
if (array_key_exists('name', $tag_event['Tag'])) {
array_push($tagsArray, $tag_event['Tag']['name']);
}
}
}
$ruleFormatMsgTags = implode(",", $tagsArray);
# proto src_ip src_port direction dst_ip dst_port msg rule_content tag sid rev
@ -142,69 +183,179 @@ class NidsExport
$sid = $startSid + ($item['Attribute']['id'] * 10); // leave 9 possible rules per attribute type
$sid++;
switch ($item['Attribute']['type']) {
// LATER nids - test all the snort attributes
// LATER nids - add the tag keyword in the rules to capture network traffic
// LATER nids - sanitize every $attribute['value'] to not conflict with snort
case 'ip-dst':
$this->ipDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-src':
$this->ipSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-dst|port':
$this->ipDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-src|port':
$this->ipSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email':
$this->emailSrcRule($ruleFormat, $item['Attribute'], $sid++);
$this->emailDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-src':
$this->emailSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-dst':
$this->emailDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-subject':
$this->emailSubjectRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-attachment':
$this->emailAttachmentRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'domain':
$this->domainRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'domain|ip':
$this->domainIpRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'hostname':
$this->hostnameRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'url':
$this->urlRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'user-agent':
$this->userAgentRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ja3-fingerprint-md5':
$this->ja3Rule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ja3s-fingerprint-md5': // Atribute type doesn't exists yet (2020-12-10) but ready when created.
$this->ja3sRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'snort':
$this->snortRule($ruleFormat, $item['Attribute'], $sid, $ruleFormatMsg, $ruleFormatReference);
// no break
default:
break;
if(!empty($item['Attribute']['type'])) { // item is an 'Attribute'
switch ($item['Attribute']['type']) {
// LATER nids - test all the snort attributes
// LATER nids - add the tag keyword in the rules to capture network traffic
// LATER nids - sanitize every $attribute['value'] to not conflict with snort
case 'ip-dst':
$this->ipDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-src':
$this->ipSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-dst|port':
$this->ipDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ip-src|port':
$this->ipSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email':
$this->emailSrcRule($ruleFormat, $item['Attribute'], $sid);
$this->emailDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-src':
$this->emailSrcRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-dst':
$this->emailDstRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-subject':
$this->emailSubjectRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'email-attachment':
$this->emailAttachmentRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'domain':
$this->domainRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'domain|ip':
$this->domainIpRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'hostname':
$this->hostnameRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'url':
$this->urlRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'user-agent':
$this->userAgentRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ja3-fingerprint-md5':
$this->ja3Rule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ja3s-fingerprint-md5': // Atribute type doesn't exists yet (2020-12-10) but ready when created.
$this->ja3sRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'snort':
$this->snortRule($ruleFormat, $item['Attribute'], $sid, $ruleFormatMsg, $ruleFormatReference);
// no break
default:
break;
}
} else if(!empty($item['Attribute']['name'])) { // Item is an 'Object'
switch ($item['Attribute']['name']) {
case 'network-connection':
$this->networkConnectionRule($ruleFormat, $item['Attribute'], $sid);
break;
case 'ddos':
$this->ddosRule($ruleFormat, $item['Attribute'], $sid);
break;
default:
break;
}
}
}
return $this->rules;
}
public function networkConnectionRule($ruleFormat, $object, &$sid)
{
$attributes = NidsExport::getObjectAttributes($object);
if(!array_key_exists('layer4-protocol', $attributes)){
$attributes['layer4-protocol'] = 'ip'; // If layer-4 protocol is unknown, we roll-back to layer-3 ('ip')
}
if(!array_key_exists('ip-src', $attributes)){
$attributes['ip-src'] = '$HOME_NET'; // If ip-src is unknown, we roll-back to $HOME_NET
}
if(!array_key_exists('ip-dst', $attributes)){
$attributes['ip-dst'] = '$HOME_NET'; // If ip-dst is unknown, we roll-back to $HOME_NET
}
if(!array_key_exists('src-port', $attributes)){
$attributes['src-port'] = 'any'; // If src-port is unknown, we roll-back to 'any'
}
if(!array_key_exists('dst-port', $attributes)){
$attributes['dst-port'] = 'any'; // If dst-port is unknown, we roll-back to 'any'
}
$this->rules[] = sprintf(
$ruleFormat,
false,
$attributes['layer4-protocol'], // proto
$attributes['ip-src'], // src_ip
$attributes['src-port'], // src_port
'->', // direction
$attributes['ip-dst'], // dst_ip
$attributes['dst-port'], // dst_port
'Network connection between ' . $attributes['ip-src'] . ' and ' . $attributes['ip-dst'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
);
}
public function ddosRule($ruleFormat, $object, &$sid)
{
$attributes = NidsExport::getObjectAttributes($object);
if(!array_key_exists('protocol', $attributes)){
$attributes['protocol'] = 'ip'; // If protocol is unknown, we roll-back to 'ip'
}
if(!array_key_exists('ip-src', $attributes)){
$attributes['ip-src'] = '$HOME_NET'; // If ip-src is unknown, we roll-back to $HOME_NET
}
if(!array_key_exists('ip-dst', $attributes)){
$attributes['ip-dst'] = '$HOME_NET'; // If ip-dst is unknown, we roll-back to $HOME_NET
}
if(!array_key_exists('src-port', $attributes)){
$attributes['src-port'] = 'any'; // If src-port is unknown, we roll-back to 'any'
}
if(!array_key_exists('dst-port', $attributes)){
$attributes['dst-port'] = 'any'; // If dst-port is unknown, we roll-back to 'any'
}
$this->rules[] = sprintf(
$ruleFormat,
false,
$attributes['protocol'], // proto
$attributes['ip-src'], // src_ip
$attributes['src-port'], // src_port
'->', // direction
$attributes['ip-dst'], // dst_ip
$attributes['dst-port'], // dst_port
'DDOS attack detected between ' . $attributes['ip-src'] . ' and ' . $attributes['ip-dst'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
);
}
public static function getObjectAttributes($object)
{
$attributes = array();
foreach ($object['Attribute'] as $attribute) {
$attributes[$attribute['object_relation']] = $attribute['value'];
}
return $attributes;
}
public function domainIpRule($ruleFormat, $attribute, &$sid)
{
$values = explode('|', $attribute['value']);
@ -225,17 +376,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'ip', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
$ipport[0], // dst_ip
$ipport[1], // dst_port
'Outgoing To IP: ' . $attribute['value'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
'ip', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
$ipport[0], // dst_ip
$ipport[1], // dst_port
'Outgoing To IP: ' . $attribute['value'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
);
}
@ -246,17 +397,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'ip', // proto
$ipport[0], // src_ip
$ipport[1], // src_port
'->', // direction
'$HOME_NET', // dst_ip
'any', // dst_port
'Incoming From IP: ' . $attribute['value'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
'ip', // proto
$ipport[0], // src_ip
$ipport[1], // src_port
'->', // direction
'$HOME_NET', // dst_ip
'any', // dst_port
'Incoming From IP: ' . $attribute['value'], // msg
'', // rule_content
'', // tag
$sid, // sid
1 // rev
);
}
@ -268,17 +419,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Source Email Address: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Source Email Address: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -290,17 +441,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Destination Email Address: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Destination Email Address: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -313,17 +464,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Bad Email Subject', // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Bad Email Subject', // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -336,17 +487,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Bad Email Attachment', // msg
$content, // rule_content // LATER nids - test and finetune this snort rule https://secure.wikimedia.org/wikipedia/en/wiki/MIME#Content-Disposition
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$EXTERNAL_NET', // src_ip
'any', // src_port
'->', // direction
'$SMTP_SERVERS', // dst_ip
'25', // dst_port
'Bad Email Attachment', // msg
$content, // rule_content // LATER nids - test and finetune this snort rule https://secure.wikimedia.org/wikipedia/en/wiki/MIME#Content-Disposition
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -358,33 +509,33 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'udp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Hostname: ' . $attribute['value'], // msg
$content, // rule_content
'', // tag
$sid, // sid
1 // rev
'udp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Hostname: ' . $attribute['value'], // msg
$content, // rule_content
'', // tag
$sid, // sid
1 // rev
);
$sid++;
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Hostname: ' . $attribute['value'], // msg
$content. ' flow:established;', // rule_content
'', // tag
$sid, // sid
1 // rev
'tcp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Hostname: ' . $attribute['value'], // msg
$content. ' flow:established;', // rule_content
'', // tag
$sid, // sid
1 // rev
);
$sid++;
// also do http requests
@ -392,17 +543,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP Hostname: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP Hostname: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -414,33 +565,33 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'udp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Domain: ' . $attribute['value'], // msg
$content, // rule_content
'', // tag
$sid, // sid
1 // rev
'udp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Domain: ' . $attribute['value'], // msg
$content, // rule_content
'', // tag
$sid, // sid
1 // rev
);
$sid++;
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Domain: ' . $attribute['value'], // msg
$content. ' flow:established;', // rule_content
'', // tag
$sid, // sid
1 // rev
'tcp', // proto
'any', // src_ip
'any', // src_port
'->', // direction
'any', // dst_ip
'53', // dst_port
'Domain: ' . $attribute['value'], // msg
$content. ' flow:established;', // rule_content
'', // tag
$sid, // sid
1 // rev
);
$sid++;
// also do http requests,
@ -448,17 +599,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP Domain: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP Domain: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -473,17 +624,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP URL: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing HTTP URL: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -495,17 +646,17 @@ class NidsExport
$this->rules[] = sprintf(
$ruleFormat,
($overruled) ? '#OVERRULED BY WHITELIST# ' : '',
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing User-Agent: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
'tcp', // proto
'$HOME_NET', // src_ip
'any', // src_port
'->', // direction
'$EXTERNAL_NET', // dst_ip
'$HTTP_PORTS', // dst_port
'Outgoing User-Agent: ' . $attribute['value'], // msg
$content, // rule_content
'tag:session,600,seconds;', // tag
$sid, // sid
1 // rev
);
}
@ -527,37 +678,37 @@ class NidsExport
$tmpRule = str_replace(array("\r","\n"), " ", $attribute['value']);
// rebuild the rule by overwriting the different keywords using preg_replace()
// sid - '/sid\s*:\s*[0-9]+\s*;/'
// rev - '/rev\s*:\s*[0-9]+\s*;/'
// sid - '/sid\s*:\s*[0-9]+\s*;/'
// rev - '/rev\s*:\s*[0-9]+\s*;/'
// classtype - '/classtype:[a-zA-Z_-]+;/'
// msg - '/msg\s*:\s*".*?"\s*;/'
// msg - '/msg\s*:\s*".*?"\s*;/'
// reference - '/reference\s*:\s*.+?;/'
// tag - '/tag\s*:\s*.+?;/'
// tag - '/tag\s*:\s*.+?;/'
$replaceCount = array();
$tmpRule = preg_replace('/sid\s*:\s*[0-9]+\s*;/', 'sid:' . $sid . ';', $tmpRule, -1, $replaceCount['sid']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
$tmpRule = preg_replace('/rev\s*:\s*[0-9]+\s*;/', 'rev:1;', $tmpRule, -1, $replaceCount['rev']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
$tmpRule = preg_replace('/classtype:[a-zA-Z_-]+;/', 'classtype:' . $this->classtype . ';', $tmpRule, -1, $replaceCount['classtype']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
$tmpRule = preg_replace('/msg\s*:\s*"(.*?)"\s*;/', sprintf($ruleFormatMsg, 'snort-rule | $1') . ';', $tmpRule, -1, $replaceCount['msg']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
$tmpRule = preg_replace('/reference\s*:\s*.+?;/', $ruleFormatReference . ';', $tmpRule, -1, $replaceCount['reference']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
$tmpRule = preg_replace('/reference\s*:\s*.+?;/', $ruleFormatReference . ';', $tmpRule, -1, $replaceCount['reference']);
if (null == $tmpRule) {
return false;
} // don't output the rule on error with the regex
} // don't output the rule on error with the regex
// FIXME nids - implement priority overwriting
// some values were not replaced, so we need to add them ourselves, and insert them in the rule
@ -667,13 +818,13 @@ class NidsExport
public function checkWhitelist($value)
{
if ($this->checkWhitelist && is_array($this->whitelist)) {
foreach ($this->whitelist as $wlitem) {
if (preg_match($wlitem, $value)) {
return true;
}
}
}
if ($this->checkWhitelist && is_array($this->whitelist)) {
foreach ($this->whitelist as $wlitem) {
if (preg_match($wlitem, $value)) {
return true;
}
}
}
return false;
}
@ -717,4 +868,4 @@ class NidsExport
}
return $ipport;
}
}
}

View File

@ -0,0 +1,63 @@
<?php
class BetterCakeEventManager extends CakeEventManager
{
/**
* This method is similar as original dispatch, but do not return newly created event. With returning event, there is
* big memory leak in PHP at least for PHP version 7.4.19.
* @param CakeEvent $event
*/
public function dispatch($event)
{
$listeners = $this->listeners($event->name());
if (empty($listeners)) {
return null;
}
foreach ($listeners as $listener) {
if ($event->isStopped()) {
break;
}
if ($listener['passParams'] === true) {
$result = call_user_func_array($listener['callable'], $event->data);
} else {
$result = $listener['callable']($event);
}
if ($result === false) {
$event->stopPropagation();
}
if ($result !== null) {
$event->result = $result;
}
}
}
/**
* @param $eventKey
* @return array
*/
public function listeners($eventKey)
{
if ($this->_isGlobal) {
$localListeners = [];
} else {
$localListeners = $this->_listeners[$eventKey] ?? [];
}
$globalListeners = static::instance()->prioritisedListeners($eventKey);
$priorities = array_merge(array_keys($globalListeners), array_keys($localListeners));
$priorities = array_unique($priorities, SORT_REGULAR);
asort($priorities);
$result = [];
foreach ($priorities as $priority) {
if (isset($globalListeners[$priority])) {
$result = array_merge($result, $globalListeners[$priority]);
}
if (isset($localListeners[$priority])) {
$result = array_merge($result, $localListeners[$priority]);
}
}
return $result;
}
}

View File

@ -484,6 +484,10 @@ class SendEmail
]);
}
if ($body instanceof SendEmailTemplate && $body->listUnsubscribe()) {
$email->addHeaders(['List-Unsubscribe' => "<{$body->listUnsubscribe()}>"]);
}
$signed = false;
if (Configure::read('GnuPG.sign')) {
if (!$this->gpg) {

View File

@ -10,6 +10,9 @@ class SendEmailTemplate
/** @var string|null */
private $referenceId;
/** @var string */
private $listUnsubscribe;
/** @var string|null */
private $subject;
@ -31,6 +34,18 @@ class SendEmailTemplate
$this->referenceId = $referenceId;
}
/**
* @param string|null $listUnsubscribe
* @return string|void
*/
public function listUnsubscribe($listUnsubscribe = null)
{
if ($listUnsubscribe === null) {
return $this->listUnsubscribe;
}
$this->listUnsubscribe = $listUnsubscribe;
}
/**
* Get subject from template. Must be called after render method.
* @param string|null $subject

View File

@ -25,6 +25,7 @@ App::uses('LogableBehavior', 'Assets.models/behaviors');
App::uses('RandomTool', 'Tools');
App::uses('FileAccessTool', 'Tools');
App::uses('JsonTool', 'Tools');
App::uses('BetterCakeEventManager', 'Tools');
class AppModel extends Model
{
@ -47,15 +48,6 @@ class AppModel extends Model
/** @var AttachmentTool|null */
private $attachmentTool;
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->findMethods['column'] = true;
if (in_array('phar', stream_get_wrappers())) {
stream_wrapper_unregister('phar');
}
}
// deprecated, use $db_changes
// major -> minor -> hotfix -> requires_logout
const OLD_DB_CHANGES = array(
@ -93,7 +85,7 @@ class AppModel extends Model
87,
);
public $advanced_updates_description = array(
const ADVANCED_UPDATES_DESCRIPTION = array(
'seenOnAttributeAndObject' => array(
'title' => 'First seen/Last seen Attribute table',
'description' => 'Update the Attribute table to support first_seen and last_seen feature, with a microsecond resolution.',
@ -107,6 +99,15 @@ class AppModel extends Model
),
);
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->findMethods['column'] = true;
if (in_array('phar', stream_get_wrappers(), true)) {
stream_wrapper_unregister('phar');
}
}
public function isAcceptedDatabaseError($errorMessage)
{
if ($this->isMysql()) {
@ -275,9 +276,9 @@ class AppModel extends Model
$liveOff = false;
$exitOnError = false;
if (isset($this->advanced_updates_description[$command])) {
$liveOff = isset($this->advanced_updates_description[$command]['liveOff']) ? $this->advanced_updates_description[$command]['liveOff'] : $liveOff;
$exitOnError = isset($this->advanced_updates_description[$command]['exitOnError']) ? $this->advanced_updates_description[$command]['exitOnError'] : $exitOnError;
if (isset(self::ADVANCED_UPDATES_DESCRIPTION[$command])) {
$liveOff = isset(self::ADVANCED_UPDATES_DESCRIPTION[$command]['liveOff']) ? self::ADVANCED_UPDATES_DESCRIPTION[$command]['liveOff'] : $liveOff;
$exitOnError = isset(self::ADVANCED_UPDATES_DESCRIPTION[$command]['exitOnError']) ? self::ADVANCED_UPDATES_DESCRIPTION[$command]['exitOnError'] : $exitOnError;
}
$sqlArray = array();
@ -1798,7 +1799,7 @@ class AppModel extends Model
$total_update_count = $sql_update_count + $index_update_count;
$this->__setUpdateProgress(0, $total_update_count, $command);
$str_index_array = array();
foreach($indexArray as $toIndex) {
foreach ($indexArray as $toIndex) {
$str_index_array[] = __('Indexing %s -> %s', $toIndex[0], $toIndex[1]);
}
$this->__setUpdateCmdMessages(array_merge($sqlArray, $str_index_array));
@ -1806,8 +1807,8 @@ class AppModel extends Model
$errorCount = 0;
// execute test before update. Exit if it fails
if (isset($this->advanced_updates_description[$command]['preUpdate'])) {
$function_name = $this->advanced_updates_description[$command]['preUpdate'];
if (isset(self::ADVANCED_UPDATES_DESCRIPTION[$command]['preUpdate'])) {
$function_name = self::ADVANCED_UPDATES_DESCRIPTION[$command]['preUpdate'];
try {
$this->{$function_name}();
} catch (Exception $e) {
@ -1943,10 +1944,15 @@ class AppModel extends Model
$this->Server->serverSettingsSaveValue('MISP.live', $isLive);
}
// check whether the adminSetting should be updated after the update
private function __postUpdate($command) {
if (isset($this->advanced_updates_description[$command]['record'])) {
if($this->advanced_updates_description[$command]['record']) {
/**
* Check whether the adminSetting should be updated after the update.
* @param string $command
* @return void
*/
private function __postUpdate($command)
{
if (isset(self::ADVANCED_UPDATES_DESCRIPTION[$command]['record'])) {
if (self::ADVANCED_UPDATES_DESCRIPTION[$command]['record']) {
$this->AdminSetting->changeSetting($command, 1);
}
}
@ -3457,4 +3463,19 @@ class AppModel extends Model
$logEntries[] = $logEntry;
Configure::write('pendingLogEntries', $logEntries);
}
/**
* Use different CakeEventManager to fix memory leak
* @return CakeEventManager
*/
public function getEventManager()
{
if (empty($this->_eventManager)) {
$this->_eventManager = new BetterCakeEventManager();
$this->_eventManager->attach($this->Behaviors);
$this->_eventManager->attach($this);
}
return $this->_eventManager;
}
}

View File

@ -1734,8 +1734,13 @@ class Attribute extends AppModel
return $result;
}
// This method takes a string from an argument with several elements (separated by '&&' and negated by '!') and returns 2 arrays
// array 1 will have all of the non negated terms and array 2 all the negated terms
/**
* This method takes a string from an argument with several elements (separated by '&&' and negated by '!') and returns 2 arrays
* array 1 will have all of the non negated terms and array 2 all the negated terms
*
* @param string|array $args
* @return array[]
*/
public function dissectArgs($args)
{
$result = array(0 => array(), 1 => array(), 2 => array());
@ -1757,7 +1762,7 @@ class Attribute extends AppModel
}
} else {
foreach ($args as $arg) {
if ($arg[0] === '!') {
if (is_string($arg) && $arg[0] === '!') {
$result[1][] = substr($arg, 1);
} else {
$result[0][] = $arg;
@ -2476,7 +2481,7 @@ class Attribute extends AppModel
}
$temp = $this->Event->EventTag->find('all', array(
'recursive' => -1,
'contain' => array('Tag'),
'contain' => ['Tag' => ['fields' => ['id', 'name', 'colour', 'numerical_value']]],
'conditions' => $tagConditions,
));
if (empty($temp)) {
@ -2484,16 +2489,11 @@ class Attribute extends AppModel
} else {
foreach ($temp as $tag) {
$tag['EventTag']['Tag'] = $tag['Tag'];
unset($tag['Tag']);
$eventTags[$eventId][] = $tag['EventTag'];
}
}
}
if (!empty($eventTags)) {
foreach ($eventTags[$eventId] as $eventTag) {
$attribute['EventTag'][] = $eventTag;
}
}
$attribute['EventTag'] = $eventTags[$eventId];
return $attribute;
}
@ -2690,8 +2690,12 @@ class Attribute extends AppModel
public function setTimestampConditions($timestamp, $conditions, $scope = 'Event.timestamp', $returnRaw = false)
{
if (is_array($timestamp)) {
$timestamp[0] = intval($this->Event->resolveTimeDelta($timestamp[0]));
$timestamp[1] = intval($this->Event->resolveTimeDelta($timestamp[1]));
if (count($timestamp) !== 2) {
throw new InvalidArgumentException('Invalid date specification, must be string or array with two elements');
}
$timestamp[0] = intval($this->resolveTimeDelta($timestamp[0]));
$timestamp[1] = intval($this->resolveTimeDelta($timestamp[1]));
if ($timestamp[0] > $timestamp[1]) {
$temp = $timestamp[0];
$timestamp[0] = $timestamp[1];
@ -2700,7 +2704,7 @@ class Attribute extends AppModel
$conditions['AND'][] = array($scope . ' >=' => $timestamp[0]);
$conditions['AND'][] = array($scope . ' <=' => $timestamp[1]);
} else {
$timestamp = intval($this->Event->resolveTimeDelta($timestamp));
$timestamp = intval($this->resolveTimeDelta($timestamp));
$conditions['AND'][] = array($scope . ' >=' => $timestamp);
}
if ($returnRaw) {
@ -2712,8 +2716,8 @@ class Attribute extends AppModel
public function setTimestampSeenConditions($timestamp, $conditions, $scope = 'Attribute.first_seen', $returnRaw = false)
{
if (is_array($timestamp)) {
$timestamp[0] = intval($this->Event->resolveTimeDelta($timestamp[0])) * 1000000; // seen in stored in micro-seconds in the DB
$timestamp[1] = intval($this->Event->resolveTimeDelta($timestamp[1])) * 1000000; // seen in stored in micro-seconds in the DB
$timestamp[0] = intval($this->resolveTimeDelta($timestamp[0])) * 1000000; // seen in stored in micro-seconds in the DB
$timestamp[1] = intval($this->resolveTimeDelta($timestamp[1])) * 1000000; // seen in stored in micro-seconds in the DB
if ($timestamp[0] > $timestamp[1]) {
$temp = $timestamp[0];
$timestamp[0] = $timestamp[1];
@ -2722,7 +2726,7 @@ class Attribute extends AppModel
$conditions['AND'][] = array($scope . ' >=' => $timestamp[0]);
$conditions['AND'][] = array($scope . ' <=' => $timestamp[1]);
} else {
$timestamp = intval($this->Event->resolveTimeDelta($timestamp)) * 1000000; // seen in stored in micro-seconds in the DB
$timestamp = intval($this->resolveTimeDelta($timestamp)) * 1000000; // seen in stored in micro-seconds in the DB
if ($scope == 'Attribute.first_seen') {
$conditions['AND'][] = array($scope . ' >=' => $timestamp);
} else {

View File

@ -769,7 +769,7 @@ class Event extends AppModel
}
$correlations = array_column($correlations, $correlationModelName);
$eventIds = array_unique(array_column($correlations, 'event_id'));
$eventIds = array_unique(array_column($correlations, 'event_id'), SORT_REGULAR);
$conditions = $this->createEventConditions($user);
$conditions['Event.id'] = $eventIds;
@ -2753,7 +2753,7 @@ class Event extends AppModel
public function set_filter_published(&$params, $conditions, $options)
{
if (isset($params['published'])) {
if (isset($params['published']) && $params['published'] !== [true, false]) {
$conditions['AND']['Event.published'] = $params['published'];
}
return $conditions;
@ -3131,8 +3131,8 @@ class Event extends AppModel
{
if (Configure::read('MISP.extended_alert_subject')) {
$subject = preg_replace("/\r|\n/", "", $event['Event']['info']);
if (strlen($subject) > 58) {
$subject = substr($subject, 0, 55) . '... - ';
if (mb_strlen($subject) > 58) {
$subject = mb_substr($subject, 0, 55) . '... - ';
} else {
$subject .= " - ";
}
@ -3159,6 +3159,10 @@ class Event extends AppModel
$template->set('tlp', $subjMarkingString);
$template->subject($subject);
$template->referenceId("event-alert|{$event['Event']['id']}");
$unsubscribeLink = $this->__getAnnounceBaseurl() . '/users/unsubscribe/' . $this->User->unsubscribeCode($user);
$template->set('unsubscribe', $unsubscribeLink);
$template->listUnsubscribe($unsubscribeLink);
return $template;
}

View File

@ -1124,7 +1124,7 @@ class GalaxyCluster extends AppModel
if (!empty($tagsToFetch)) {
$tags = $this->GalaxyClusterRelation->GalaxyClusterRelationTag->Tag->find('all', [
'conditions' => ['id' => array_unique($tagsToFetch)],
'conditions' => ['id' => array_unique($tagsToFetch, SORT_REGULAR)],
'recursive' => -1,
]);
$tags = array_column(array_column($tags, 'Tag'), null, 'id');

View File

@ -205,7 +205,7 @@ class Log extends AppModel
*/
public function createLogEntry($user, $action, $model, $modelId = 0, $title = '', $change = '')
{
if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings', 'enable'], true) && Configure::read('MISP.log_new_audit')) {
if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings', 'enable', 'edit'], true) && Configure::read('MISP.log_new_audit')) {
return; // Do not store tag changes when new audit is enabled
}
if ($user === 'SYSTEM') {

View File

@ -1613,4 +1613,14 @@ class User extends AppModel
'conditions' => array('EventDelegation.org_id' => $user['org_id'])
));
}
/**
* Generate code that is used in event alert unsubscribe link.
* @return string
*/
public function unsubscribeCode(array $user)
{
$salt = Configure::read('Security.salt');
return substr(hash('sha256', "{$user['id']}|$salt"), 0, 8);
}
}

View File

@ -10,7 +10,7 @@ if (!isset($contactAlert)) {
if ($hideDetails) { // Used when GnuPG.bodyonlyencrypted is enabled and e-mail cannot be send in encrypted form
$eventUrl = $baseurl . "/events/view/" . $event['Event']['id'];
echo __("A new or modified event was just published on %s", $eventUrl) . PHP_EOL . PHP_EOL;
echo __("If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via %s", $baseurl . '/users/edit');
echo __("If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via %s", $unsubscribe);
return;
}
@ -112,5 +112,5 @@ You receive this e-mail because the e-mail address <?= $user['email'] ?> is set
to receive <?= $contactAlert ? 'contact' : 'publish' ?> alerts on the MISP instance at <?= $baseurl ?>.
If you would like to unsubscribe from receiving such alert e-mails, simply
disable <?= $contactAlert ? 'contact' : 'publish' ?> alerts via <?= $baseurl ?>/users/edit
disable <?= $contactAlert ? 'contact' : 'publish' ?> alerts via <?= $unsubscribe ?>.
==============================================

View File

@ -11,7 +11,7 @@ echo $this->element('genericElements/Form/genericForm', [
'class' => 'input span6',
'div' => 'input clear',
'type' => 'select',
'options' => array_combine($exportFormats, $exportFormats),
'options' => $exportFormats,
],
],
'submit' => [

View File

@ -4831,7 +4831,9 @@ $(document.body).on('click', 'a[data-paginator]', function (e) {
xhr({
dataType: "html",
success: function (data) {
$(paginatorTarget).html(data);
var $target = $(paginatorTarget);
destroyPopovers($target);
$target.html(data);
},
url: $(this).attr('href'),
});
@ -4849,6 +4851,12 @@ $(document.body).on('click', '[data-popover-popup]', function (e) {
popoverPopupNew(this, url);
});
function destroyPopovers($element) {
$element.find('[data-dismissid]').each(function() {
$(this).popover('destroy');
});
}
function queryEventLock(event_id, timestamp) {
var interval = null;
var errorCount = 0;

View File

@ -797,6 +797,24 @@ class TestComprehensive(unittest.TestCase):
self.assertTrue(created_user.autoalert, created_user)
self.admin_misp_connector.delete_user(created_user)
def test_search_snort_suricata(self):
event = create_simple_event()
event.add_attribute('ip-src', '8.8.8.8', to_ids=True)
event = self.user_misp_connector.add_event(event)
check_response(event)
self.admin_misp_connector.publish(event, alert=False)
snort = self._search({'returnFormat': 'snort', 'eventid': event.id})
self.assertIsInstance(snort, str)
self.assertIn('8.8.8.8', snort)
suricata = self._search({'returnFormat': 'suricata', 'eventid': event.id})
self.assertIsInstance(suricata, str)
self.assertIn('8.8.8.8', suricata)
self.admin_misp_connector.delete_event(event)
def _search(self, query: dict):
response = self.admin_misp_connector._prepare_request('POST', 'events/restSearch', data=query)
response = self.admin_misp_connector._check_response(response)