October CMS vulnerable to Potential Host Header Poisoning on misconfigured servers
Description
October is a free, open-source, self-hosted CMS platform based on the Laravel PHP Framework. In October before version 1.1.2, when running on poorly configured servers (i.e. the server routes any request, regardless of the HOST header to an October CMS instance) the potential exists for Host Header Poisoning attacks to succeed. This has been addressed in version 1.1.2 by adding a feature to allow a set of trusted hosts to be specified in the application. As a workaround one may set the configuration setting cms.linkPolicy to force.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
october/backendPackagist | < 1.1.2 | 1.1.2 |
Affected products
1- Range: < 1.1.2
Patches
4555ab61f2313Add app.trustedHosts config and force host checks on password reset (#5423)
2 files changed · +46 −1
config/app.php+31 −1 modified@@ -43,6 +43,36 @@ 'url' => 'http://localhost', + /* + |-------------------------------------------------------------------------- + | Trusted hosts + |-------------------------------------------------------------------------- + | + | You may specify valid hosts for your application as an array or boolean + | below. This helps prevent host header poisoning attacks. + | + | Possible values: + | - `true`: Trust the host specified in app.url, as well as the "www" + | subdomain, if applicable. + | - `false`: Disable the trusted hosts feature. + | - array: Defines the domains to be trusted hosts. Each item should be + | a string defining a domain, IP address, or a regex pattern. + | + | Example of array values: + | + | 'trustedHosts' => [ + | 'example.com', // Matches just example.com + | 'www.example.com', // Matches just www.example.com + | '^(.+\.)?example\.com$', // Matches example.com and all subdomains + | 'https://example.com', // Matches just example.com + | ], + | + | NOTE: Even when set to `false`, this functionality is explicitly enabled + | on the Backend password reset flow for security reasons. + */ + + 'trustedHosts' => true, + /* |-------------------------------------------------------------------------- | Application Timezone @@ -148,7 +178,7 @@ */ 'loadDiscoveredPackages' => false, - + /* |-------------------------------------------------------------------------- | Class Aliases
modules/backend/controllers/Auth.php+15 −0 modified@@ -13,6 +13,7 @@ use ValidationException; use Exception; use Config; +use October\Rain\Foundation\Http\Middleware\CheckForTrustedHost; /** * Authentication controller @@ -147,6 +148,20 @@ public function restore() */ public function restore_onSubmit() { + // Force Trusted Host verification on password reset link generation + // regardless of config to protect against host header poisoning + $trustedHosts = Config::get('app.trustedHosts', false); + if ($trustedHosts === false) { + $hosts = CheckForTrustedHost::processTrustedHosts(true); + + if (count($hosts)) { + Request::setTrustedHosts($hosts); + + // Trigger the host validation logic + Request::getHost(); + } + } + $rules = [ 'login' => 'required|between:2,255' ];
f638d3f78cfeAdd app.trustedHosts config and force host checks on password reset (#5423)
2 files changed · +45 −0
config/app.php+30 −0 modified@@ -43,6 +43,36 @@ 'url' => 'http://localhost', + /* + |-------------------------------------------------------------------------- + | Trusted hosts + |-------------------------------------------------------------------------- + | + | You may specify valid hosts for your application as an array or boolean + | below. This helps prevent host header poisoning attacks. + | + | Possible values: + | - `true`: Trust the host specified in app.url, as well as the "www" + | subdomain, if applicable. + | - `false`: Disable the trusted hosts feature. + | - array: Defines the domains to be trusted hosts. Each item should be + | a string defining a domain, IP address, or a regex pattern. + | + | Example of array values: + | + | 'trustedHosts' => [ + | 'example.com', // Matches just example.com + | 'www.example.com', // Matches just www.example.com + | '^(.+\.)?example\.com$', // Matches example.com and all subdomains + | 'https://example.com', // Matches just example.com + | ], + | + | NOTE: Even when set to `false`, this functionality is explicitly enabled + | on the Backend password reset flow for security reasons. + */ + + 'trustedHosts' => true, + /* |-------------------------------------------------------------------------- | Application Timezone
modules/backend/controllers/Auth.php+15 −0 modified@@ -13,6 +13,7 @@ use ValidationException; use Exception; use Config; +use October\Rain\Foundation\Http\Middleware\CheckForTrustedHost; /** * Authentication controller @@ -147,6 +148,20 @@ public function restore() */ public function restore_onSubmit() { + // Force Trusted Host verification on password reset link generation + // regardless of config to protect against host header poisoning + $trustedHosts = Config::get('app.trustedHosts', false); + if ($trustedHosts === false) { + $hosts = CheckForTrustedHost::processTrustedHosts(true); + + if (count($hosts)) { + Request::setTrustedHosts($hosts); + + // Trigger the host validation logic + Request::getHost(); + } + } + $rules = [ 'login' => 'required|between:2,255' ];
f29865ae3db7Add trusted hosts support to library (#549)
4 files changed · +377 −6
src/Foundation/Http/Kernel.php+7 −6 modified@@ -10,13 +10,13 @@ class Kernel extends HttpKernel * @var array */ protected $bootstrappers = [ - '\October\Rain\Foundation\Bootstrap\RegisterClassLoader', - '\October\Rain\Foundation\Bootstrap\LoadEnvironmentVariables', - '\October\Rain\Foundation\Bootstrap\LoadConfiguration', - '\October\Rain\Foundation\Bootstrap\LoadTranslation', + \October\Rain\Foundation\Bootstrap\RegisterClassLoader::class, + \October\Rain\Foundation\Bootstrap\LoadEnvironmentVariables::class, + \October\Rain\Foundation\Bootstrap\LoadConfiguration::class, + \October\Rain\Foundation\Bootstrap\LoadTranslation::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, - '\October\Rain\Foundation\Bootstrap\RegisterOctober', + \October\Rain\Foundation\Bootstrap\RegisterOctober::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; @@ -27,7 +27,8 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - '\October\Rain\Foundation\Http\Middleware\CheckForMaintenanceMode', + \October\Rain\Foundation\Http\Middleware\CheckForTrustedHost::class, + \October\Rain\Foundation\Http\Middleware\CheckForMaintenanceMode::class, ]; /**
src/Foundation/Http/Middleware/CheckForTrustedHost.php+82 −0 added@@ -0,0 +1,82 @@ +<?php namespace October\Rain\Foundation\Http\Middleware; + +use Config; +use October\Rain\Http\Middleware\TrustHosts as BaseMiddleware; + +class CheckForTrustedHost extends BaseMiddleware +{ + /** + * Get the host patterns that should be trusted. + * + * Trusted hosts should be defined in the `config/app.php` configuration file as an array, ie.: + * + * 'trustedHosts' => [ + * 'example.com', // Matches just example.com + * 'www.example.com', // Matches just www.example.com + * '^(.+\.)?example\.com$', // Matches example.com and all subdomains + * 'https://example.com', // Matches just example.com + * ], + * + * or as a boolean - if true, it will trust the `app.url` host and all subdomains, if false it + * will disable the feature entirely. + * + * Hosts can be defined as regex patterns for complex matching. + * + * @return array + */ + public function hosts() + { + return self::processTrustedHosts(Config::get('app.trustedHosts', [])); + } + + /** + * Processes the trusted hosts into an array of patterns the match for host header checks. + * + * @param array|bool $hosts + * @return array + */ + public static function processTrustedHosts($hosts) + { + if ($hosts === true) { + $url = Config::get('app.url', null); + + // If no app URL is set, then disable trusted hosts. + if (is_null($url)) { + return []; + } + + // Allow both the domain and the `www` subdomain for app.url + // regardless of the presence of www in the app.url value + $host = parse_url($url, PHP_URL_HOST); + if (preg_match('/^www\.(.*?)$/i', $host, $matches)) { + $host = '^(www\.)?' . preg_quote($matches[1]) . '$'; + } else { + $host = '^(www\.)?' . preg_quote($host) . '$'; + } + + $hosts = [$host]; + } elseif ($hosts === false) { + return []; + } + + $hosts = array_map(function ($host) { + // If a URL is provided, extract the host + if (filter_var($host, FILTER_VALIDATE_URL)) { + $host = parse_url($host, PHP_URL_HOST); + } + + // Prepare IP address & plain hostname values to be processed by the regex filter + if ( + filter_var($host, FILTER_VALIDATE_IP) + || filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) + ) { + return '^' . preg_quote($host) . '$'; + } + + // Allow everything else through as is + return $host; + }, $hosts); + + return $hosts; + } +}
src/Http/Middleware/TrustHosts.php+71 −0 added@@ -0,0 +1,71 @@ +<?php namespace October\Rain\Http\Middleware; + +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Http\Request; + +abstract class TrustHosts +{ + /** + * The application instance. + * + * @var \Illuminate\Contracts\Foundation\Application + */ + protected $app; + + /** + * Create a new middleware instance. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Get the host patterns that should be trusted. + * + * @return array + */ + abstract public function hosts(); + + /** + * Handle the incoming request. + * + * @param \Illuminate\Http\Request $request + * @param callable $next + * @return \Illuminate\Http\Response + */ + public function handle(Request $request, $next) + { + if ($this->shouldSpecifyTrustedHosts()) { + Request::setTrustedHosts(array_filter($this->hosts())); + } + + return $next($request); + } + + /** + * Determine if the application should specify trusted hosts. + * + * @return bool + */ + protected function shouldSpecifyTrustedHosts() + { + return $this->app['config']->get('app.env') !== 'local' + && $this->app->runningUnitTests() === false; + } + + /** + * Get a regular expression matching the application URL and all of its subdomains. + * + * @return string|null + */ + protected function allSubdomainsOfApplicationUrl() + { + if ($host = parse_url($this->app['config']->get('app.url'), PHP_URL_HOST)) { + return '^(.+\.)?'.preg_quote($host).'$'; + } + } +}
tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php+217 −0 added@@ -0,0 +1,217 @@ +<?php + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +use Illuminate\Routing\RouteCollection; +use October\Rain\Router\UrlGenerator; +use October\Rain\Foundation\Http\Middleware\CheckForTrustedHost; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; + +/** + * Adaptation of https://github.com/laravel/framework/pull/27206. Credit to @shrft for original implentation. + */ +class CheckForTrustedHostTest extends TestCase +{ + protected static $orignalTrustHosts; + + public static function setUpBeforeClass(): void + { + self::$orignalTrustHosts = Request::getTrustedHosts(); + } + + public static function tearDownAfterClass(): void + { + Request::setTrustedHosts(self::$orignalTrustHosts); + } + + public function testTrustedHost() + { + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://octobercms.com', $url); + } + + public function testTrustedHostWwwSubdomain() + { + $trustedHosts = ['www.octobercms.com']; + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://www.octobercms.com', $url); + } + + public function testTrustedHostWwwSubdomainFailure() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $urlGenerator->to('/'); + } + + public function testTrustedHostWwwRegex() + { + $trustedHosts = ['^(www\.)?octobercms\.com$']; + $headers = ['HOST' => 'octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://octobercms.com', $url); + + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://www.octobercms.com', $url); + } + + public function testTrustedIpHost() + { + $trustedHosts = ['127.0.0.1']; + $headers = ['HOST' => '127.0.0.1']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://127.0.0.1', $url); + } + + public function testNoTrustedHostsSet() + { + $trustedHosts = false; + $headers = ['HOST' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://malicious.com', $url); + } + + public function testThrowExceptionForUntrustedHosts() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $urlGenerator->to('/'); + } + + public function testThrowExceptionForUntrustedServerName() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = []; + $servers = ['SERVER_NAME' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testThrowExceptionForUntrustedServerAddr() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = []; + $servers = ['SERVER_ADDR' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testRegexTrustedHost() + { + $trustedHosts = ['^[a-z0-9]+\.octobercms\.com$']; + $headers = ['HOST' => 'test123.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test123.octobercms.com', $url); + + $headers = ['HOST' => 'test456.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test456.octobercms.com', $url); + } + + public function testRegexFailTrustedHost() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['^[a-z0-9]+\.octobercms\.com$']; + $headers = ['HOST' => 'test.123.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testArrayTrustedHost() + { + $trustedHosts = ['test1.octobercms.com', 'test2.octobercms.com']; + $headers = ['HOST' => 'test1.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test1.octobercms.com', $url); + + $headers = ['HOST' => 'test2.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test2.octobercms.com', $url); + } + + public function testArrayFailTrustedHost() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['test1.octobercms.com', 'test2.octobercms.com']; + $headers = ['HOST' => 'test3.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + protected function createUrlGenerator($trustedHosts = [], $headers = [], $servers = []) + { + $middleware = $this->getMockBuilder(CheckForTrustedHost::class) + ->disableOriginalConstructor() + ->setMethods(['hosts', 'shouldSpecifyTrustedHosts']) + ->getMock(); + + $middleware->expects($this->any()) + ->method('hosts') + ->willReturn(CheckForTrustedHost::processTrustedHosts($trustedHosts)); + + $middleware->expects($this->any()) + ->method('shouldSpecifyTrustedHosts') + ->willReturn(true); + + $request = new Request; + + foreach ($headers as $key => $val) { + $request->headers->set($key, $val); + } + + foreach ($servers as $key => $val) { + $request->server->set($key, $val); + } + + $middleware->handle($request, function () { + }); + + $routes = new RouteCollection; + $routes->add(new Route('GET', 'foo', [ + 'uses' => 'FooController@index', + 'as' => 'foo_index', + ])); + + return new UrlGenerator($routes, $request); + } +}
f86fcbcd066dAdd trusted hosts support to library (#549)
4 files changed · +377 −6
src/Foundation/Http/Kernel.php+7 −6 modified@@ -10,13 +10,13 @@ class Kernel extends HttpKernel * @var array */ protected $bootstrappers = [ - '\October\Rain\Foundation\Bootstrap\RegisterClassLoader', - '\October\Rain\Foundation\Bootstrap\LoadEnvironmentVariables', - '\October\Rain\Foundation\Bootstrap\LoadConfiguration', - '\October\Rain\Foundation\Bootstrap\LoadTranslation', + \October\Rain\Foundation\Bootstrap\RegisterClassLoader::class, + \October\Rain\Foundation\Bootstrap\LoadEnvironmentVariables::class, + \October\Rain\Foundation\Bootstrap\LoadConfiguration::class, + \October\Rain\Foundation\Bootstrap\LoadTranslation::class, \Illuminate\Foundation\Bootstrap\HandleExceptions::class, \Illuminate\Foundation\Bootstrap\RegisterFacades::class, - '\October\Rain\Foundation\Bootstrap\RegisterOctober', + \October\Rain\Foundation\Bootstrap\RegisterOctober::class, \Illuminate\Foundation\Bootstrap\RegisterProviders::class, \Illuminate\Foundation\Bootstrap\BootProviders::class, ]; @@ -27,7 +27,8 @@ class Kernel extends HttpKernel * @var array */ protected $middleware = [ - '\October\Rain\Foundation\Http\Middleware\CheckForMaintenanceMode', + \October\Rain\Foundation\Http\Middleware\CheckForTrustedHost::class, + \October\Rain\Foundation\Http\Middleware\CheckForMaintenanceMode::class, ]; /**
src/Foundation/Http/Middleware/CheckForTrustedHost.php+82 −0 added@@ -0,0 +1,82 @@ +<?php namespace October\Rain\Foundation\Http\Middleware; + +use Config; +use October\Rain\Http\Middleware\TrustHosts as BaseMiddleware; + +class CheckForTrustedHost extends BaseMiddleware +{ + /** + * Get the host patterns that should be trusted. + * + * Trusted hosts should be defined in the `config/app.php` configuration file as an array, ie.: + * + * 'trustedHosts' => [ + * 'example.com', // Matches just example.com + * 'www.example.com', // Matches just www.example.com + * '^(.+\.)?example\.com$', // Matches example.com and all subdomains + * 'https://example.com', // Matches just example.com + * ], + * + * or as a boolean - if true, it will trust the `app.url` host and all subdomains, if false it + * will disable the feature entirely. + * + * Hosts can be defined as regex patterns for complex matching. + * + * @return array + */ + public function hosts() + { + return self::processTrustedHosts(Config::get('app.trustedHosts', [])); + } + + /** + * Processes the trusted hosts into an array of patterns the match for host header checks. + * + * @param array|bool $hosts + * @return array + */ + public static function processTrustedHosts($hosts) + { + if ($hosts === true) { + $url = Config::get('app.url', null); + + // If no app URL is set, then disable trusted hosts. + if (is_null($url)) { + return []; + } + + // Allow both the domain and the `www` subdomain for app.url + // regardless of the presence of www in the app.url value + $host = parse_url($url, PHP_URL_HOST); + if (preg_match('/^www\.(.*?)$/i', $host, $matches)) { + $host = '^(www\.)?' . preg_quote($matches[1]) . '$'; + } else { + $host = '^(www\.)?' . preg_quote($host) . '$'; + } + + $hosts = [$host]; + } elseif ($hosts === false) { + return []; + } + + $hosts = array_map(function ($host) { + // If a URL is provided, extract the host + if (filter_var($host, FILTER_VALIDATE_URL)) { + $host = parse_url($host, PHP_URL_HOST); + } + + // Prepare IP address & plain hostname values to be processed by the regex filter + if ( + filter_var($host, FILTER_VALIDATE_IP) + || filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) + ) { + return '^' . preg_quote($host) . '$'; + } + + // Allow everything else through as is + return $host; + }, $hosts); + + return $hosts; + } +}
src/Http/Middleware/TrustHosts.php+71 −0 added@@ -0,0 +1,71 @@ +<?php namespace October\Rain\Http\Middleware; + +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Http\Request; + +abstract class TrustHosts +{ + /** + * The application instance. + * + * @var \Illuminate\Contracts\Foundation\Application + */ + protected $app; + + /** + * Create a new middleware instance. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return void + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Get the host patterns that should be trusted. + * + * @return array + */ + abstract public function hosts(); + + /** + * Handle the incoming request. + * + * @param \Illuminate\Http\Request $request + * @param callable $next + * @return \Illuminate\Http\Response + */ + public function handle(Request $request, $next) + { + if ($this->shouldSpecifyTrustedHosts()) { + Request::setTrustedHosts(array_filter($this->hosts())); + } + + return $next($request); + } + + /** + * Determine if the application should specify trusted hosts. + * + * @return bool + */ + protected function shouldSpecifyTrustedHosts() + { + return $this->app['config']->get('app.env') !== 'local' + && $this->app->runningUnitTests() === false; + } + + /** + * Get a regular expression matching the application URL and all of its subdomains. + * + * @return string|null + */ + protected function allSubdomainsOfApplicationUrl() + { + if ($host = parse_url($this->app['config']->get('app.url'), PHP_URL_HOST)) { + return '^(.+\.)?'.preg_quote($host).'$'; + } + } +}
tests/Foundation/Http/Middleware/CheckForTrustedHostTest.php+217 −0 added@@ -0,0 +1,217 @@ +<?php + +use Illuminate\Http\Request; +use Illuminate\Routing\Route; +use Illuminate\Routing\RouteCollection; +use October\Rain\Router\UrlGenerator; +use October\Rain\Foundation\Http\Middleware\CheckForTrustedHost; +use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; + +/** + * Adaptation of https://github.com/laravel/framework/pull/27206. Credit to @shrft for original implentation. + */ +class CheckForTrustedHostTest extends TestCase +{ + protected static $orignalTrustHosts; + + public static function setUpBeforeClass(): void + { + self::$orignalTrustHosts = Request::getTrustedHosts(); + } + + public static function tearDownAfterClass(): void + { + Request::setTrustedHosts(self::$orignalTrustHosts); + } + + public function testTrustedHost() + { + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://octobercms.com', $url); + } + + public function testTrustedHostWwwSubdomain() + { + $trustedHosts = ['www.octobercms.com']; + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://www.octobercms.com', $url); + } + + public function testTrustedHostWwwSubdomainFailure() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $urlGenerator->to('/'); + } + + public function testTrustedHostWwwRegex() + { + $trustedHosts = ['^(www\.)?octobercms\.com$']; + $headers = ['HOST' => 'octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://octobercms.com', $url); + + $headers = ['HOST' => 'www.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://www.octobercms.com', $url); + } + + public function testTrustedIpHost() + { + $trustedHosts = ['127.0.0.1']; + $headers = ['HOST' => '127.0.0.1']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://127.0.0.1', $url); + } + + public function testNoTrustedHostsSet() + { + $trustedHosts = false; + $headers = ['HOST' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://malicious.com', $url); + } + + public function testThrowExceptionForUntrustedHosts() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = ['HOST' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers); + $urlGenerator->to('/'); + } + + public function testThrowExceptionForUntrustedServerName() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = []; + $servers = ['SERVER_NAME' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testThrowExceptionForUntrustedServerAddr() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['octobercms.com']; + $headers = []; + $servers = ['SERVER_ADDR' => 'malicious.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testRegexTrustedHost() + { + $trustedHosts = ['^[a-z0-9]+\.octobercms\.com$']; + $headers = ['HOST' => 'test123.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test123.octobercms.com', $url); + + $headers = ['HOST' => 'test456.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test456.octobercms.com', $url); + } + + public function testRegexFailTrustedHost() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['^[a-z0-9]+\.octobercms\.com$']; + $headers = ['HOST' => 'test.123.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + public function testArrayTrustedHost() + { + $trustedHosts = ['test1.octobercms.com', 'test2.octobercms.com']; + $headers = ['HOST' => 'test1.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test1.octobercms.com', $url); + + $headers = ['HOST' => 'test2.octobercms.com']; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $url = $urlGenerator->to('/'); + + $this->assertEquals('http://test2.octobercms.com', $url); + } + + public function testArrayFailTrustedHost() + { + $this->expectException(SuspiciousOperationException::class); + + $trustedHosts = ['test1.octobercms.com', 'test2.octobercms.com']; + $headers = ['HOST' => 'test3.octobercms.com']; + $servers = []; + $urlGenerator = $this->createUrlGenerator($trustedHosts, $headers, $servers); + $urlGenerator->to('/'); + } + + protected function createUrlGenerator($trustedHosts = [], $headers = [], $servers = []) + { + $middleware = $this->getMockBuilder(CheckForTrustedHost::class) + ->disableOriginalConstructor() + ->setMethods(['hosts', 'shouldSpecifyTrustedHosts']) + ->getMock(); + + $middleware->expects($this->any()) + ->method('hosts') + ->willReturn(CheckForTrustedHost::processTrustedHosts($trustedHosts)); + + $middleware->expects($this->any()) + ->method('shouldSpecifyTrustedHosts') + ->willReturn(true); + + $request = new Request; + + foreach ($headers as $key => $val) { + $request->headers->set($key, $val); + } + + foreach ($servers as $key => $val) { + $request->server->set($key, $val); + } + + $middleware->handle($request, function () { + }); + + $routes = new RouteCollection; + $routes->add(new Route('GET', 'foo', [ + 'uses' => 'FooController@index', + 'as' => 'foo_index', + ])); + + return new UrlGenerator($routes, $request); + } +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-xhfx-hgmf-v6vpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-21265ghsaADVISORY
- github.com/octobercms/library/commit/f29865ae3db7a03be7c49294cd93980ec457f10dghsax_refsource_MISCWEB
- github.com/octobercms/library/commit/f86fcbcd066d6f8b939e8fe897409d152b11c3c6ghsax_refsource_MISCWEB
- github.com/octobercms/october/commit/555ab61f2313f45d7d5d138656420ead536c5d30ghsax_refsource_MISCWEB
- github.com/octobercms/october/commit/f638d3f78cfe91d7f6658820f9d5e424306a3db0ghsax_refsource_MISCWEB
- github.com/octobercms/october/security/advisories/GHSA-xhfx-hgmf-v6vpghsax_refsource_CONFIRMWEB
- packagist.org/packages/october/backendghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.