mirror of https://github.com/MISP/MISP
Merge branch '2.4' of github.com:MISP/MISP into 2.4
commit
9768fc9bcc
|
@ -48,7 +48,7 @@ class AppController extends Controller
|
|||
|
||||
public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
|
||||
|
||||
private $__queryVersion = '113';
|
||||
private $__queryVersion = '114';
|
||||
public $pyMispVersion = '2.4.133';
|
||||
public $phpmin = '7.2';
|
||||
public $phprec = '7.4';
|
||||
|
|
|
@ -159,6 +159,10 @@ class ACLComponent extends Component
|
|||
'restore' => array('perm_add'),
|
||||
'index' => array('*'),
|
||||
'getProxyMISPElements' => array('*'),
|
||||
'extractAllFromReport' => array('*'),
|
||||
'extractFromReport' => array('*'),
|
||||
'replaceSuggestionInReport' => array('*'),
|
||||
'importReportFromUrl' => array('*'),
|
||||
),
|
||||
'events' => array(
|
||||
'add' => array('perm_add'),
|
||||
|
|
|
@ -185,11 +185,138 @@ class EventReportsController extends AppController
|
|||
$this->__injectIndexVariablesToViewContext($filters);
|
||||
if (!empty($filters['index_for_event'])) {
|
||||
$this->set('extendedEvent', !empty($filters['extended_event']));
|
||||
$fetcherModule = $this->EventReport->isFetchURLModuleEnabled();
|
||||
$this->set('importModuleEnabled', is_array($fetcherModule));
|
||||
$this->render('ajax/indexForEvent');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function extractAllFromReport($reportId)
|
||||
{
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
|
||||
} else {
|
||||
if ($this->request->is('post')) {
|
||||
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $reportId, 'edit', $throwErrors=true, $full=false);
|
||||
$results = $this->EventReport->getComplexTypeToolResultWithReplacements($this->Auth->user(), $report);
|
||||
$report['EventReport']['content'] = $results['replacementResult']['contentWithReplacements'];
|
||||
$contextResults = $this->EventReport->extractWithReplacements($this->Auth->user(), $report, ['replace' => true]);
|
||||
$suggestionResult = $this->EventReport->transformFreeTextIntoSuggestion($contextResults['contentWithReplacements'], $results['complexTypeToolResult']);
|
||||
$errors = $this->EventReport->applySuggestions($this->Auth->user(), $report, $suggestionResult['contentWithSuggestions'], $suggestionResult['suggestionsMapping']);
|
||||
if (empty($errors)) {
|
||||
if (!empty($this->data['EventReport']['tag_event'])) {
|
||||
$this->EventReport->attachTagsAfterReplacements($this->Auth->User(), $contextResults['replacedContext'], $report['EventReport']['event_id']);
|
||||
}
|
||||
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
|
||||
$data = [ 'report' => $report ];
|
||||
$successMessage = __('Automatic extraction applied to Event Report %s', $reportId);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, $data, 'applySuggestions', $reportId);
|
||||
} else {
|
||||
$errorMessage = __('Automatic extraction could not be applied to Event Report %s.%sReasons: %s', $reportId, PHP_EOL, json_encode($errors));
|
||||
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'applySuggestions', $reportId);
|
||||
}
|
||||
}
|
||||
$this->layout = 'ajax';
|
||||
$this->set('reportId', $reportId);
|
||||
$this->render('ajax/extractAllFromReport');
|
||||
}
|
||||
}
|
||||
|
||||
public function extractFromReport($reportId)
|
||||
{
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
|
||||
} else {
|
||||
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $reportId, 'view', $throwErrors=true, $full=false);
|
||||
$dataResults = $this->EventReport->getComplexTypeToolResultWithReplacements($this->Auth->user(), $report);
|
||||
$report['EventReport']['content'] = $dataResults['replacementResult']['contentWithReplacements'];
|
||||
$contextResults = $this->EventReport->extractWithReplacements($this->Auth->user(), $report);
|
||||
$typeToCategoryMapping = $this->EventReport->Event->Attribute->typeToCategoryMapping();
|
||||
$data = [
|
||||
'complexTypeToolResult' => $dataResults['complexTypeToolResult'],
|
||||
'typeToCategoryMapping' => $typeToCategoryMapping,
|
||||
'replacementValues' => $dataResults['replacementResult']['replacedValues'],
|
||||
'replacementContext' => $contextResults['replacedContext']
|
||||
];
|
||||
return $this->RestResponse->viewData($data, $this->response->type());
|
||||
}
|
||||
}
|
||||
|
||||
public function replaceSuggestionInReport($reportId)
|
||||
{
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
|
||||
} else {
|
||||
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $reportId, 'edit', $throwErrors=true, $full=false);
|
||||
if ($this->request->is('post')) {
|
||||
$errors = [];
|
||||
$suggestions = $this->EventReport->jsonDecode($this->data['EventReport']['suggestions']);
|
||||
if (!empty($suggestions['content']) && !empty($suggestions['mapping'])) {
|
||||
$errors = $this->EventReport->applySuggestions($this->Auth->user(), $report, $suggestions['content'], $suggestions['mapping']);
|
||||
} else {
|
||||
$errors[] = __('`content` and `mapping` key cannot be empty');
|
||||
}
|
||||
if (empty($errors)) {
|
||||
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $reportId);
|
||||
$results = $this->EventReport->getComplexTypeToolResultWithReplacements($this->Auth->user(), $report);
|
||||
$contextResults = $this->EventReport->extractWithReplacements($this->Auth->user(), $report);
|
||||
$data = [
|
||||
'report' => $report,
|
||||
'complexTypeToolResult' => $results['complexTypeToolResult'],
|
||||
'replacementValues' => $results['replacementResult']['replacedValues'],
|
||||
'replacementContext' => $contextResults['replacedContext']
|
||||
];
|
||||
$successMessage = __('Suggestions applied to Event Report %s', $reportId);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, $data, 'applySuggestions', $reportId);
|
||||
} else {
|
||||
$errorMessage = __('Suggestions could not be applied to Event Report %s.%sReasons: %s', $reportId, PHP_EOL, json_encode($errors));
|
||||
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'applySuggestions', $reportId);
|
||||
}
|
||||
}
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/replaceSuggestionInReport');
|
||||
}
|
||||
}
|
||||
|
||||
public function importReportFromUrl($event_id)
|
||||
{
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
|
||||
}
|
||||
$fetcherModule = $this->EventReport->isFetchURLModuleEnabled();
|
||||
if ($this->request->is('post')) {
|
||||
if (empty($this->data['EventReport']['url'])) {
|
||||
throw new MethodNotAllowedException(__('An URL must be provided'));
|
||||
}
|
||||
$url = $this->data['EventReport']['url'];
|
||||
$markdown = $this->EventReport->downloadMarkdownFromURL($event_id, $url);
|
||||
$errors = [];
|
||||
if (!empty($markdown)) {
|
||||
$report = [
|
||||
'name' => __('Report from - %s (%s)', $url, time()),
|
||||
'distribution' => 5,
|
||||
'content' => $markdown
|
||||
];
|
||||
$errors = $this->EventReport->addReport($this->Auth->user(), $report, $event_id);
|
||||
} else {
|
||||
$errors[] = __('Could not fetch report from URL. Fetcher module not enabled or could not download the page');
|
||||
}
|
||||
$redirectTarget = array('controller' => 'events', 'action' => 'view', $event_id);
|
||||
if (!empty($errors)) {
|
||||
return $this->__getFailResponseBasedOnContext($errors, array(), 'addFromURL', $this->EventReport->id, $redirectTarget);
|
||||
} else {
|
||||
$successMessage = __('Report downloaded and created');
|
||||
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $this->EventReport->id);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'addFromURL', false, $redirectTarget);
|
||||
}
|
||||
}
|
||||
$this->set('importModuleEnabled', is_array($fetcherModule));
|
||||
$this->set('event_id', $event_id);
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/importReportFromUrl');
|
||||
}
|
||||
|
||||
private function __generateIndexConditions($filters = [])
|
||||
{
|
||||
$aclConditions = $this->EventReport->buildACLConditions($this->Auth->user());
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
class ButtonWidget
|
||||
{
|
||||
public $title = 'Button Widget';
|
||||
public $render = 'Button';
|
||||
public $width = 3;
|
||||
public $height = 2;
|
||||
public $cacheLifetime = false;
|
||||
public $autoRefreshDelay = false;
|
||||
public $params = array(
|
||||
'url' => 'URL (after base url) to redirect to',
|
||||
'text' => 'Text to display on the button'
|
||||
);
|
||||
public $description = 'Simple button to allow shortcuts';
|
||||
public $placeholder =
|
||||
'{
|
||||
"url": "/events/index",
|
||||
"text": "Go to events"
|
||||
}';
|
||||
|
||||
public function handler($user, $options = array())
|
||||
{
|
||||
$data = array();
|
||||
if(isset($options['url'])) {
|
||||
$data['url'] = $options['url'];
|
||||
}
|
||||
if(isset($options['text'])) {
|
||||
$data['text'] = $options['text'];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -279,7 +279,7 @@ class SendEmail
|
|||
*/
|
||||
public function sendExternal(array $params)
|
||||
{
|
||||
foreach (array('body', 'reply-to', 'to', 'subject', 'text') as $requiredParam) {
|
||||
foreach (array('body', 'reply-to', 'to', 'subject') as $requiredParam) {
|
||||
if (!isset($params[$requiredParam])) {
|
||||
throw new InvalidArgumentException("Param '$requiredParam' is required, but not provided.");
|
||||
}
|
||||
|
|
|
@ -4779,4 +4779,18 @@ class Attribute extends AppModel
|
|||
{
|
||||
return (is_int($value) && $value >= 0) || ctype_digit($value);
|
||||
}
|
||||
|
||||
public function typeToCategoryMapping()
|
||||
{
|
||||
$typeCategoryMapping = array();
|
||||
foreach ($this->categoryDefinitions as $k => $cat) {
|
||||
foreach ($cat['types'] as $type) {
|
||||
$typeCategoryMapping[$type][$k] = $k;
|
||||
}
|
||||
}
|
||||
foreach ($typeCategoryMapping as $k => $v) {
|
||||
$typeCategoryMapping[$k] = array_values($v);
|
||||
}
|
||||
return $typeCategoryMapping;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ class EventReport extends AppModel
|
|||
'userKey' => 'user_id',
|
||||
'change' => 'full'
|
||||
),
|
||||
'Regexp' => array('fields' => array('value')),
|
||||
);
|
||||
|
||||
public $validate = array(
|
||||
|
@ -519,4 +520,380 @@ class EventReport extends AppModel
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function applySuggestions($user, $report, $contentWithSuggestions, $suggestionsMapping) {
|
||||
$errors = [];
|
||||
$replacedContent = $contentWithSuggestions;
|
||||
$success = 0;
|
||||
foreach ($suggestionsMapping as $value => $suggestedAttribute) {
|
||||
$suggestedAttribute['value'] = $value;
|
||||
$savedAttribute = $this->createAttributeFromSuggestion($user, $report, $suggestedAttribute);
|
||||
if (empty($savedAttribute['errors'])) {
|
||||
$success++;
|
||||
$replacedContent = $this->applySuggestionsInText($replacedContent, $savedAttribute['attribute'], $value);
|
||||
} else {
|
||||
$replacedContent = $this->revertToOriginalInText($replacedContent, $value);
|
||||
$errors[] = $savedAttribute['errors'];
|
||||
}
|
||||
}
|
||||
if ($success > 0 || count($suggestionsMapping) == 0) {
|
||||
$report['EventReport']['content'] = $replacedContent;
|
||||
$editErrors = $this->editReport($user, $report, $report['EventReport']['event_id']);
|
||||
if (!empty($editErrors)) {
|
||||
$errors[] = $editErrors;
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function applySuggestionsInText($contentWithSuggestions, $attribute, $value)
|
||||
{
|
||||
$textToBeReplaced = sprintf('@[suggestion](%s)', $value);
|
||||
$textToInject = sprintf('@[attribute](%s)', $attribute['Attribute']['uuid']);
|
||||
$replacedContent = str_replace($textToBeReplaced, $textToInject, $contentWithSuggestions);
|
||||
return $replacedContent;
|
||||
}
|
||||
|
||||
public function revertToOriginalInText($contentWithSuggestions, $value)
|
||||
{
|
||||
$textToBeReplaced = sprintf('@[suggestion](%s)', $value);
|
||||
$textToInject = $value;
|
||||
$replacedContent = str_replace($textToBeReplaced, $textToInject, $contentWithSuggestions);
|
||||
return $replacedContent;
|
||||
}
|
||||
|
||||
private function createAttributeFromSuggestion($user, $report, $suggestedAttribute)
|
||||
{
|
||||
$errors = [];
|
||||
$attribute = [
|
||||
'event_id' => $report['EventReport']['event_id'],
|
||||
'distribution' => 5,
|
||||
'category' => $suggestedAttribute['category'],
|
||||
'type' => $suggestedAttribute['type'],
|
||||
'value' => $suggestedAttribute['value'],
|
||||
'to_ids' => $suggestedAttribute['to_ids'],
|
||||
];
|
||||
$validationErrors = array();
|
||||
$this->Event->Attribute->captureAttribute($attribute, $report['EventReport']['event_id'], $user, false, false, false, $validationErrors);
|
||||
$savedAttribute = false;
|
||||
if (!empty($validationErrors)) {
|
||||
$errors = $validationErrors;
|
||||
} else {
|
||||
$savedAttribute = $this->Event->Attribute->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Attribute.id' => $this->Event->Attribute->id),
|
||||
));
|
||||
}
|
||||
return [
|
||||
'errors' => $errors,
|
||||
'attribute' => $savedAttribute
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* transformFreeTextIntoReplacement
|
||||
*
|
||||
* @param array $user
|
||||
* @param array $report
|
||||
* @param array $complexTypeToolResult Uses the complex type tool output to support import regex replacements.
|
||||
* Another solution would be to run the regex replacement on each token of the report which is too heavy
|
||||
* @return array
|
||||
*/
|
||||
public function transformFreeTextIntoReplacement(array $user, array $report, array $complexTypeToolResult)
|
||||
{
|
||||
$complexTypeToolResultWithImportRegex = $this->injectImportRegexOnComplexTypeToolResult($complexTypeToolResult);
|
||||
$valueToValueWithRegex = Hash::combine($complexTypeToolResultWithImportRegex, '{n}.valueWithImportRegex', '{n}.value');
|
||||
$proxyElements = $this->getProxyMISPElements($user, $report['EventReport']['event_id']);
|
||||
$originalContent = $report['EventReport']['content'];
|
||||
$content = $originalContent;
|
||||
$replacedValues = [];
|
||||
foreach ($proxyElements['attribute'] as $uuid => $attribute) {
|
||||
$count = 0;
|
||||
$textToInject = sprintf('@[attribute](%s)', $uuid);
|
||||
$content = str_replace($attribute['value'], $textToInject, $content, $count);
|
||||
if ($count > 0 || strpos($originalContent, $attribute['value'])) { // Check if the value has been replaced by the first match
|
||||
if (!isset($replacedValues[$attribute['value']])) {
|
||||
$replacedValues[$attribute['value']] = [
|
||||
'attributeUUIDs' => [$uuid],
|
||||
'valueInReport' => $attribute['value'],
|
||||
];
|
||||
} else {
|
||||
$replacedValues[$attribute['value']]['attributeUUIDs'][] = $uuid;
|
||||
}
|
||||
$count = 0;
|
||||
}
|
||||
if (isset($valueToValueWithRegex[$attribute['value']]) && $valueToValueWithRegex[$attribute['value']] != $attribute['value']) {
|
||||
$content = str_replace($valueToValueWithRegex[$attribute['value']], $textToInject, $content, $count);
|
||||
if ($count > 0 || strpos($originalContent, $valueToValueWithRegex[$attribute['value']])) {
|
||||
if (!isset($replacedValues[$attribute['value']])) {
|
||||
$replacedValues[$attribute['value']] = [
|
||||
'attributeUUIDs' => [$uuid],
|
||||
'valueInReport' => $valueToValueWithRegex[$attribute['value']],
|
||||
];
|
||||
} else {
|
||||
$replacedValues[$attribute['value']]['attributeUUIDs'][] = $uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [
|
||||
'contentWithReplacements' => $content,
|
||||
'replacedValues' => $replacedValues
|
||||
];
|
||||
}
|
||||
|
||||
public function transformFreeTextIntoSuggestion($content, $complexTypeToolResult)
|
||||
{
|
||||
$replacedContent = $content;
|
||||
$suggestionsMapping = [];
|
||||
$typeToCategoryMapping = $this->Event->Attribute->typeToCategoryMapping();
|
||||
foreach ($complexTypeToolResult as $i => $complexTypeToolEntry) {
|
||||
$textToBeReplaced = $complexTypeToolEntry['value'];
|
||||
$textToInject = sprintf('@[suggestion](%s)', $textToBeReplaced);
|
||||
$suggestionsMapping[$textToBeReplaced] = [
|
||||
'category' => $typeToCategoryMapping[$complexTypeToolEntry['default_type']][0],
|
||||
'type' => $complexTypeToolEntry['default_type'],
|
||||
'value' => $textToBeReplaced,
|
||||
'to_ids' => $complexTypeToolEntry['to_ids'],
|
||||
];
|
||||
$replacedContent = str_replace($textToBeReplaced, $textToInject, $replacedContent);
|
||||
}
|
||||
return [
|
||||
'contentWithSuggestions' => $replacedContent,
|
||||
'suggestionsMapping' => $suggestionsMapping
|
||||
];
|
||||
}
|
||||
|
||||
public function injectImportRegexOnComplexTypeToolResult($complexTypeToolResult) {
|
||||
foreach ($complexTypeToolResult as $i => $complexTypeToolEntry) {
|
||||
$transformedValue = $this->runRegexp($complexTypeToolEntry['default_type'], $complexTypeToolEntry['value']);
|
||||
if ($transformedValue !== false) {
|
||||
$complexTypeToolResult[$i]['valueWithImportRegex'] = $transformedValue;
|
||||
}
|
||||
}
|
||||
return $complexTypeToolResult;
|
||||
}
|
||||
|
||||
public function getComplexTypeToolResultFromReport($content)
|
||||
{
|
||||
App::uses('ComplexTypeTool', 'Tools');
|
||||
$complexTypeTool = new ComplexTypeTool();
|
||||
$this->Warninglist = ClassRegistry::init('Warninglist');
|
||||
$complexTypeTool->setTLDs($this->Warninglist->fetchTLDLists());
|
||||
$complexTypeToolResult = $complexTypeTool->checkComplexRouter($content, 'freetext');
|
||||
return $complexTypeToolResult;
|
||||
}
|
||||
|
||||
public function getComplexTypeToolResultWithReplacements($user, $report)
|
||||
{
|
||||
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($report['EventReport']['content']);
|
||||
$replacementResult = $this->transformFreeTextIntoReplacement($user, $report, $complexTypeToolResult);
|
||||
$complexTypeToolResult = $this->getComplexTypeToolResultFromReport($replacementResult['contentWithReplacements']);
|
||||
return [
|
||||
'complexTypeToolResult' => $complexTypeToolResult,
|
||||
'replacementResult' => $replacementResult,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* extractWithReplacements Extract context information from report with special care for ATT&CK
|
||||
*
|
||||
* @param array $user
|
||||
* @param array $report
|
||||
* @return array
|
||||
*/
|
||||
public function extractWithReplacements(array $user, array $report, array $options = [])
|
||||
{
|
||||
$baseOptions = [
|
||||
'replace' => false,
|
||||
'tags' => true,
|
||||
'synonyms' => true,
|
||||
'synonyms_min_characters' => 4,
|
||||
'prune_deprecated' => true,
|
||||
'attack' => true,
|
||||
];
|
||||
$options = array_merge($baseOptions, $options);
|
||||
$originalContent = $report['EventReport']['content'];
|
||||
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
|
||||
$mitreAttackGalaxyId = $this->GalaxyCluster->Galaxy->getMitreAttackGalaxyId();
|
||||
$clusterContain = ['Tag'];
|
||||
$replacedContext = [];
|
||||
|
||||
if ($options['prune_deprecated']) {
|
||||
$clusterContain['Galaxy'] = ['conditions' => ['Galaxy.namespace !=' => 'deprecated']];
|
||||
}
|
||||
if ($options['synonyms']) {
|
||||
$clusterContain['GalaxyElement'] = ['conditions' => ['GalaxyElement.key' => 'synonyms']];
|
||||
}
|
||||
$clusterConditions = [];
|
||||
if ($options['attack']) {
|
||||
$clusterConditions = ['GalaxyCluster.galaxy_id !=' => $mitreAttackGalaxyId];
|
||||
}
|
||||
$clusters = $this->GalaxyCluster->find('all', [
|
||||
'conditions' => $clusterConditions,
|
||||
'contain' => $clusterContain
|
||||
]);
|
||||
|
||||
if ($options['tags']) {
|
||||
$this->Tag = ClassRegistry::init('Tag');
|
||||
$tags = $this->Tag->fetchUsableTags($user);
|
||||
foreach ($tags as $i => $tag) {
|
||||
$tagName = $tag['Tag']['name'];
|
||||
$found = $this->isValidReplacementTag($originalContent, $tagName);
|
||||
if ($found) {
|
||||
$replacedContext[$tagName][$tagName] = $tag['Tag'];
|
||||
} else {
|
||||
$tagNameUpper = strtoupper($tagName);
|
||||
$found = $this->isValidReplacementTag($originalContent, $tagNameUpper);
|
||||
if ($found) {
|
||||
$replacedContext[$tagNameUpper][$tagName] = $tag['Tag'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($clusters as $i => $cluster) {
|
||||
$cluster['GalaxyCluster']['colour'] = '#0088cc';
|
||||
$tagName = $cluster['GalaxyCluster']['tag_name'];
|
||||
$found = $this->isValidReplacementTag($originalContent, $tagName);
|
||||
if ($found) {
|
||||
$replacedContext[$tagName][$tagName] = $cluster['GalaxyCluster'];
|
||||
}
|
||||
$toSearch = ' ' . $cluster['GalaxyCluster']['value'] . ' ';
|
||||
$found = strpos($originalContent, $toSearch) !== false;
|
||||
if ($found) {
|
||||
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
|
||||
}
|
||||
if ($options['synonyms']) {
|
||||
foreach ($cluster['GalaxyElement'] as $j => $element) {
|
||||
if (strlen($element['value']) >= $options['synonyms_min_characters']) {
|
||||
$toSearch = ' ' . $element['value'] . ' ';
|
||||
$found = strpos($originalContent, $toSearch) !== false;
|
||||
if ($found) {
|
||||
$replacedContext[$element['value']][$tagName] = $cluster['GalaxyCluster'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['attack']) {
|
||||
unset($clusterContain['Galaxy']);
|
||||
$attackClusters = $this->GalaxyCluster->find('all', [
|
||||
'conditions' => ['GalaxyCluster.galaxy_id' => $mitreAttackGalaxyId],
|
||||
'contain' => $clusterContain
|
||||
]);
|
||||
foreach ($attackClusters as $i => $cluster) {
|
||||
$cluster['GalaxyCluster']['colour'] = '#0088cc';
|
||||
$tagName = $cluster['GalaxyCluster']['tag_name'];
|
||||
$toSearch = ' ' . $cluster['GalaxyCluster']['value'] . ' ';
|
||||
$found = strpos($originalContent, $toSearch) !== false;
|
||||
if ($found) {
|
||||
$replacedContext[$cluster['GalaxyCluster']['value']][$tagName] = $cluster['GalaxyCluster'];
|
||||
} else {
|
||||
$clusterParts = explode(' - ', $cluster['GalaxyCluster']['value'], 2);
|
||||
$toSearch = ' ' . $clusterParts[0] . ' ';
|
||||
$found = strpos($originalContent, $toSearch) !== false;
|
||||
if ($found) {
|
||||
$replacedContext[$clusterParts[0]][$tagName] = $cluster['GalaxyCluster'];
|
||||
} else {
|
||||
$toSearch = ' ' . $clusterParts[1] . ' ';
|
||||
$found = strpos($originalContent, $toSearch) !== false;
|
||||
if ($found) {
|
||||
$replacedContext[$clusterParts[1]][$tagName] = $cluster['GalaxyCluster'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$toReturn = [
|
||||
'replacedContext' => $replacedContext
|
||||
];
|
||||
if ($options['replace']) {
|
||||
$content = $originalContent;
|
||||
foreach ($replacedContext as $rawText => $replacements) {
|
||||
// Replace with first one until a better strategy is found
|
||||
reset($replacements);
|
||||
$replacement = key($replacements);
|
||||
$textToInject = sprintf('@[tag](%s)', $replacement);
|
||||
$content = str_replace($rawText, $textToInject, $content);
|
||||
}
|
||||
$toReturn['contentWithReplacements'] = $content;
|
||||
}
|
||||
return $toReturn;
|
||||
}
|
||||
|
||||
public function downloadMarkdownFromURL($event_id, $url)
|
||||
{
|
||||
$this->Module = ClassRegistry::init('Module');
|
||||
$module = $this->isFetchURLModuleEnabled();
|
||||
if (!is_array($module)) {
|
||||
return false;
|
||||
}
|
||||
$modulePayload = [
|
||||
'module' => $module['name'],
|
||||
'event_id' => $event_id,
|
||||
'url' => $url
|
||||
];
|
||||
$module = $this->isFetchURLModuleEnabled();
|
||||
if (!empty($module)) {
|
||||
$result = $this->Module->queryModuleServer('/query', $modulePayload, false);
|
||||
if (empty($result['results'][0]['values'][0])) {
|
||||
return '';
|
||||
}
|
||||
return $result['results'][0]['values'][0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isFetchURLModuleEnabled() {
|
||||
$this->Module = ClassRegistry::init('Module');
|
||||
$module = $this->Module->getEnabledModule('html_to_markdown', 'expansion');
|
||||
return !empty($module) ? $module : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* findValidReplacementTag Search if tagName is in content and is not wrapped in a tag reference
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $tagName
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidReplacementTag($content, $tagName)
|
||||
{
|
||||
$lastIndex = 0;
|
||||
$allIndices = [];
|
||||
$toSearch = strpos($tagName, ':') === false ? ' ' . $tagName . ' ' : $tagName;
|
||||
while (($lastIndex = strpos($content, $toSearch, $lastIndex)) !== false) {
|
||||
$allIndices[] = $lastIndex;
|
||||
$lastIndex = $lastIndex + strlen($toSearch);
|
||||
}
|
||||
if (empty($allIndices)) {
|
||||
return false;
|
||||
} else {
|
||||
$wrapper = '@[tag](';
|
||||
foreach ($allIndices as $i => $index) {
|
||||
$stringBeforeTag = substr($content, $index - strlen($wrapper), strlen($wrapper));
|
||||
if ($stringBeforeTag != $wrapper) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function attachTagsAfterReplacements($user, $replacedContext, $eventId)
|
||||
{
|
||||
$this->EventTag = ClassRegistry::init('EventTag');
|
||||
foreach ($replacedContext as $rawText => $tagNames) {
|
||||
// Replace with first one until a better strategy is found
|
||||
reset($tagNames);
|
||||
$tagName = key($tagNames);
|
||||
$tagId = $this->EventTag->Tag->lookupTagIdFromName($tagName);
|
||||
if ($tagId === -1) {
|
||||
$tagId = $this->EventTag->Tag->captureTag(['name' => $tagName], $user);
|
||||
}
|
||||
$this->EventTag->attachTagToEvent($eventId, $tagId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ class Module extends AppModel
|
|||
return 'The requested module is not enabled.';
|
||||
}
|
||||
if (is_array($modules)) {
|
||||
foreach ($modules['modules'] as $module) {
|
||||
foreach ($modules as $module) {
|
||||
if ($module['name'] == $name) {
|
||||
if ($type && in_array(strtolower($type), $module['meta']['module-type'])) {
|
||||
return $module;
|
||||
|
|
|
@ -4,6 +4,10 @@ App::uses('GpgTool', 'Tools');
|
|||
|
||||
class Server extends AppModel
|
||||
{
|
||||
const SETTING_CRITICAL = 0,
|
||||
SETTING_RECOMMENDED = 1,
|
||||
SETTING_OPTIONAL = 2;
|
||||
|
||||
public $name = 'Server';
|
||||
|
||||
public $actsAs = array('SysLogLogable.SysLogLogable' => array(
|
||||
|
@ -1100,7 +1104,33 @@ class Server extends AppModel
|
|||
'test' => 'testBool',
|
||||
'type' => 'boolean',
|
||||
'null' => true
|
||||
)
|
||||
),
|
||||
'attachment_scan_module' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('Name of enrichment module that will be used for attachment malware scanning. This module must return av-signature or sb-signature object.'),
|
||||
'value' => '',
|
||||
'errorMessage' => '',
|
||||
'type' => 'string',
|
||||
'null' => true,
|
||||
],
|
||||
'attachment_scan_hash_only' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('Send to attachment scan module just file hash. This can be useful if module sends attachment to remote service and you don\'t want to leak real data.'),
|
||||
'value' => false,
|
||||
'errorMessage' => '',
|
||||
'test' => 'testBool',
|
||||
'type' => 'boolean',
|
||||
'null' => true,
|
||||
],
|
||||
'attachment_scan_timeout' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('How long to wait for scan results in seconds.'),
|
||||
'value' => 30,
|
||||
'errorMessage' => '',
|
||||
'test' => 'testForPositiveInteger',
|
||||
'type' => 'numeric',
|
||||
'null' => true,
|
||||
]
|
||||
),
|
||||
'GnuPG' => array(
|
||||
'branch' => 1,
|
||||
|
@ -3443,6 +3473,14 @@ class Server extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
public function testForPositiveInteger($value)
|
||||
{
|
||||
if ((is_int($value) && $value >= 0) || ctype_digit($value)) {
|
||||
return true;
|
||||
}
|
||||
return __('The value has to be a whole number greater or equal 0.');
|
||||
}
|
||||
|
||||
public function testForCookieTimeout($value)
|
||||
{
|
||||
$numeric = $this->testForNumeric($value);
|
||||
|
|
|
@ -5,6 +5,72 @@ This plugin enables CakePHP applications to use Single Sign-On to authenticate i
|
|||
|
||||
## Usage
|
||||
|
||||
### Prerequisites - Shibboleth Service Provider
|
||||
The MISP plugin takes care of the mapping of your shibboleth session attributes to MISP, but you will still need to install the service provider (SP) and configure it yourself. The documentation for Shibboleth Service Provider 3 can be found at https://wiki.shibboleth.net/confluence/display/SP3/Home.
|
||||
|
||||
To install Shibboleth SP3 on Ubuntu, you can use the instructions provided by SWITCH at https://www.switch.ch/aai/guides/sp/installation/ and then follow the below steps. If you already installed and configured Shibboleth you can skip this section.
|
||||
|
||||
Create signing and encryption certificate. The value following -e should be your entity ID, for example https://<host>/shibboleth.
|
||||
```bash
|
||||
sudo shib-keygen -f -u _shibd -h <host> -y 5 -e https://<host>/shibboleth -o /etc/shibboleth
|
||||
```
|
||||
|
||||
Edit /etc/shibboleth/shibboleth2.xml to use the created certificate for both signing and encryption (change the values for key and certificate).
|
||||
```xml
|
||||
<CredentialResolver type="File" use="signing"
|
||||
key="sp-key.pem" certificate="sp-cert.pem"/>
|
||||
<CredentialResolver type="File" use="encryption"
|
||||
key="sp-key.pem" certificate="sp-cert.pem"/>
|
||||
```
|
||||
|
||||
Edit /etc/shibboleth/shibboleth2.xml to set secure cookie properties (cookieProps) if you want to.
|
||||
```xml
|
||||
<Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
|
||||
checkAddress="false" handlerSSL="false" cookieProps="https"
|
||||
redirectLimit="exact">
|
||||
```
|
||||
|
||||
At this point, you should already be able to test your configuration. The last line of the output should be "overall configuration is loadable, check console for non-fatal problems".
|
||||
```bash
|
||||
sudo shibd -t
|
||||
```
|
||||
|
||||
Set entityID in /etc/shibboleth/shibboleth2.xml.
|
||||
```xml
|
||||
<ApplicationDefaults entityID="https://<host>/shibboleth"
|
||||
REMOTE_USER="eppn subject-id pairwise-id persistent-id"
|
||||
cipherSuites="DEFAULT:!EXP:!LOW:!aNULL:!eNULL:!DES:!IDEA:!SEED:!RC4:!3DES:!kRSA:!SSLv2:!SSLv3:!TLSv1:!TLSv1.1">
|
||||
```
|
||||
|
||||
Copy your identity provider metadata to /etc/shibboleth, for example to /etc/shibboleth/idp-metadata.xml and refer to it in /etc/shibboleth/shibboleth2.xml. Uncomment and edit the relevant line.
|
||||
```xml
|
||||
<MetadataProvider type="XML" validate="true" path="idp-metadata.xml"/>
|
||||
```
|
||||
|
||||
Optionally, you can make sure the service provider does not create a session if some attributes, like OrgTag and GroupTag are missing. If users attempt to login an this happens, they will receive a pre-configured reply (default at /etc/shibboleth/attrChecker.html).
|
||||
In /etc/shibboleth/shibboleth2.xml, edit ApplicationDefaults by adding the sessionHook:
|
||||
```xml
|
||||
<ApplicationDefaults entityID="https://<HOST>/shibboleth"
|
||||
REMOTE_USER="eppn persistent-id targeted-id"
|
||||
signing="front" encryption="false"
|
||||
sessionHook="/Shibboleth.sso/AttrChecker"
|
||||
```
|
||||
Optional for attribute checking: add your checks (note that the incoming attribute names can be different for you, for more info on possible checks refer to https://wiki.shibboleth.net/confluence/display/SP3/Attribute+Checker+Handler):
|
||||
```xml
|
||||
<Handler type="AttributeChecker" Location="/AttrChecker" template="attrChecker.html" attributes="OrgTag GroupTag" flushSession="true"/>
|
||||
```
|
||||
|
||||
At this point you will have to send your metadata to your identity provider. You can get template metadata based on your configuration from https://<host>/Shibboleth.sso/Metadata.
|
||||
|
||||
### MISP plugin configuration
|
||||
|
||||
Edit your MISP apache configuration by adding the below (location depends on your handler path, /Shibboleth.sso by default).
|
||||
```Apache
|
||||
<Location /Shibboleth.sso>
|
||||
SetHandler shib
|
||||
</Locations>
|
||||
```
|
||||
|
||||
Enable the plugin at bootstrap.php:
|
||||
|
||||
```php
|
||||
|
@ -18,43 +84,68 @@ Uncomment the following line to enable SSO authorization
|
|||
'auth'=>array('ShibbAuth.ApacheShibb'),
|
||||
```
|
||||
|
||||
And configure it. MailTag, OrgTag and GroupTag are the string that represent the key for the values needed by the plugin.
|
||||
For example if you are using ADFS OrgTag will be ADFS_FEDERATION, GroupTag will be ADFS_GROUP, etc. meaning the key for the values needed.
|
||||
DefaultOrg are values that come by default just in case they are not defined or obtained from the environment variables.
|
||||
The GroupRoleMatching is an array that allows the definition and correlation between groups and roles in MISP, being them updated
|
||||
If the line does not exist, add it to 'Security' array, for example like below. Note that you should just add the line to your own existing config.
|
||||
```php
|
||||
'Security' =>
|
||||
array (
|
||||
'level' => 'medium',
|
||||
'salt' => '',
|
||||
'cipherSeed' => '',
|
||||
'password_policy_length' => 12,
|
||||
'password_policy_complexity' => '/^((?=.*\\d)|(?=.*\\W+))(?![\\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/',
|
||||
'self_registration_message' => 'If you would like to send us a registration request, please fill out the form below. Make sure you fill out as much information as possible in order to ease the task of the administrators.',
|
||||
'auth'=>array('ShibbAuth.ApacheShibb'),
|
||||
)
|
||||
```
|
||||
|
||||
And configure it. MailTag, OrgTag and GroupTag are the keys for the values needed by the plugin.
|
||||
For example if you are using ADFS you should replace IDP_FEDERATION_TAG by ADFS_FEDERATION, IDP_GROUP_TAG by ADFS_GROUP, etc.
|
||||
Replace MISP_DEFAULT_ORG by the organization you want users to be assigned to in case no organization value is given by the identity provider.
|
||||
The GroupRoleMatching is an array that allows the definition and correlation between groups and roles in MISP. These get updated
|
||||
if the groups are updated (i.e. a user that was admin and their groups changed inside the organization will have his role changed in MISP
|
||||
upon the next login being now user or org admin respectively). The GroupSeparator is the character used to separate the different groups
|
||||
in the list given by apache.
|
||||
in the list given by apache. By default, you can leave it at ';'.
|
||||
|
||||
```php
|
||||
'ApacheShibbAuth' => // Configuration for shibboleth authentication
|
||||
array(
|
||||
'MailTag' => 'EMAIL_TAG',
|
||||
'OrgTag' => 'FEDERATION_TAG',
|
||||
'GroupTag' => 'GROUP_TAG',
|
||||
'MailTag' => 'IDP_EMAIL_TAG',
|
||||
'OrgTag' => 'IDP_FEDERATION_TAG',
|
||||
'GroupTag' => 'IDP_GROUP_TAG',
|
||||
'GroupSeparator' => ';',
|
||||
'GroupRoleMatching' => array( // 3:User, 1:admin. May be good to set "1" for the first user
|
||||
'group_three' => '3',
|
||||
'group_two' => 2,
|
||||
'group_one' => 1,
|
||||
'possible_group_attribute_value_3' => '3',
|
||||
'possible_group_attribute_value_2' => 2,
|
||||
'possible_group_attribute_value_1' => 1,
|
||||
),
|
||||
'DefaultOrg' => 'DEFAULT_ORG',
|
||||
'DefaultOrg' => 'MISP_DEFAULT_ORG',
|
||||
),
|
||||
```
|
||||
If used with Apache as webserver it might be useful to make a distinction to filter out API/Syncs from SSO login. It can be added to the vhost as follows:
|
||||
If used with Apache as webserver it might be useful to make a distinction to filter out API/Syncs from SSO login. It can be added to the vhost as follows (Added lines are the If/Else clauses):
|
||||
|
||||
```Apache
|
||||
<If "-T req('Authorization')">
|
||||
Require all granted
|
||||
AuthType None
|
||||
</If>
|
||||
<Else>
|
||||
Require valid-user
|
||||
AuthType shibboleth
|
||||
ShibRequestSetting requiresession On
|
||||
ShibRequestSetting shibexportassertion Off
|
||||
ShibUseHeaders On
|
||||
</Else>
|
||||
<Directory /var/www/MISP/app/webroot>
|
||||
Options -Indexes
|
||||
AllowOverride all
|
||||
<If "-T req('Authorization')">
|
||||
Require all granted
|
||||
AuthType None
|
||||
</If>
|
||||
<Else>
|
||||
Require valid-user
|
||||
AuthType shibboleth
|
||||
ShibRequestSetting requiresession On
|
||||
ShibRequestSetting shibexportassertion Off
|
||||
ShibUseHeaders On
|
||||
</Else>
|
||||
</Directory>
|
||||
```
|
||||
|
||||
If you want the logout button to work for killing your session, you can use the CustomAuth plugin to configure a custom logout url, by default the url should be https://<host>/Shibboleth.sso/Logout. This leads to a local logout. If you want to also trigger a logout at the identity provider, you can use the return mechanism. In this case you will need to change the allowed redirects. Your logout url will look like https://<host>/Shibboleth.sso/Logout?return=https://<idp_host>/Logout. Edit your shibboleth configuration (often at /etc/shibboleth/shibboleth2.xml) as necessary. Relevant shibboleth documentation can be found at https://wiki.shibboleth.net/confluence/display/SP3/Logout and https://wiki.shibboleth.net/confluence/display/SP3/Sessions.
|
||||
```xml
|
||||
<Sessions lifetime="28800" timeout="3600" relayState="ss:mem"
|
||||
checkAddress="false" handlerSSL="false" cookieProps="https"
|
||||
redirectLimit="exact+whitelist" redirectWhitelist="https://<idp_host>">
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -206,40 +206,38 @@ $quickEdit = function($fieldName) use ($editScope, $object, $event) {
|
|||
<td class="shortish">
|
||||
<ul class="inline" style="margin:0">
|
||||
<?php
|
||||
if (!empty($object['Feed'])) {
|
||||
if (isset($object['Feed'])) {
|
||||
foreach ($object['Feed'] as $feed) {
|
||||
$popover = '';
|
||||
foreach ($feed as $k => $v) {
|
||||
if ($k == 'id') continue;
|
||||
if (is_array($v)) {
|
||||
foreach ($v as $k2 => $v2) {
|
||||
$v[$k2] = h($v2);
|
||||
}
|
||||
$v = implode('<br>', $v);
|
||||
} else {
|
||||
$v = h($v);
|
||||
}
|
||||
$popover .= '<span class="bold black">' . Inflector::humanize(h($k)) . '</span>: <span class="blue">' . $v . '</span><br>';
|
||||
$relatedData = array(
|
||||
__('Name') => $feed['name'],
|
||||
__('URL') => $feed['url'],
|
||||
__('Provider') => $feed['provider'],
|
||||
);
|
||||
if (isset($feed['event_uuids'])) {
|
||||
$relatedData[__('Event UUIDs')] = implode('<br>', $feed['event_uuids']);
|
||||
}
|
||||
$popover = '';
|
||||
foreach ($relatedData as $k => $v) {
|
||||
$popover .= '<span class="bold black">' . h($k) . '</span>: <span class="blue">' . h($v) . '</span><br>';
|
||||
}
|
||||
$liContents = '';
|
||||
if ($isSiteAdmin || $hostOrgUser) {
|
||||
if ($feed['source_format'] === 'misp') {
|
||||
$liContents .= sprintf(
|
||||
$liContents = sprintf(
|
||||
'<form action="%s/feeds/previewIndex/%s" method="post" style="margin:0;line-height:auto;">%s%s</form>',
|
||||
$baseurl,
|
||||
h($feed['id']),
|
||||
sprintf(
|
||||
'<input type="hidden" name="data[Feed][eventid]" value="%s">',
|
||||
h(json_encode($feed['event_uuids'], true))
|
||||
h(json_encode($feed['event_uuids']))
|
||||
),
|
||||
sprintf(
|
||||
'<input type="submit" class="linkButton useCursorPointer" value="%s" data-toggle="popover" data-content="%s" data-trigger="hover" style="margin-right:3px;line-height:normal;vertical-align: text-top;" />',
|
||||
'<input type="submit" class="linkButton useCursorPointer" value="%s" data-toggle="popover" data-content="%s" data-trigger="hover" style="margin-right:3px;line-height:normal;vertical-align: text-top;">',
|
||||
h($feed['id']),
|
||||
h($popover)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$liContents .= sprintf(
|
||||
$liContents = sprintf(
|
||||
'<a href="%s/feeds/previewIndex/%s" style="margin-right:3px;" data-toggle="popover" data-content="%s" data-trigger="hover">%s</a>',
|
||||
$baseurl,
|
||||
h($feed['id']),
|
||||
|
@ -248,7 +246,7 @@ $quickEdit = function($fieldName) use ($editScope, $object, $event) {
|
|||
);
|
||||
}
|
||||
} else {
|
||||
$liContents .= sprintf(
|
||||
$liContents = sprintf(
|
||||
'<span style="margin-right:3px;">%s</span>',
|
||||
h($feed['id'])
|
||||
);
|
||||
|
@ -259,7 +257,7 @@ $quickEdit = function($fieldName) use ($editScope, $object, $event) {
|
|||
);
|
||||
}
|
||||
}
|
||||
if (!empty($object['Server'])) {
|
||||
if (isset($object['Server'])) {
|
||||
foreach ($object['Server'] as $server) {
|
||||
$popover = '';
|
||||
foreach ($server as $k => $v) {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<div>
|
||||
<?php
|
||||
/*
|
||||
* A simple button to add a link to a specific section
|
||||
*
|
||||
* Expected input:
|
||||
* { url: <relative url>, text: <text to be displayed on the button>}
|
||||
*
|
||||
* Example:
|
||||
* {url: "/events/index", text: "To the list of events"}
|
||||
*
|
||||
*/
|
||||
echo '<a href="'.$baseurl.h($data['url']).'">';
|
||||
echo '<button class="btn btn-primary widget-button">';
|
||||
echo h($data['text']);
|
||||
echo '</button></a>';
|
||||
?>
|
||||
</div>
|
||||
|
||||
<style widget-scoped>
|
||||
.widget-button {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: large;
|
||||
}
|
||||
</style>
|
|
@ -130,6 +130,11 @@
|
|||
<span class="<?= $this->FontAwesome->getClass('quote-left') ?> useCursorPointer icon" onclick="replacementAction('quote')" title="<?= __('Quote') ?>"></span>
|
||||
<span class="<?= $this->FontAwesome->getClass('code') ?> useCursorPointer icon" onclick="replacementAction('code')" title="<?= __('Code') ?>"></span>
|
||||
<span class="<?= $this->FontAwesome->getClass('table') ?> useCursorPointer icon" onclick="replacementAction('table')" title="<?= __('Table') ?>"></span>
|
||||
<span style="position: absolute;right: 10px;">
|
||||
<button id="cancelMarkdownButton" type="button" class="btn btn-mini" onclick="cancelEdit()">
|
||||
<?= __('Cancel') ?>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<textarea id="editor"></textarea>
|
||||
<div id="bottom-bar" class="editor-action-bar">
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
<?php
|
||||
echo $this->element('genericElements/Form/genericForm', array(
|
||||
'form' => $this->Form,
|
||||
'data' => array(
|
||||
'title' => __('Automatic entities extraction'),
|
||||
'description' => __('Automatically extracting entities from a report will use the freetext import tools to extract and create attributes from the report.'),
|
||||
'model' => 'EventReport',
|
||||
'fields' => array(
|
||||
sprintf('<h5>%s</h5>', __('Post extraction actions:')),
|
||||
array(
|
||||
'label' => __('Tag the event with contextual elements found in the report'),
|
||||
'field' => 'tag_event',
|
||||
'type' => 'checkbox',
|
||||
'div' => array('class' => 'checkbox')
|
||||
),
|
||||
array(
|
||||
'field' => 'id',
|
||||
'default' => $reportId,
|
||||
'type' => 'hidden'
|
||||
)
|
||||
),
|
||||
'submit' => array(
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => sprintf('submitPopoverForm(\'%s\', \'extractAllFromReport\', 0, 1)', h($reportId))
|
||||
),
|
||||
)
|
||||
));
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/Form/genericForm', array(
|
||||
'form' => $this->Form,
|
||||
'data' => array(
|
||||
'title' => __('Import from URL (event %s)', h($event_id)),
|
||||
'model' => 'EventReport',
|
||||
'fields' => array(
|
||||
array(
|
||||
'type' => 'textarea',
|
||||
'field' => 'url',
|
||||
'class' => 'input span6',
|
||||
'div' => 'text',
|
||||
'label' => sprintf('<b>%s:</b> ', __('URL')) . __('Content for this URL will be downloaded and converted to Mardown')
|
||||
),
|
||||
),
|
||||
'submit' => array(
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => sprintf('submitPopoverForm(\'%s\', \'addEventReport\', 0, 1)', h($event_id))
|
||||
)
|
||||
)
|
||||
));
|
|
@ -6,6 +6,11 @@
|
|||
<button class="btn btn-small btn-primary" onclick="openGenericModal(baseurl + '/eventReports/add/<?= h($event_id) ?>')">
|
||||
<i class="<?= $this->FontAwesome->getClass('plus') ?>"></i> <?= __('Add Event Report') ?>
|
||||
</button>
|
||||
<?php if ($importModuleEnabled): ?>
|
||||
<button class="btn btn-small btn-primary" onclick="openGenericModal(baseurl + '/eventReports/importReportFromUrl/<?= h($event_id) ?>')" title="<?= __('Content for this URL will be downloaded and converted to Mardown') ?>">
|
||||
<i class="<?= $this->FontAwesome->getClass('link') ?>"></i> <?= __('Import from URL') ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
echo $this->element('/genericElements/IndexTable/index_table', array(
|
||||
|
@ -85,13 +90,6 @@
|
|||
'icon' => 'eye',
|
||||
'dbclickAction' => true
|
||||
),
|
||||
array(
|
||||
'url' => '/eventReports/edit',
|
||||
'url_params_data_paths' => array(
|
||||
'EventReport.id'
|
||||
),
|
||||
'icon' => 'edit'
|
||||
),
|
||||
array(
|
||||
'title' => __('Delete'),
|
||||
'icon' => 'trash',
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/Form/genericForm', array(
|
||||
'form' => $this->Form,
|
||||
'data' => array(
|
||||
'title' => __('Replace suggestions in report'),
|
||||
'model' => 'EventReport',
|
||||
'fields' => array(
|
||||
array(
|
||||
'field' => 'suggestions',
|
||||
'class' => 'textarea'
|
||||
),
|
||||
),
|
||||
'submit' => array(
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => ''
|
||||
),
|
||||
)
|
||||
));
|
||||
?>
|
|
@ -144,7 +144,7 @@
|
|||
?></pre>
|
||||
<code><request><type>ip</type><eventid>!51</eventid><eventid>!62</eventid><withAttachment>false</withAttachment><tags>APT1</tags><tags>!OSINT</tags><from>false</from><to>2015-02-15</to></request></code><br /><br />
|
||||
<p><?php echo __('Alternatively, it is also possible to pass the filters via the parameters in the URL, though it is highly advised to use POST requests with JSON objects instead. The format is as described below');?>:</p>
|
||||
<pre><?php echo $baseurl.'/attributes/bro/download/[type]/[tags]/[event_id]/[allowNonIDS]/[from]/[to]/[last]'; ?></pre>
|
||||
<pre><?php echo $baseurl.'/attributes/bro/download/[type]/[tags]/[event_id]/[from]/[to]/[last]'; ?></pre>
|
||||
<b>type</b>: <?php echo __('The Bro type, any valid Bro type is accepted. The mapping between Bro and MISP types is as follows');?>:<br />
|
||||
<pre><?php
|
||||
foreach ($broTypes as $key => $value) {
|
||||
|
|
|
@ -374,7 +374,7 @@
|
|||
endif;
|
||||
if (!empty($event['Feed']) || !empty($event['Event']['FeedCount'])):
|
||||
?>
|
||||
<h3>Related Feeds</h3>
|
||||
<h3><?= __('Related Feeds') ?> <a href="#attributeList" title="<?= __('Show just attributes that has feed hits') ?>" onclick="toggleBoolFilter('<?= $baseurl ?>/events/view/<?= h($event['Event']['id']) ?>', 'feed')"><?= __('(show)') ?></a></h3>
|
||||
<?php
|
||||
if (!empty($event['Feed'])):
|
||||
?>
|
||||
|
@ -382,10 +382,9 @@
|
|||
<?php
|
||||
foreach ($event['Feed'] as $relatedFeed):
|
||||
$relatedData = array(
|
||||
'Name' => $relatedFeed['name'],
|
||||
'URL' => $relatedFeed['url'],
|
||||
'Provider' => $relatedFeed['provider'],
|
||||
'Source Format' => $relatedFeed['source_format'] === 'misp' ? 'MISP' : $relatedFeed['source_format'],
|
||||
__('Name') => $relatedFeed['name'],
|
||||
__('URL') => $relatedFeed['url'],
|
||||
__('Provider') => $relatedFeed['provider'],
|
||||
);
|
||||
$popover = '';
|
||||
foreach ($relatedData as $k => $v) {
|
||||
|
@ -397,13 +396,13 @@
|
|||
if ($relatedFeed ['source_format'] === 'misp'):
|
||||
?>
|
||||
<form action="<?php echo $baseurl; ?>/feeds/previewIndex/<?php echo h($relatedFeed['id']); ?>" method="post" style="margin:0px;">
|
||||
<input type="hidden" name="data[Feed][eventid]" value="<?php echo h(json_encode($relatedFeed['event_uuids'], true)); ?>">
|
||||
<input type="submit" class="linkButton useCursorPointer" value="<?php echo h($relatedFeed['name']) . ' (' . $relatedFeed['id'] . ')'; ?>" data-toggle="popover" data-content="<?php echo h($popover); ?>" data-trigger="hover" />
|
||||
<input type="hidden" name="data[Feed][eventid]" value="<?php echo h(json_encode($relatedFeed['event_uuids'])); ?>">
|
||||
<input type="submit" class="linkButton useCursorPointer" value="<?php echo h($relatedFeed['name']) . ' (' . $relatedFeed['id'] . ')'; ?>" data-toggle="popover" data-content="<?php echo h($popover); ?>" data-trigger="hover">
|
||||
</form>
|
||||
<?php
|
||||
else:
|
||||
?>
|
||||
<a href="<?php echo $baseurl; ?>/feeds/previewIndex/<?php echo h($relatedFeed['id']); ?>" data-toggle="popover" data-content="<?php echo h($popover); ?>" data-trigger="hover"><?php echo h($relatedFeed['name']) . ' (' . $relatedFeed['id'] . ')'; ?></a><br />
|
||||
<a href="<?php echo $baseurl; ?>/feeds/previewIndex/<?php echo h($relatedFeed['id']); ?>" data-toggle="popover" data-content="<?php echo h($popover); ?>" data-trigger="hover"><?php echo h($relatedFeed['name']) . ' (' . $relatedFeed['id'] . ')'; ?></a><br>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
|
@ -458,7 +457,10 @@
|
|||
?>
|
||||
<?php if (!empty($event['warnings'])): ?>
|
||||
<div class="warning_container">
|
||||
<h4 class="red"><?= __('Warning: Potential false positives') ?> <a href="#attributeList" onclick="toggleBoolFilter('<?= $baseurl ?>/events/view/<?= h($event['Event']['id']) ?>', 'warning')"><?= __('(show)') ?></a></h4>
|
||||
<h4 class="red">
|
||||
<?= __('Warning: Potential false positives') ?>
|
||||
<a href="#attributeList" title="<?= __('Show just attributes that has warning') ?>" onclick="toggleBoolFilter('<?= $baseurl ?>/events/view/<?= h($event['Event']['id']) ?>', 'warning')"><?= __('(show)') ?></a>
|
||||
</h4>
|
||||
<?php
|
||||
$links = [];
|
||||
foreach ($event['warnings'] as $id => $name) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
$table_data = array();
|
||||
$table_data[] = array('key' => __('Id'), 'value' => $feed['Feed']['id']);
|
||||
$table_data[] = array('key' => __('ID'), 'value' => $feed['Feed']['id']);
|
||||
$table_data[] = array('key' => __('Name'), 'value' => $feed['Feed']['name']);
|
||||
$table_data[] = array('key' => __('URL'), 'value' => $feed['Feed']['url']);
|
||||
$table_data[] = array(
|
||||
|
@ -19,7 +19,7 @@
|
|||
$this->element(
|
||||
'ajaxTags',
|
||||
array(
|
||||
'event' => false,
|
||||
'scope' => 'feed',
|
||||
'tags' => array(array('Tag' => $feed['Tag'])),
|
||||
'tagAccess' => false,
|
||||
'static_tags_only' => true
|
||||
|
@ -74,13 +74,12 @@
|
|||
|
||||
// $table_data[] = array('key' => __('Role'), 'html' => $this->Html->link($user['Role']['name'], array('controller' => 'roles', 'action' => 'view', $user['Role']['id'])));
|
||||
echo sprintf(
|
||||
'<div class="feeds view"><div class="row-fluid"><div class="span8" style="margin:0px;">%s<hr /><div class="feed_overlap_tool">%s</div></div></div></div>%s',
|
||||
'<div class="feeds view"><div class="row-fluid"><div class="span8" style="margin:0px;">%s<hr /><div class="feed_overlap_tool">%s</div></div></div></div>',
|
||||
sprintf(
|
||||
'<h2>%s</h2>%s',
|
||||
__('Feed'),
|
||||
$this->element('genericElements/viewMetaTable', array('table_data' => $table_data))
|
||||
),
|
||||
$this->element('Feeds/View/feed_overlap_tool', array('other_feeds' => $other_feeds, 'feed' => $feed)),
|
||||
$this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'feeds', 'menuItem' => 'view'))
|
||||
$this->element('Feeds/View/feed_overlap_tool', array('other_feeds' => $other_feeds, 'feed' => $feed))
|
||||
);
|
||||
?>
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'feeds', 'menuItem' => 'view'));
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b41e3d4f5037d0d325d38b2d5cf4f4bc94274cbc
|
||||
Subproject commit b56a4d9e5c84e5f3aa10bd852c83e199f327391d
|
|
@ -1 +1 @@
|
|||
Subproject commit 27a554ab12acbc1242f801b5682364b2047cf9e0
|
||||
Subproject commit abf42cc8fb71c003c40dc0767f89804b45eb5303
|
|
@ -1 +1 @@
|
|||
Subproject commit 278f726f1dbc7fab2607dd6618453559e6b5983f
|
||||
Subproject commit 8d60a4f5052c4e303ac3f7f90b3ee14f655f88fd
|
|
@ -6,6 +6,19 @@
|
|||
.misp-element-wrapper.object .obj-value {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.attribute .attr-value > span {
|
||||
max-width: unset !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
.misp-element-wrapper.object .obj-value > span {
|
||||
max-width: unset !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
span.misp-element-wrapper.attribute {
|
||||
max-width: unset !important;
|
||||
white-space: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
span.misp-element-wrapper {
|
||||
|
@ -21,6 +34,25 @@ span.misp-element-wrapper {
|
|||
cursor: help;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.suggestion {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-color: #333 !important;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.suggestion input[type="checkbox"] {
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.attribute.suggestion.picked {
|
||||
box-shadow: #2f96b4 0px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.attribute.suggestion.picked .attr-type {
|
||||
background-color: #2f96b4 !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.invalid {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
@ -35,6 +67,12 @@ span.misp-element-wrapper {
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.attribute.suggestion .attr-type {
|
||||
color: white;
|
||||
background-color: #333 !important;
|
||||
border-right: 1px solid #333 !important;
|
||||
}
|
||||
|
||||
.misp-element-wrapper.attribute .attr-type > span {
|
||||
margin: 2px 3px;
|
||||
}
|
||||
|
@ -93,6 +131,18 @@ span.misp-element-wrapper.object {
|
|||
max-width: 90%;
|
||||
}
|
||||
|
||||
#suggestion-container ul.nav-tabs {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#suggestion-container div.tab-content {
|
||||
height: calc(100vh - 120px - 38px) !important;
|
||||
}
|
||||
|
||||
.markdownEditor-full-container:fullscreen #suggestion-container div.tab-content {
|
||||
height: calc(100vh - 50px - 38px) !important;
|
||||
}
|
||||
|
||||
.CodeMirror-hint-active .blue {
|
||||
color: white !important;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
div.markdownEditor-full-container {
|
||||
position: unset !important;
|
||||
}
|
||||
}
|
||||
|
||||
.markdownEditor-full-container {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -155,6 +155,13 @@ function initCodeMirror() {
|
|||
}
|
||||
if (typeof cmCustomHints === 'function') {
|
||||
cmOptions['hintOptions']['hint'] = cmCustomHints
|
||||
// cmOptions['hintOptions']['hint'] = function (cm, options) {
|
||||
// var result = cmCustomHints(cm, options);
|
||||
// if (result) {
|
||||
// CodeMirror.on(result, 'shown', function() {})
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
}
|
||||
cm = CodeMirror.fromTextArea($editor[0], cmOptions);
|
||||
cm.on('changes', function(cm, event) {
|
||||
|
@ -365,12 +372,13 @@ function setEditorData(data) {
|
|||
cm.setValue(data)
|
||||
}
|
||||
|
||||
function saveMarkdown() {
|
||||
function saveMarkdown(confirmSave, callback) {
|
||||
confirmSave = confirmSave === undefined ? true : confirmSave
|
||||
if (modelNameForSave === undefined || markdownModelFieldNameForSave === undefined) {
|
||||
console.log('Model or field not defined. Save not possible')
|
||||
return
|
||||
}
|
||||
if (!confirm(saveConfirmMessage)) {
|
||||
if (confirmSave && !confirm(saveConfirmMessage)) {
|
||||
return
|
||||
}
|
||||
var url = baseurl + "/eventReports/edit/" + reportid
|
||||
|
@ -404,6 +412,9 @@ function saveMarkdown() {
|
|||
$('#temp').remove();
|
||||
toggleLoadingInSaveButton(false)
|
||||
$editor.prop('disabled', false);
|
||||
if (callback !== undefined) {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
type:"post",
|
||||
url: formUrl
|
||||
|
@ -411,6 +422,11 @@ function saveMarkdown() {
|
|||
})
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
setEditorData(originalRaw);
|
||||
setMode('viewer')
|
||||
}
|
||||
|
||||
function downloadMarkdown(type) {
|
||||
var content, fileType, baseName, extension
|
||||
if (type == 'pdf') {
|
||||
|
@ -575,6 +591,10 @@ function baseReplacementAction(action) {
|
|||
cm.focus()
|
||||
}
|
||||
|
||||
function setCMReadOnly(readonly) {
|
||||
cm.setOption('readOnly', readonly)
|
||||
}
|
||||
|
||||
function insertTopToolbarSection() {
|
||||
$topBar.append($('<i />').addClass('top-bar-separator'))
|
||||
}
|
||||
|
|
|
@ -118,7 +118,7 @@ function genericPopup(url, popupTarget, callback) {
|
|||
if (callback !== undefined) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}).fail(xhrFailCallback)
|
||||
}
|
||||
|
||||
function screenshotPopup(url, title) {
|
||||
|
@ -2523,33 +2523,42 @@ function serverSettingsPostActivationScripts(name, setting, id) {
|
|||
}
|
||||
|
||||
function serverSettingSubmitForm(name, setting, id) {
|
||||
subGroup = getSubGroupFromSetting(setting);
|
||||
var subGroup = getSubGroupFromSetting(setting);
|
||||
var formData = $(name + '_field').closest("form").serialize();
|
||||
$.ajax({
|
||||
data: formData,
|
||||
cache: false,
|
||||
beforeSend: function (XMLHttpRequest) {
|
||||
beforeSend: function () {
|
||||
$(".loading").show();
|
||||
},
|
||||
success:function (data, textStatus) {
|
||||
success: function (data) {
|
||||
if (!data.saved) {
|
||||
$(".loading").hide();
|
||||
showMessage('fail', data.errors);
|
||||
resetForms();
|
||||
$('.inline-field-placeholder').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type:"get",
|
||||
type: "get",
|
||||
url: baseurl + "/servers/serverSettingsReloadSetting/" + setting + "/" + id,
|
||||
success:function (data2, textStatus2) {
|
||||
success: function (data2) {
|
||||
$('#' + subGroup + "_" + id + '_row').replaceWith(data2);
|
||||
$(".loading").hide();
|
||||
},
|
||||
error:function() {
|
||||
error: function() {
|
||||
showMessage('fail', 'Could not refresh the table.');
|
||||
}
|
||||
});
|
||||
},
|
||||
error:function() {
|
||||
error: function() {
|
||||
$(".loading").hide();
|
||||
showMessage('fail', 'Request failed for an unknown reason.');
|
||||
resetForms();
|
||||
$('.inline-field-placeholder').hide();
|
||||
},
|
||||
type:"post",
|
||||
type: "post",
|
||||
url: baseurl + "/servers/serverSettingsEdit/" + setting + "/" + id + "/" + 1
|
||||
});
|
||||
$(name + '_field').unbind("keyup");
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0d6db44c80afd81976f54f58c8cb02e4d33acc16
|
||||
Subproject commit 52d806b349333d40c5dd75b62e8e64d6a18fcdf4
|
Loading…
Reference in New Issue