diff --git a/.gitignore b/.gitignore index 2c412cb..019c65e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.DS_Store /.idea/ /vendor/ /composer.lock +/build/*.phar \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index d089125..5c56450 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Nicolas Oelgart +Copyright (c) 2019 Nicolas Oelgart Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..232c398 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.DEFAULT_GOAL := build-phar +.PHONY: clean + +build-phar: + composer install --no-dev + php ./bin/create-phar.php ./build/httpsec.phar + +install: + cp ./build/httpsec.phar /usr/local/bin/httpsec + chmod u+x /usr/local/bin/httpsec + +test: + composer install --dev + ./vendor/bin/phpunit + +clean: + rm ./build/httpsec.phar diff --git a/README.md b/README.md index f89969f..a517c90 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ -# Security Headers Check +# httpsec (beta) -WIP +Test a site's HTTP headers for possible security issues + +**Basic usage** +```shell +$ httpsec https://www.target.com +``` + +**Advances usage** + +If you're trying to test a site that requires authentication, a POST request, or anything +of the like, you can use `curl` and pipe the result to `httpsec` +```shell +$ curl https://yahoo.com/ --head | httpsec +``` + +**Screenshot** + +![screenshot](resources/screenshots/screenshot.png) + +**Build** +```shell +$ make +``` + +**Test** +```shell +$ make test +``` + +**Install** +```shell +$ make install +``` diff --git a/bin/create-phar.php b/bin/create-phar.php new file mode 100755 index 0000000..539265d --- /dev/null +++ b/bin/create-phar.php @@ -0,0 +1,33 @@ +files()->in([ + $baseDir . '/bin', + $baseDir . '/config', + $baseDir . '/src', + $baseDir . '/vendor' +]); + +$phar = new Phar($pharFile); +$phar->setStub("#!/usr/bin/env php\ncompress(Phar::GZ); +$phar->buildFromIterator($finder->getIterator(), $baseDir); + +echo "$pharFile successfully created", PHP_EOL; diff --git a/bin/header-audit b/bin/run.php similarity index 96% rename from bin/header-audit rename to bin/run.php index 7937a7a..92b30bc 100755 --- a/bin/header-audit +++ b/bin/run.php @@ -1,5 +1,4 @@ -#!/usr/bin/env php -getHeadersFromString( - $this->getRawHeaders($url->redirectTo($headers->get('location')[0])) + $this->getRawHeaders($url->redirectTo($headers->getFirst('location'))) ); } diff --git a/src/Domain/Header/HttpHeaderBag.php b/src/Domain/Header/HttpHeaderBag.php index 8ccb5a6..79d1f7b 100644 --- a/src/Domain/Header/HttpHeaderBag.php +++ b/src/Domain/Header/HttpHeaderBag.php @@ -43,6 +43,11 @@ public function get(string $headerName): array return $headers; } + public function getFirst(string $headerName): HttpHeader + { + return $this->get($headerName)[0]; + } + /** @return HttpHeader|bool */ public function current() { diff --git a/src/Domain/Result/Warning/XFrameOptionsNotNecessaryDueToValidContentSecurityPolicyKudos.php b/src/Domain/Result/Warning/XFrameOptionsNotNecessaryDueToValidContentSecurityPolicyKudos.php new file mode 100644 index 0000000..152c3fc --- /dev/null +++ b/src/Domain/Result/Warning/XFrameOptionsNotNecessaryDueToValidContentSecurityPolicyKudos.php @@ -0,0 +1,15 @@ + + */ +namespace nicoSWD\SecHeaderCheck\Domain\Result\Warning; + +use nicoSWD\SecHeaderCheck\Domain\Result\Kudos; + +final class XFrameOptionsNotNecessaryDueToValidContentSecurityPolicyKudos extends Kudos +{ + protected $message = 'Not necessary due to valid Content-Security-Policy frame-ancestors'; +} diff --git a/src/Infrastructure/ResultPrinter/ConsoleResultPrinter.php b/src/Infrastructure/ResultPrinter/ConsoleResultPrinter.php index 2442225..e10cfb9 100644 --- a/src/Infrastructure/ResultPrinter/ConsoleResultPrinter.php +++ b/src/Infrastructure/ResultPrinter/ConsoleResultPrinter.php @@ -60,7 +60,7 @@ public function getOutput(AuditionResult $scanResults, OutputOptions $outputOpti $output .= ' ' . PHP_EOL ; } - $output .= PHP_EOL .'Total Score: ' . $scanResults->getScore() . ' out of 10 (Fail)'; +// $output .= PHP_EOL .'Total Score: ' . $scanResults->getScore() . ' out of 10 (Fail)'; return $output; } @@ -73,9 +73,10 @@ private function prettyName($headerName): string private function getWarnings(ObservationCollection $observations): string { $out = ''; + $c = 1; foreach ($observations as $observation) { - $out .= PHP_EOL . ' =>'; + $out .= PHP_EOL . ' ' . $c++ . ')'; if ($observation->isInfo()) { $out .= ' ' . (string) $observation . ' '; @@ -98,18 +99,18 @@ private function shortenHeaderValue(string $headerName, string $headerValue): st if ($headerName === SecurityHeader::SET_COOKIE) { $callback = function (array $match): string { if (strlen($match['value']) < 20) { - return $match['all']; + return $match['full_match']; } return sprintf( - '%s=s%s(...)%s', + '%s=%s(...)%s', $match['name'], substr($match['value'], 0, 8), substr($match['value'], -8) ); }; - return preg_replace_callback('~^(?(?.*?)=(?.*?;))~', $callback, $headerValue); + return preg_replace_callback('~(?(?.*?)=(?.*?;))~', $callback, $headerValue); } return $headerValue;