2022-04-18 17:58:50 +02:00
< ? php
class Ls22Shell extends AppShell
{
public $uses = [ 'Server' ];
private $__servers = [];
private function __getInstances ( $path )
{
if ( empty ( $path )) {
$path = 'instances.csv' ;
}
$file = file_get_contents ( $path );
$lines = explode ( PHP_EOL , $file );
foreach ( $lines as $k => $line ) {
if ( $k === 0 ) {
continue ;
}
$fields = explode ( ',' , $line );
if ( count ( $fields ) === 4 && $fields [ 1 ] === 'admin@admin.test' ) {
$this -> __servers [] = [
'Server' => [
'url' => trim ( $fields [ 0 ]),
'authkey' => trim ( $fields [ 2 ])
]
];
}
}
}
public function getOptionParser ()
{
$parser = parent :: getOptionParser ();
$parser -> addSubcommand ( 'enableTaxonomy' , [
'help' => __ ( 'Enable a taxonomy with all its tags.' ),
'parser' => array (
'options' => array (
'instances' => [
'help' => 'Path to the instance file, by default "instances.csv" from the local directory' ,
'short' => 'i' ,
'required' => true
],
'taxonomy' => [
'help' => 'The name of the taxonomy to enable, such as "tlp"' ,
'short' => 't' ,
'required' => true
],
'misp_url_filter' => [
'help' => 'The url of the instance to enable it for - otherwise all are selected' ,
'short' => 'm' ,
'required' => false
]
),
),
]);
2022-07-07 11:07:55 +02:00
$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
]
),
),
]);
2022-04-18 17:58:50 +02:00
$parser -> addSubcommand ( 'addWarninglist' , [
'help' => __ ( 'Inject warninglist' ),
'parser' => array (
'options' => array (
'instances' => [
'help' => 'Path to the instance file, by default "instances.csv" from the local directory' ,
'short' => 'i' ,
'required' => true
],
'warninglist' => [
'help' => 'Path to the warninglist file' ,
'short' => 'w' ,
'required' => true
]
),
),
]);
$parser -> addSubcommand ( 'status' , [
'help' => __ ( 'Check if the instances are available / the API key works.' ),
'parser' => array (
'options' => array (
'instances' => [
'help' => 'Path to the instance file, by default "instances.csv" from the local directory' ,
'short' => 'i' ,
'required' => true
]
),
),
]);
$parser -> addSubcommand ( 'scores' , [
'help' => __ ( 'Generate the scores for all BTs.' ),
'parser' => array (
'options' => array (
'instances' => [
'help' => 'Path to the instance file, by default "instances.csv" from the local directory' ,
'short' => 'i' ,
'required' => true
],
'server_url' => [
'help' => 'URL of the server to query for the scores. If nothing is specified, the first valid entry from instances.csv is taken.' ,
'short' => 's' ,
'required' => false
],
'from' => [
'help' => 'Lower bound of the date. Accepts timestamp or date distance (such as 1d or 5h). Defaults to unbounded.' ,
'short' => 'f' ,
'required' => false
],
'to' => [
'help' => 'Upper bound of the date. Accepts timestamp or date distance (such as 1d or 5h). Defaults to unbounded.' ,
'short' => 't' ,
'required' => false
2022-07-07 11:07:55 +02:00
],
'org' => [
'help' => 'Name the org that should be evaluated. If not set, all will be included.' ,
'short' => 'o' ,
'required' => false
2022-04-18 17:58:50 +02:00
]
),
),
]);
return $parser ;
}
2022-07-07 11:07:55 +02:00
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' ] . ': ' . '<error>Connection or auth failed</error>' , 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</%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' ] . ': ' . '<error>Connection or auth failed</error>' , 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</%s>' ,
$success ? 'info' : 'error' ,
$success ? 'Success' : 'Failed' ,
$success ? 'info' : 'error'
)
),
1 ,
Shell :: NORMAL
);
}
}
}
}
2022-04-18 17:58:50 +02:00
public function enableTaxonomy ()
{
$taxonomyToEnable = $this -> param ( 'taxonomy' );
$instanceFilter = $this -> param ( 'misp_url_filter' );
if ( empty ( $taxonomyToEnable )) {
$this -> error ( 'No taxonomy provided' , 'Provide a taxonomy by specifying the -t or --taxonomy options.' );
}
$this -> __getInstances ( $this -> param ( 'instances' ));
$results = [];
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' ] . '/taxonomies/index' , false , $request );
if ( ! $response -> isOk ()) {
$this -> out ( $server [ 'Server' ][ 'url' ] . ': ' . '<error>Connection or auth failed</error>' , 1 , Shell :: NORMAL );
}
$taxonomies = json_decode ( $response -> body , true );
$success = false ;
foreach ( $taxonomies as $taxonomy ) {
if ( $taxonomy [ 'Taxonomy' ][ 'namespace' ] === $taxonomyToEnable ) {
$response = $HttpSocket -> post ( $server [ 'Server' ][ 'url' ] . '/taxonomies/enable/' . $taxonomy [ 'Taxonomy' ][ 'id' ], '{}' , $request );
if ( $response -> isOk ()) {
$response = $HttpSocket -> post ( $server [ 'Server' ][ 'url' ] . '/taxonomies/addTag/' . $taxonomy [ 'Taxonomy' ][ 'id' ], '{}' , $request );
if ( $response -> isOk ()) {
$success = true ;
}
}
}
}
$results [ $server [ 'Server' ][ 'url' ]] = $success ? 'Success' : 'Failed' ;
$statusWrapped = sprintf (
'<%s>%s</%s>' ,
$success ? 'info' : 'error' ,
$results [ $server [ 'Server' ][ 'url' ]],
$success ? 'info' : 'error'
);
$this -> out ( $server [ 'Server' ][ 'url' ] . ': ' . $statusWrapped , 1 , Shell :: NORMAL );
}
}
public function status ()
{
$this -> __getInstances ( $this -> param ( 'instances' ));
$results = [];
foreach ( $this -> __servers as $server ) {
$HttpSocket = $this -> Server -> setupHttpSocket ( $server , null );
$request = $this -> Server -> setupSyncRequest ( $server , 'Server' );
$start_time = microtime ( true );
2022-07-07 11:07:55 +02:00
$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 (
'<error>%s %s: %s</error>' ,
'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</%s>' ,
$response -> isOk () ? 'info' : 'error' ,
$response -> isOk () ? 'OK (' . $execution_time . 'ms)' : 'Failed. (' . $response -> code . ')' ,
$response -> isOk () ? 'info' : 'error'
);
}
2022-04-18 17:58:50 +02:00
$this -> out ( $server [ 'Server' ][ 'url' ] . ': ' . $statusWrapped , 1 , Shell :: NORMAL );
}
}
public function addWarninglist ()
{
$path = $this -> param ( 'warninglist' );
if ( empty ( $path )) {
$this -> error ( 'No warninglist provided' , 'Provide a path to a file containing a warninglist JSON by specifying the -w or --warninglist options.' );
}
$file = file_get_contents ( $path );
$this -> __getInstances ( $this -> param ( 'instances' ));
$results = [];
foreach ( $this -> __servers as $server ) {
$HttpSocket = $this -> Server -> setupHttpSocket ( $server , null );
$request = $this -> Server -> setupSyncRequest ( $server , 'Server' );
$start_time = microtime ( true );
$response = $HttpSocket -> post ( $server [ 'Server' ][ 'url' ] . '/warninglists/add' , $file , $request );
$statusWrapped = sprintf (
'<%s>%s</%s>' ,
$response -> isOk () ? 'info' : 'error' ,
$response -> isOk () ? 'OK' : 'Could not create warninglist' ,
$response -> isOk () ? 'info' : 'error'
);
$this -> out ( $server [ 'Server' ][ 'url' ] . ': ' . $statusWrapped , 1 , Shell :: NORMAL );
}
}
public function scores ()
{
$results = [];
$this -> __getInstances ( $this -> param ( 'instances' ));
$server = null ;
if ( ! empty ( $this -> param [ 'server_url' ])) {
foreach ( $this -> __servers as $temp_server ) {
if ( $temp_server [ 'Server' ][ 'url' ] === $this -> param [ 'server_url' ]) {
$server = $temp_server ;
}
}
} else {
$server = $this -> __servers [ 0 ];
}
$HttpSocket = $this -> Server -> setupHttpSocket ( $server , null );
$request = $this -> Server -> setupSyncRequest ( $server );
2022-07-07 11:07:55 +02:00
$response = $HttpSocket -> get ( $server [ 'Server' ][ 'url' ] . '/organisations/index/scope:all' , false , $request );
2022-04-18 17:58:50 +02:00
$orgs = json_decode ( $response -> body (), true );
$this -> out ( __ ( 'Organisations fetched. %d found.' , count ( $orgs )), 1 , Shell :: VERBOSE );
$org_mapping = [];
foreach ( $orgs as $org ) {
2022-07-07 11:07:55 +02:00
if ( ! empty ( $this -> param ( 'org' )) && $org [ 'Organisation' ][ 'name' ] !== $this -> param ( 'org' )) {
continue ;
}
if ( $org [ 'Organisation' ][ 'name' ] === 'YT' ) {
2022-04-18 17:58:50 +02:00
continue ;
}
$org_mapping [ $org [ 'Organisation' ][ 'name' ]] = $org [ 'Organisation' ][ 'id' ];
}
2022-07-07 11:07:55 +02:00
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' ];
}
2022-04-18 17:58:50 +02:00
foreach ( $org_mapping as $org_name => $org_id ) {
$time_range = [];
$params = [
'org' => $org_id
];
if ( ! empty ( $time_range )) {
$params [ 'publish_timestamp' ] = $time_range ;
}
$response = $HttpSocket -> post ( $server [ 'Server' ][ 'url' ] . '/events/restSearch' , json_encode ( $params ), $request );
$events = json_decode ( $response -> body (), true );
$this -> out ( __ ( 'Events fetched from %s. %d found.' , $org_name , count ( $events [ 'response' ])), 1 , Shell :: VERBOSE );
$results [ $org_name ] = [
'attribute_count' => 0 ,
'object_count' => 0 ,
'connected_elements' => 0 ,
'event_tags' => 0 ,
'attribute_tags' => 0 ,
'attack' => 0 ,
'other' => 0 ,
'attribute_attack' => 0 ,
'attribute_other' => 0 ,
2022-07-07 11:07:55 +02:00
'score' => 0 ,
'warnings' => 0
2022-04-18 17:58:50 +02:00
];
foreach ( $events [ 'response' ] as $event ) {
if ( ! empty ( $event [ 'Event' ][ 'Tag' ])) {
foreach ( $event [ 'Event' ][ 'Tag' ] as $tag ) {
if ( substr ( $tag [ 'name' ], 0 , 32 ) === 'misp-galaxy:mitre-attack-pattern' ) {
$results [ $org_name ][ 'attack' ] += 1 ;
} else {
$results [ $org_name ][ 'other' ] += 1 ;
}
}
}
if ( ! empty ( $event [ 'Event' ][ 'Galaxy' ])) {
foreach ( $event [ 'Event' ][ 'Galaxy' ] as $galaxy ) {
if ( $galaxy [ 'type' ] === 'mitre-attack-pattern' ) {
$results [ $org_name ][ 'attack' ] += 1 ;
} else {
$results [ $org_name ][ 'other' ] += 1 ;
}
}
}
foreach ( $event [ 'Event' ][ 'Attribute' ] as $attribute ) {
if ( ! empty ( $attribute [ 'referenced_by' ])) {
$results [ $org_name ][ 'connected_elements' ] += 1 ;
}
if ( ! empty ( $attribute [ 'Tag' ])) {
foreach ( $attribute [ 'Tag' ] as $tag ) {
if ( substr ( $tag [ 'name' ], 0 , 32 ) === 'misp-galaxy:mitre-attack-pattern' ) {
$results [ $org_name ][ 'attribute_attack' ] += 1 ;
} else {
$results [ $org_name ][ 'attribute_other' ] += 1 ;
}
}
}
2022-07-07 11:07:55 +02:00
if ( ! empty ( $attribute [ 'warnings' ])) {
$result [ $org_name ][ 'warnings' ] += 1 ;
}
2022-04-18 17:58:50 +02:00
}
$results [ $org_name ][ 'attribute_count' ] += count ( $event [ 'Event' ][ 'Attribute' ]);
if ( ! empty ( $event [ 'Event' ][ 'Object' ])) {
foreach ( $event [ 'Event' ][ 'Object' ] as $object ) {
$results [ $org_name ][ 'attribute_count' ] += count ( $object [ 'Attribute' ]);
$results [ $org_name ][ 'object_count' ] += 1 ;
if ( ! empty ( $object [ 'ObjectReference' ])) {
$results [ $org_name ][ 'connected_elements' ] += 1 ;
}
foreach ( $object [ 'Attribute' ] as $attribute ) {
if ( ! empty ( $attribute [ 'Tag' ])) {
foreach ( $attribute [ 'Tag' ] as $tag ) {
if ( substr ( $tag [ 'name' ], 0 , 32 ) === 'misp-galaxy:mitre-attack-pattern' ) {
$results [ $org_name ][ 'attribute_attack' ] += 1 ;
} else {
$results [ $org_name ][ 'attribute_other' ] += 1 ;
}
}
}
}
}
}
}
}
$scores = [];
foreach ( $results as $k => $result ) {
$totalCount = $result [ 'attribute_count' ] + $result [ 'object_count' ];
if ( $totalCount ) {
2022-07-07 11:07:55 +02:00
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 ;
}
2022-04-18 17:58:50 +02:00
$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' ]);
}
2022-07-07 11:07:55 +02:00
foreach ([ 'connectedness' , 'attack_weight' , 'other_weight' , 'warnings' ] as $metric ) {
2022-04-18 17:58:50 +02:00
if ( empty ( $results [ $k ][ 'metrics' ][ $metric ])) {
$results [ $k ][ 'metrics' ][ $metric ] = 0 ;
}
if ( $results [ $k ][ 'metrics' ][ $metric ] > 100 ) {
$results [ $k ][ 'metrics' ][ $metric ] = 100 ;
}
}
2022-07-07 11:07:55 +02:00
$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' ]);
2022-04-18 17:58:50 +02:00
}
arsort ( $scores , SORT_DESC );
$this -> out ( str_repeat ( '=' , 128 ), 1 , Shell :: NORMAL );
$this -> out ( sprintf (
'| %s | %s | %s |' ,
str_pad ( 'Org' , 10 , ' ' , STR_PAD_RIGHT ),
str_pad ( 'Graph' , 100 , ' ' , STR_PAD_RIGHT ),
str_pad ( 'Score' , 8 , ' ' , STR_PAD_RIGHT )
), 1 , Shell :: NORMAL );
$this -> out ( str_repeat ( '=' , 128 ), 1 , Shell :: NORMAL );
foreach ( $scores as $org => $score ) {
2022-07-07 11:07:55 +02:00
$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 ));
2022-04-18 17:58:50 +02:00
$this -> out ( sprintf (
'| %s | %s | %s |' ,
str_pad ( $org , 10 , ' ' , STR_PAD_RIGHT ),
sprintf (
2022-07-07 11:07:55 +02:00
'<error>%s</error><warning>%s</warning><question>%s</question><info>%s</info>%s' ,
$score_string [ 0 ],
$score_string [ 1 ],
$score_string [ 2 ],
$score_string [ 3 ],
str_repeat ( ' ' , 100 - mb_strlen ( implode ( '' , $score_string )))
2022-04-18 17:58:50 +02:00
),
2022-07-07 11:07:55 +02:00
str_pad ( $score [ 'total' ] . '%' , 8 , ' ' , STR_PAD_RIGHT )
2022-04-18 17:58:50 +02:00
), 1 , Shell :: NORMAL );
}
$this -> out ( str_repeat ( '=' , 128 ), 1 , Shell :: NORMAL );
2022-07-07 11:07:55 +02:00
$this -> out ( sprintf (
'| Legend: %s %s %s %s %s |' ,
'<error>█: Warnings</error>' ,
'<warning>█: Connectedness</warning>' ,
'<question>█: ATT&CK context</question>' ,
'<info>█: Other Context</info>' ,
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 ));
2022-04-18 17:58:50 +02:00
}
}