Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add trusted hosts support to library (#549)
Adds support for a new configuration value app.trustedHosts that defines the allowed hosts for the application. This allows a developer to prevent host header poisoning. Possible values: true: Trust the host specified in app.url, as well as all subdomains. false: Disable the trusted hosts feature. array: Defines the domains to be trusted hosts. Each item should be a string defining a domain, or a regex pattern. Related: octobercms/october#5423 (cherry picked from commit f29865a)
- Loading branch information
Ben Thomson
authored and
Luke Towers
committed
Jan 4, 2021
1 parent
b57093b
commit f86fcbc
Showing
4 changed files
with
377 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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).'$'; | ||
} | ||
} | ||
} |
Oops, something went wrong.