diff --git a/app/Console/Command/Ls22Shell.php b/app/Console/Command/Ls22Shell.php index 63a945bbe..e5791abcb 100644 --- a/app/Console/Command/Ls22Shell.php +++ b/app/Console/Command/Ls22Shell.php @@ -52,6 +52,55 @@ class Ls22Shell extends AppShell ), ), ]); + $parser->addSubcommand('checkSyncConnections', [ + 'help' => __('Check the given sync connection(s) for the given server(s).'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ], + 'misp_url_filter' => [ + 'help' => 'The url of the instance to execute changes on. If not set, all are updated.', + 'short' => 'm', + 'required' => false + ], + 'synced_misp_url_filter' => [ + 'help' => 'The sync connection to modify on each valid instance (as selected by the misp_url_filter). If not set, all sync connections on the selected instances will be updated.', + 'short' => 's', + 'required' => false + ] + ), + ), + ]); + $parser->addSubcommand('modifySyncConnection', [ + 'help' => __('Modify sync connection(s).'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ], + 'misp_url_filter' => [ + 'help' => 'The url of the instance to execute changes on. If not set, all are updated.', + 'short' => 'm', + 'required' => false + ], + 'synced_misp_url_filter' => [ + 'help' => 'The sync connection to modify on each valid instance (as selected by the misp_url_filter). If not set, all sync connections on the selected instances will be updated.', + 'short' => 's', + 'required' => false + ], + 'json' => [ + 'help' => 'JSON delta to push (such as \'{"push": 1}\').', + 'short' => 'j', + 'required' => true + ] + ), + ), + ]); $parser->addSubcommand('addWarninglist', [ 'help' => __('Inject warninglist'), 'parser' => array( @@ -104,6 +153,11 @@ class Ls22Shell extends AppShell 'help' => 'Upper bound of the date. Accepts timestamp or date distance (such as 1d or 5h). Defaults to unbounded.', 'short' => 't', 'required' => false + ], + 'org' => [ + 'help' => 'Name the org that should be evaluated. If not set, all will be included.', + 'short' => 'o', + 'required' => false ] ), ), @@ -111,6 +165,103 @@ class Ls22Shell extends AppShell return $parser; } + public function checkSyncConnections() + { + $this->__getInstances($this->param('instances')); + $results = []; + $instanceFilter = $this->param('misp_url_filter'); + $syncedInstanceFilter = $this->param('synced_misp_url_filter'); + foreach ($this->__servers as $server) { + if (!empty($instanceFilter) && strtolower(trim($server['Server']['url'])) !== strtolower(trim($instanceFilter))) { + continue; + } + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server, 'Server'); + $start_time = microtime(true); + $response = $HttpSocket->get($server['Server']['url'] . '/servers/index', false, $request); + $baseline = round((microtime(true) - $start_time) * 1000); + if (!$response->isOk()) { + $this->out($server['Server']['url'] . ': ' . 'Connection or auth failed', 1, Shell::NORMAL); + continue; + } + $synced_servers = json_decode($response->body, true); + foreach ($synced_servers as $synced_server) { + $success = false; + if (empty($syncedInstanceFilter) || strtolower($synced_server['Server']['url']) === strtolower($syncedInstanceFilter)) { + $start_time = microtime(true); + $response = $HttpSocket->get($server['Server']['url'] . '/servers/testConnection/' . $synced_server['Server']['id'], '{}', $request); + $execution_time = round((microtime(true) - $start_time) * 1000) - $baseline; + if ($response->isOk()) { + $success = true; + } + $this->out( + sprintf( + '%s connection to %s: %s (%sms)', + $server['Server']['url'], + $synced_server['Server']['url'], + sprintf( + '<%s>%s', + $success ? 'info' : 'error', + $success ? 'Success' : 'Failed', + $success ? 'info' : 'error' + ), + $execution_time + ), + 1, + Shell::NORMAL + ); + } + } + } + } + + public function modifySyncConnection() + { + $this->__getInstances($this->param('instances')); + $results = []; + $instanceFilter = $this->param('misp_url_filter'); + $syncedInstanceFilter = $this->param('synced_misp_url_filter'); + $json = $this->param('json'); + foreach ($this->__servers as $server) { + if (!empty($instanceFilter) && strtolower(trim($server['Server']['url'])) !== strtolower(trim($instanceFilter))) { + continue; + } + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server, 'Server'); + $response = $HttpSocket->get($server['Server']['url'] . '/servers/index', false, $request); + if (!$response->isOk()) { + $this->out($server['Server']['url'] . ': ' . 'Connection or auth failed', 1, Shell::NORMAL); + } + $synced_servers = json_decode($response->body, true); + $success = false; + foreach ($synced_servers as $synced_server) { + if (empty($syncedInstanceFilter) || strtolower($synced_server['Server']['url']) === strtolower($syncedInstanceFilter)) { + debug($json); + $response = $HttpSocket->post($server['Server']['url'] . '/servers/edit/' . $synced_server['Server']['id'], $json, $request); + debug($response->body); + if ($response->isOk()) { + $success = true; + } + $this->out( + sprintf( + '%s connection to %s: %s', + $server['Server']['url'], + $synced_server['Server']['url'], + sprintf( + '<%s>%s', + $success ? 'info' : 'error', + $success ? 'Success' : 'Failed', + $success ? 'info' : 'error' + ) + ), + 1, + Shell::NORMAL + ); + } + } + } + } + public function enableTaxonomy() { $taxonomyToEnable = $this->param('taxonomy'); @@ -162,16 +313,29 @@ class Ls22Shell extends AppShell $HttpSocket = $this->Server->setupHttpSocket($server, null); $request = $this->Server->setupSyncRequest($server, 'Server'); $start_time = microtime(true); - $response = $HttpSocket->get($server['Server']['url'] . '/users/view/me', false, $request); - $execution_time = round((microtime(true) - $start_time) * 1000); - $statusWrapped = sprintf( - '<%s>%s', - $response->isOk() ? 'info' : 'error', - $response->isOk() ? 'OK (' . $execution_time . 'ms)' : 'Failed. (' . $response->code . ')', - $response->isOk() ? 'info' : 'error' - ); + $fatal_error = false; + try { + $response = $HttpSocket->get($server['Server']['url'] . '/users/view/me', false, $request); + } catch (Exception $e) { + $fatal_error = true; + echo "\x07"; + $statusWrapped = sprintf( + '%s %s: %s', + 'Something went wrong while trying to reach', + $server['Server']['url'], + $e->getMessage() + ); + } + if (!$fatal_error) { + $execution_time = round((microtime(true) - $start_time) * 1000); + $statusWrapped = sprintf( + '<%s>%s', + $response->isOk() ? 'info' : 'error', + $response->isOk() ? 'OK (' . $execution_time . 'ms)' : 'Failed. (' . $response->code . ')', + $response->isOk() ? 'info' : 'error' + ); + } $this->out($server['Server']['url'] . ': ' . $statusWrapped, 1, Shell::NORMAL); - $results[$server['Server']['url']] = $response->isOk() ? $execution_time : false; } } @@ -215,28 +379,30 @@ class Ls22Shell extends AppShell } $HttpSocket = $this->Server->setupHttpSocket($server, null); $request = $this->Server->setupSyncRequest($server); - $response = $HttpSocket->get($server['Server']['url'] . '/organisations/index', false, $request); + $response = $HttpSocket->get($server['Server']['url'] . '/organisations/index/scope:all', false, $request); $orgs = json_decode($response->body(), true); $this->out(__('Organisations fetched. %d found.', count($orgs)), 1, Shell::VERBOSE); $org_mapping = []; foreach ($orgs as $org) { - $name = explode(' ', $org['Organisation']['name']); - if ($name[0] !== 'BT') { + if (!empty($this->param('org')) && $org['Organisation']['name'] !== $this->param('org')) { + continue; + } + if ($org['Organisation']['name'] === 'YT') { continue; } $org_mapping[$org['Organisation']['name']] = $org['Organisation']['id']; } + if (!empty($this->param['from'])) { + $time_range[] = $this->param['from']; + } + if (!empty($this->param['to'])) { + if (empty($time_range)) { + $time_range[] = '365d'; + } + $time_range[] = $this->param['to']; + } foreach ($org_mapping as $org_name => $org_id) { $time_range = []; - if (!empty($this->param['from'])) { - $time_range[] = $this->param['from']; - } - if (!empty($this->param['to'])) { - if (empty($time_range)) { - $time_range[] = '365d'; - } - $time_range[] = $this->param['to']; - } $params = [ 'org' => $org_id ]; @@ -256,7 +422,8 @@ class Ls22Shell extends AppShell 'other' => 0, 'attribute_attack' => 0, 'attribute_other' => 0, - 'score' => 0 + 'score' => 0, + 'warnings' => 0 ]; foreach ($events['response'] as $event) { if (!empty($event['Event']['Tag'])) { @@ -290,6 +457,9 @@ class Ls22Shell extends AppShell } } } + if (!empty($attribute['warnings'])) { + $result[$org_name]['warnings'] += 1; + } } $results[$org_name]['attribute_count'] += count($event['Event']['Attribute']); if (!empty($event['Event']['Object'])) { @@ -319,11 +489,18 @@ class Ls22Shell extends AppShell foreach ($results as $k => $result) { $totalCount = $result['attribute_count'] + $result['object_count']; if ($totalCount) { + if (empty($result['warnings'])) { + $results[$k]['metrics']['warnings'] = 100; + } else if (100 * $result['warnings'] < $result['attribute_count']) { + $results[$k]['metrics']['warnings'] = 50; + } else { + $results[$k]['metrics']['warnings'] = 0; + } $results[$k]['metrics']['connectedness'] = 100 * ($result['connected_elements'] / ($result['attribute_count'] + $result['object_count'])); $results[$k]['metrics']['attack_weight'] = 100 * (2*($result['attack']) + $result['attribute_attack']) / ($result['attribute_count'] + $result['object_count']); $results[$k]['metrics']['other_weight'] = 100 * (2*($result['other']) + $result['attribute_other']) / ($result['attribute_count'] + $result['object_count']); } - foreach (['connectedness', 'attack_weight', 'other_weight'] as $metric) { + foreach (['connectedness', 'attack_weight', 'other_weight', 'warnings'] as $metric) { if (empty($results[$k]['metrics'][$metric])) { $results[$k]['metrics'][$metric] = 0; } @@ -331,8 +508,17 @@ class Ls22Shell extends AppShell $results[$k]['metrics'][$metric] = 100; } } - $results[$k]['score'] = round(40 * $results[$k]['metrics']['connectedness'] + 40 * $results[$k]['metrics']['attack_weight'] + 20 * $results[$k]['metrics']['other_weight']) / 100; - $scores[$k] = $results[$k]['score']; + $results[$k]['score'] = round( + 20 * $results[$k]['metrics']['warnings'] + + 20 * $results[$k]['metrics']['connectedness'] + + 40 * $results[$k]['metrics']['attack_weight'] + + 20 * $results[$k]['metrics']['other_weight'] + ) / 100; + $scores[$k]['total'] = $results[$k]['score']; + $scores[$k]['warnings'] = round(20 * $results[$k]['metrics']['warnings']); + $scores[$k]['connectedness'] = round(20 * $results[$k]['metrics']['connectedness']); + $scores[$k]['attack_weight'] = round(40 * $results[$k]['metrics']['attack_weight']); + $scores[$k]['other_weight'] = round(20 * $results[$k]['metrics']['other_weight']); } arsort($scores, SORT_DESC); $this->out(str_repeat('=', 128), 1, Shell::NORMAL); @@ -344,18 +530,34 @@ class Ls22Shell extends AppShell ), 1, Shell::NORMAL); $this->out(str_repeat('=', 128), 1, Shell::NORMAL); foreach ($scores as $org => $score) { - $score_string = str_repeat('█', round($score)); + $score_string[0] = str_repeat('█', round($score['warnings']/100)); + $score_string[1] = str_repeat('█', round($score['connectedness']/100)); + $score_string[2] = str_repeat('█', round($score['attack_weight']/100)); + $score_string[3] = str_repeat('█', round($score['other_weight']/100)); $this->out(sprintf( '| %s | %s | %s |', str_pad($org, 10, ' ', STR_PAD_RIGHT), sprintf( - '%s%s', - $score_string, - str_repeat(' ', 100 - mb_strlen($score_string)) + '%s%s%s%s%s', + $score_string[0], + $score_string[1], + $score_string[2], + $score_string[3], + str_repeat(' ', 100 - mb_strlen(implode('', $score_string))) ), - str_pad($score . '%', 8, ' ', STR_PAD_RIGHT) + str_pad($score['total'] . '%', 8, ' ', STR_PAD_RIGHT) ), 1, Shell::NORMAL); } $this->out(str_repeat('=', 128), 1, Shell::NORMAL); + $this->out(sprintf( + '| Legend: %s %s %s %s %s |', + '█: Warnings', + '█: Connectedness', + '█: ATT&CK context', + '█: Other Context', + str_repeat(' ', 52) + ), 1, Shell::NORMAL); + $this->out(str_repeat('=', 128), 1, Shell::NORMAL); + file_put_contents(APP . 'tmp/report.json', json_encode($results, JSON_PRETTY_PRINT)); } }