Skip to content

Commit 5257b0e

Browse files
authored
Move Host Header Injection check to middleware (#1076)
* Move Host Header Injection check to middleware This moves the security check from bootstrap.php to a dedicated HostHeaderMiddleware. This fixes the issue where throwing an exception in bootstrap resulted in blank pages because error handling wasn't initialized yet. Benefits: - Errors are properly rendered by ErrorHandlerMiddleware - Logging works correctly since Log::setConfig() has already run - CLI commands are not affected - Validates Host header matches configured fullBaseUrl Addresses concerns raised in #1071. * Update middleware test to include HostHeaderMiddleware
1 parent 1487d36 commit 5257b0e

4 files changed

Lines changed: 74 additions & 20 deletions

File tree

config/bootstrap.php

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
use Cake\Cache\Cache;
3636
use Cake\Core\Configure;
3737
use Cake\Core\Configure\Engine\PhpConfig;
38-
use Cake\Core\Exception\CakeException;
3938
use Cake\Datasource\ConnectionManager;
4039
use Cake\Error\ErrorTrap;
4140
use Cake\Error\ExceptionTrap;
@@ -146,12 +145,11 @@
146145
}
147146

148147
/*
149-
* SECURITY: Validate and set the full base URL.
150-
* This URL is used as the base of all absolute links.
148+
* Set the full base URL for the application.
151149
*
152-
* IMPORTANT: In production, App.fullBaseUrl MUST be explicitly configured to prevent
153-
* Host Header Injection attacks. Relying on the HTTP_HOST header can allow attackers
154-
* to hijack password reset tokens and other security-critical operations.
150+
* SECURITY: In production, App.fullBaseUrl MUST be explicitly configured to prevent
151+
* Host Header Injection attacks. The HostHeaderMiddleware enforces this requirement
152+
* and validates incoming Host headers against the configured value.
155153
*
156154
* Set APP_FULL_BASE_URL in your environment variables or configure App.fullBaseUrl
157155
* in config/app.php or config/app_local.php
@@ -162,21 +160,10 @@
162160
if (!$fullBaseUrl) {
163161
$httpHost = env('HTTP_HOST');
164162

165-
/*
166-
* Only enforce fullBaseUrl requirement when we're in a web request context.
167-
* This allows CLI tools (like PHPStan) to load the bootstrap without throwing.
168-
*/
169-
if (!Configure::read('debug') && $httpHost) {
170-
throw new CakeException(
171-
'SECURITY: App.fullBaseUrl is not configured. ' .
172-
'This is required in production to prevent Host Header Injection attacks. ' .
173-
'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php',
174-
);
175-
}
176-
177163
/*
178164
* Development mode fallback: Use HTTP_HOST for convenience.
179-
* WARNING: This is ONLY safe in development. Never use this pattern in production!
165+
* WARNING: This is ONLY safe in development. In production, the
166+
* HostHeaderMiddleware will reject requests when fullBaseUrl is not configured.
180167
*/
181168
if ($httpHost) {
182169
$s = null;

src/Application.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
namespace App;
1818

19+
use App\Middleware\HostHeaderMiddleware;
1920
use Cake\Core\Configure;
2021
use Cake\Core\ContainerInterface;
2122
use Cake\Datasource\FactoryLocator;
@@ -66,6 +67,11 @@ public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
6667
// and make an error page/response
6768
->add(new ErrorHandlerMiddleware(Configure::read('Error'), $this))
6869

70+
// Validate Host header to prevent Host Header Injection attacks.
71+
// In production, ensures App.fullBaseUrl is configured and validates
72+
// the incoming Host header against it.
73+
->add(new HostHeaderMiddleware())
74+
6975
// Handle plugin/theme assets like CakePHP normally does.
7076
->add(new AssetMiddleware([
7177
'cacheTime' => Configure::read('Asset.cacheTime'),
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace App\Middleware;
5+
6+
use Cake\Core\Configure;
7+
use Cake\Http\Exception\BadRequestException;
8+
use Cake\Http\Exception\InternalErrorException;
9+
use Psr\Http\Message\ResponseInterface;
10+
use Psr\Http\Message\ServerRequestInterface;
11+
use Psr\Http\Server\MiddlewareInterface;
12+
use Psr\Http\Server\RequestHandlerInterface;
13+
14+
/**
15+
* Middleware to validate Host header and prevent Host Header Injection attacks.
16+
*
17+
* In production, this middleware ensures that App.fullBaseUrl is configured
18+
* and validates incoming Host headers against it. This prevents attackers
19+
* from manipulating password reset links and other security-critical URLs.
20+
*
21+
* @see https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/17-Testing_for_Host_Header_Injection
22+
*/
23+
class HostHeaderMiddleware implements MiddlewareInterface
24+
{
25+
/**
26+
* Process the request and validate the Host header.
27+
*
28+
* @param \Psr\Http\Message\ServerRequestInterface $request The request.
29+
* @param \Psr\Http\Server\RequestHandlerInterface $handler The request handler.
30+
* @return \Psr\Http\Message\ResponseInterface A response.
31+
*/
32+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
33+
{
34+
if (Configure::read('debug')) {
35+
return $handler->handle($request);
36+
}
37+
38+
$fullBaseUrl = Configure::read('App.fullBaseUrl');
39+
if (!$fullBaseUrl) {
40+
throw new InternalErrorException(
41+
'SECURITY: App.fullBaseUrl is not configured. ' .
42+
'This is required in production to prevent Host Header Injection attacks. ' .
43+
'Set APP_FULL_BASE_URL environment variable or configure App.fullBaseUrl in config/app.php',
44+
);
45+
}
46+
47+
$configuredHost = parse_url($fullBaseUrl, PHP_URL_HOST);
48+
$requestHost = $request->getUri()->getHost();
49+
50+
if ($configuredHost && $requestHost && strtolower($configuredHost) !== strtolower($requestHost)) {
51+
throw new BadRequestException(
52+
'Invalid Host header. Request host does not match configured application host.',
53+
);
54+
}
55+
56+
return $handler->handle($request);
57+
}
58+
}

tests/TestCase/ApplicationTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace App\Test\TestCase;
1818

1919
use App\Application;
20+
use App\Middleware\HostHeaderMiddleware;
2021
use Cake\Core\Configure;
2122
use Cake\Error\Middleware\ErrorHandlerMiddleware;
2223
use Cake\Http\MiddlewareQueue;
@@ -78,8 +79,10 @@ public function testMiddleware()
7879

7980
$this->assertInstanceOf(ErrorHandlerMiddleware::class, $middleware->current());
8081
$middleware->seek(1);
81-
$this->assertInstanceOf(AssetMiddleware::class, $middleware->current());
82+
$this->assertInstanceOf(HostHeaderMiddleware::class, $middleware->current());
8283
$middleware->seek(2);
84+
$this->assertInstanceOf(AssetMiddleware::class, $middleware->current());
85+
$middleware->seek(3);
8386
$this->assertInstanceOf(RoutingMiddleware::class, $middleware->current());
8487
}
8588
}

0 commit comments

Comments
 (0)