From fb98ca7b19934b0bcf499bdf2c22fe0d8286bbfe Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Sun, 30 Sep 2018 18:34:53 -0600 Subject: [PATCH] Added a new Security CLI command --- bin/grav | 1 + system/src/Grav/Common/Security.php | 20 ++++-- .../src/Grav/Console/Cli/SecurityCommand.php | 63 +++++++++++++------ 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/bin/grav b/bin/grav index faa8846f7..e36f0cc1c 100755 --- a/bin/grav +++ b/bin/grav @@ -41,5 +41,6 @@ $app->addCommands(array( new \Grav\Console\Cli\ClearCacheCommand(), new \Grav\Console\Cli\BackupCommand(), new \Grav\Console\Cli\NewProjectCommand(), + new \Grav\Console\Cli\SecurityCommand(), )); $app->run(); diff --git a/system/src/Grav/Common/Security.php b/system/src/Grav/Common/Security.php index b2f647110..9b345cb18 100644 --- a/system/src/Grav/Common/Security.php +++ b/system/src/Grav/Common/Security.php @@ -11,24 +11,32 @@ namespace Grav\Common; class Security { - public static function detectXssFromPages($pages, callable $messager = null) + public static function detectXssFromPages($pages, callable $status = null) { $routes = $pages->routes(); + // Remove duplicate for homepage + unset($routes['/']); + $list = []; +// // This needs Symfony 4.1 to work +// $status && $status([ +// 'type' => 'count', +// 'steps' => count($routes), +// ]); + foreach ($routes as $route => $path) { - $messager && $messager([ + $status && $status([ 'type' => 'progress', - 'percentage' => false, - 'complete' => false ]); try { $page = $pages->get($path); + // call the content to load/cache it - $header = $page->header(); + $header = (array) $page->header(); $content = $page->content(); $data = ['header' => $header, 'content' => $content]; @@ -43,7 +51,7 @@ class Security } } - dump($list); + return $list; } /** diff --git a/system/src/Grav/Console/Cli/SecurityCommand.php b/system/src/Grav/Console/Cli/SecurityCommand.php index 5e3568447..3361d44ce 100644 --- a/system/src/Grav/Console/Cli/SecurityCommand.php +++ b/system/src/Grav/Console/Cli/SecurityCommand.php @@ -13,12 +13,10 @@ use Grav\Common\Security; use Grav\Console\ConsoleCommand; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Style\SymfonyStyle; class SecurityCommand extends ConsoleCommand { - /** @var string $source */ - protected $source; - /** @var ProgressBar $progress */ protected $progress; @@ -29,13 +27,7 @@ class SecurityCommand extends ConsoleCommand { $this ->setName("security") - ->addArgument( - 'xss', - InputArgument::OPTIONAL, - 'Perform XSS security checks on all pages' - - ) - ->setDescription("Capable of running XSS Security checks") + ->setDescription("Capable of running various Security checks") ->setHelp('The security runs various security checks on your Grav site'); $this->source = getcwd(); @@ -46,34 +38,69 @@ class SecurityCommand extends ConsoleCommand */ protected function serve() { - $this->progress = new ProgressBar($this->output); - $this->progress->setFormat('Archiving %current% files [%bar%] %elapsed:6s% %memory:6s%'); + /** @var Grav $grav */ $grav = Grav::instance(); + $grav['uri']->init(); $grav['config']->init(); $grav['debugger']->enabled(false); + $grav['streams']; + $grav['plugins']->init(); + $grav['themes']->init(); + + $grav['twig']->init(); $grav['pages']->init(); - $results = Security::detectXssFromPages($grav['pages'], [$this, 'output']); + $this->progress = new ProgressBar($this->output, (count($grav['pages']->routes()) - 1)); + $this->progress->setFormat('Scanning %current% pages [%bar%] %percent:3s%% %elapsed:6s%'); + $this->progress->setBarWidth(100); - print_r($results); + $io = new SymfonyStyle($this->input, $this->output); + $io->title('Grav Security Check'); + + $output = Security::detectXssFromPages($grav['pages'], [$this, 'outputProgress']); + + $io->newline(2); + + if (!empty($output)) { + + $counter = 1; + foreach ($output as $route => $results) { + + $results_parts = array_map(function($value, $key) { + return $key.': \''.$value . '\''; + }, array_values($results), array_keys($results)); + + $io->writeln($counter++ .' - ' . $route . '' . implode(', ', $results_parts) . ''); + } + + $io->error('Security Scan complete: ' . count($output) . ' potential XSS issues found...'); + + } else { + $io->success('Security Scan complete: No issues found...'); + } + + $io->newline(1); } /** * @param $args */ - public function output($args) + public function outputProgress($args) { switch ($args['type']) { - case 'message': - $this->output->writeln($args['message']); + case 'count': + $steps = $args['steps']; + $freq = intval($steps > 100 ? round($steps / 100) : $steps); + $this->progress->setMaxSteps($steps); + $this->progress->setRedrawFrequency($freq); break; case 'progress': - if ($args['complete']) { + if (isset($args['complete']) && $args['complete']) { $this->progress->finish(); } else { $this->progress->advance();