security #cve-2018-14774 [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer (nicolas-grekas)
* commit '08a32d44b6': [HttpKernel] fix trusted headers management in HttpCache and InlineFragmentRenderer
This commit is contained in:
commit
9cfcaba0bf
|
@ -13,6 +13,7 @@ namespace Symfony\Component\HttpKernel\Fragment;
|
|||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\Controller\ControllerReference;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
@ -76,7 +77,7 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
|
|||
|
||||
$level = ob_get_level();
|
||||
try {
|
||||
return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||
return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false);
|
||||
} catch (\Exception $e) {
|
||||
// we dispatch the exception event to trigger the logging
|
||||
// the response that comes back is simply ignored
|
||||
|
@ -109,20 +110,6 @@ class InlineFragmentRenderer extends RoutableFragmentRenderer
|
|||
$cookies = $request->cookies->all();
|
||||
$server = $request->server->all();
|
||||
|
||||
// Override the arguments to emulate a sub-request.
|
||||
// Sub-request object will point to localhost as client ip and real client ip
|
||||
// will be included into trusted header for client ip
|
||||
try {
|
||||
if ($trustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) {
|
||||
$currentXForwardedFor = $request->headers->get($trustedHeaderName, '');
|
||||
|
||||
$server['HTTP_'.$trustedHeaderName] = ($currentXForwardedFor ? $currentXForwardedFor.', ' : '').$request->getClientIp();
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
$server['REMOTE_ADDR'] = '127.0.0.1';
|
||||
unset($server['HTTP_IF_MODIFIED_SINCE']);
|
||||
unset($server['HTTP_IF_NONE_MATCH']);
|
||||
|
||||
|
|
|
@ -464,27 +464,8 @@ class HttpCache implements HttpKernelInterface, TerminableInterface
|
|||
$this->surrogate->addSurrogateCapability($request);
|
||||
}
|
||||
|
||||
// modify the X-Forwarded-For header if needed
|
||||
$forwardedFor = $request->headers->get('X-Forwarded-For');
|
||||
if ($forwardedFor) {
|
||||
$request->headers->set('X-Forwarded-For', $forwardedFor.', '.$request->server->get('REMOTE_ADDR'));
|
||||
} else {
|
||||
$request->headers->set('X-Forwarded-For', $request->server->get('REMOTE_ADDR'));
|
||||
}
|
||||
|
||||
// fix the client IP address by setting it to 127.0.0.1 as HttpCache
|
||||
// is always called from the same process as the backend.
|
||||
$request->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
// make sure HttpCache is a trusted proxy
|
||||
if (!in_array('127.0.0.1', $trustedProxies = Request::getTrustedProxies())) {
|
||||
$trustedProxies[] = '127.0.0.1';
|
||||
Request::setTrustedProxies($trustedProxies);
|
||||
}
|
||||
|
||||
// always a "master" request (as the real master request can be in cache)
|
||||
$response = $this->kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, $catch);
|
||||
// FIXME: we probably need to also catch exceptions if raw === true
|
||||
$response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch);
|
||||
|
||||
// we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC
|
||||
if (null !== $entry && in_array($response->getStatusCode(), array(500, 502, 503, 504))) {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\HttpCache;
|
||||
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SubRequestHandler
|
||||
{
|
||||
/**
|
||||
* @return Response
|
||||
*/
|
||||
public static function handle(HttpKernelInterface $kernel, Request $request, $type, $catch)
|
||||
{
|
||||
// save global state related to trusted headers and proxies
|
||||
$trustedProxies = Request::getTrustedProxies();
|
||||
$trustedHeaders = array(
|
||||
Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED),
|
||||
Request::HEADER_CLIENT_IP => Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP),
|
||||
Request::HEADER_CLIENT_HOST => Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST),
|
||||
Request::HEADER_CLIENT_PROTO => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO),
|
||||
Request::HEADER_CLIENT_PORT => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT),
|
||||
);
|
||||
|
||||
// remove untrusted values
|
||||
$remoteAddr = $request->server->get('REMOTE_ADDR');
|
||||
if (!IpUtils::checkIp($remoteAddr, $trustedProxies)) {
|
||||
foreach (array_filter($trustedHeaders) as $name) {
|
||||
$request->headers->remove($name);
|
||||
}
|
||||
}
|
||||
|
||||
// compute trusted values, taking any trusted proxies into account
|
||||
$trustedIps = array();
|
||||
$trustedValues = array();
|
||||
foreach (array_reverse($request->getClientIps()) as $ip) {
|
||||
$trustedIps[] = $ip;
|
||||
$trustedValues[] = sprintf('for="%s"', $ip);
|
||||
}
|
||||
if ($ip !== $remoteAddr) {
|
||||
$trustedIps[] = $remoteAddr;
|
||||
$trustedValues[] = sprintf('for="%s"', $remoteAddr);
|
||||
}
|
||||
|
||||
// set trusted values, reusing as much as possible the global trusted settings
|
||||
if ($name = $trustedHeaders[Request::HEADER_FORWARDED]) {
|
||||
$trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme());
|
||||
$request->headers->set($name, implode(', ', $trustedValues));
|
||||
}
|
||||
if ($name = $trustedHeaders[Request::HEADER_CLIENT_IP]) {
|
||||
$request->headers->set($name, implode(', ', $trustedIps));
|
||||
}
|
||||
if (!$name && !$trustedHeaders[Request::HEADER_FORWARDED]) {
|
||||
$request->headers->set('X-Forwarded-For', implode(', ', $trustedIps));
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR');
|
||||
}
|
||||
|
||||
// fix the client IP address by setting it to 127.0.0.1,
|
||||
// which is the core responsibility of this method
|
||||
$request->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
|
||||
// ensure 127.0.0.1 is set as trusted proxy
|
||||
if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) {
|
||||
Request::setTrustedProxies(array_merge($trustedProxies, array('127.0.0.1')));
|
||||
}
|
||||
|
||||
try {
|
||||
$e = null;
|
||||
$response = $kernel->handle($request, $type, $catch);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
// restore global state
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaders[Request::HEADER_CLIENT_IP]);
|
||||
Request::setTrustedProxies($trustedProxies);
|
||||
|
||||
if (null !== $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -26,12 +26,16 @@ class InlineFragmentRendererTest extends TestCase
|
|||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->originalTrustedHeaderName = Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP);
|
||||
$this->originalTrustedHeaderNames = array(
|
||||
Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP),
|
||||
Request::getTrustedHeaderName(Request::HEADER_FORWARDED),
|
||||
);
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $this->originalTrustedHeaderName);
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $this->originalTrustedHeaderNames[0]);
|
||||
Request::setTrustedHeaderName(Request::HEADER_FORWARDED, $this->originalTrustedHeaderNames[1]);
|
||||
}
|
||||
|
||||
public function testRender()
|
||||
|
@ -55,7 +59,7 @@ class InlineFragmentRendererTest extends TestCase
|
|||
$subRequest = Request::create('/_fragment?_path=_format%3Dhtml%26_locale%3Den%26_controller%3Dmain_controller');
|
||||
$subRequest->attributes->replace(array('object' => $object, '_format' => 'html', '_controller' => 'main_controller', '_locale' => 'en'));
|
||||
$subRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$subRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
$subRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($subRequest));
|
||||
|
||||
|
@ -83,8 +87,12 @@ class InlineFragmentRendererTest extends TestCase
|
|||
public function testRenderWithTrustedHeaderDisabled()
|
||||
{
|
||||
Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, '');
|
||||
Request::setTrustedHeaderName(Request::HEADER_FORWARDED, '');
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest(Request::create('/')));
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
$this->assertSame('foo', $strategy->render('/', Request::create('/'))->getContent());
|
||||
}
|
||||
|
||||
|
@ -168,11 +176,10 @@ class InlineFragmentRendererTest extends TestCase
|
|||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
|
||||
if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) {
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
}
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
|
@ -194,16 +201,52 @@ class InlineFragmentRendererTest extends TestCase
|
|||
public function testHeadersPossiblyResultingIn304AreNotAssignedToSubrequest()
|
||||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
if (Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP)) {
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->server->set('HTTP_X_FORWARDED_FOR', '127.0.0.1');
|
||||
}
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
$request = Request::create('/', 'GET', array(), array(), array(), array('HTTP_IF_MODIFIED_SINCE' => 'Fri, 01 Jan 2016 00:00:00 GMT', 'HTTP_IF_NONE_MATCH' => '*'));
|
||||
$strategy->render('/', $request);
|
||||
}
|
||||
|
||||
public function testFirstTrustedProxyIsSetAsRemote()
|
||||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
Request::setTrustedProxies(array('1.1.1.1'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$strategy->render('/', $request);
|
||||
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
|
||||
public function testIpAddressOfRangedTrustedProxyIsSetAsRemote()
|
||||
{
|
||||
$expectedSubRequest = Request::create('/');
|
||||
$expectedSubRequest->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$expectedSubRequest->server->set('REMOTE_ADDR', '127.0.0.1');
|
||||
$expectedSubRequest->headers->set('x-forwarded-for', array('127.0.0.1'));
|
||||
$expectedSubRequest->headers->set('forwarded', array('for="127.0.0.1";host="localhost";proto=http'));
|
||||
|
||||
Request::setTrustedProxies(array('1.1.1.1/24'));
|
||||
|
||||
$strategy = new InlineFragmentRenderer($this->getKernelExpectingRequest($expectedSubRequest));
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->headers->set('Surrogate-Capability', 'abc="ESI/1.0"');
|
||||
$strategy->render('/', $request);
|
||||
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Kernel expecting a request equals to $request
|
||||
* Allows delta in comparison in case REQUEST_TIME changed by 1 second.
|
||||
|
|
|
@ -1301,64 +1301,72 @@ class HttpCacheTest extends HttpCacheTestCase
|
|||
$this->setNextResponse();
|
||||
$this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
|
||||
|
||||
$this->assertEquals('127.0.0.1', $this->kernel->getBackendRequest()->server->get('REMOTE_ADDR'));
|
||||
$that = $this;
|
||||
$this->kernel->assert(function ($backendRequest) use ($that) {
|
||||
$that->assertSame('127.0.0.1', $backendRequest->server->get('REMOTE_ADDR'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTrustedProxyData
|
||||
*/
|
||||
public function testHttpCacheIsSetAsATrustedProxy(array $existing, array $expected)
|
||||
public function testHttpCacheIsSetAsATrustedProxy(array $existing)
|
||||
{
|
||||
Request::setTrustedProxies($existing);
|
||||
|
||||
$this->setNextResponse();
|
||||
$this->request('GET', '/', array('REMOTE_ADDR' => '10.0.0.1'));
|
||||
$this->assertSame($existing, Request::getTrustedProxies());
|
||||
|
||||
$this->assertEquals($expected, Request::getTrustedProxies());
|
||||
$that = $this;
|
||||
$existing = array_unique(array_merge($existing, array('127.0.0.1')));
|
||||
$this->kernel->assert(function ($backendRequest) use ($existing, $that) {
|
||||
$that->assertSame($existing, Request::getTrustedProxies());
|
||||
$that->assertsame('10.0.0.1', $backendRequest->getClientIp());
|
||||
});
|
||||
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
|
||||
public function getTrustedProxyData()
|
||||
{
|
||||
return array(
|
||||
array(array(), array('127.0.0.1')),
|
||||
array(array('10.0.0.2'), array('10.0.0.2', '127.0.0.1')),
|
||||
array(array('10.0.0.2', '127.0.0.1'), array('10.0.0.2', '127.0.0.1')),
|
||||
array(array()),
|
||||
array(array('10.0.0.2')),
|
||||
array(array('10.0.0.2', '127.0.0.1')),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getXForwardedForData
|
||||
* @dataProvider getForwardedData
|
||||
*/
|
||||
public function testXForwarderForHeaderForForwardedRequests($xForwardedFor, $expected)
|
||||
public function testForwarderHeaderForForwardedRequests($forwarded, $expected)
|
||||
{
|
||||
$this->setNextResponse();
|
||||
$server = array('REMOTE_ADDR' => '10.0.0.1');
|
||||
if (false !== $xForwardedFor) {
|
||||
$server['HTTP_X_FORWARDED_FOR'] = $xForwardedFor;
|
||||
if (null !== $forwarded) {
|
||||
Request::setTrustedProxies($server);
|
||||
$server['HTTP_FORWARDED'] = $forwarded;
|
||||
}
|
||||
$this->request('GET', '/', $server);
|
||||
|
||||
$this->assertEquals($expected, $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
|
||||
$that = $this;
|
||||
$this->kernel->assert(function ($backendRequest) use ($expected, $that) {
|
||||
$that->assertSame($expected, $backendRequest->headers->get('Forwarded'));
|
||||
});
|
||||
|
||||
Request::setTrustedProxies(array());
|
||||
}
|
||||
|
||||
public function getXForwardedForData()
|
||||
public function getForwardedData()
|
||||
{
|
||||
return array(
|
||||
array(false, '10.0.0.1'),
|
||||
array('10.0.0.2', '10.0.0.2, 10.0.0.1'),
|
||||
array('10.0.0.2, 10.0.0.3', '10.0.0.2, 10.0.0.3, 10.0.0.1'),
|
||||
array(null, 'for="10.0.0.1";host="localhost";proto=http'),
|
||||
array('for=10.0.0.2', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.1"'),
|
||||
array('for=10.0.0.2, for=10.0.0.3', 'for="10.0.0.2";host="localhost";proto=http, for="10.0.0.3", for="10.0.0.1"'),
|
||||
);
|
||||
}
|
||||
|
||||
public function testXForwarderForHeaderForPassRequests()
|
||||
{
|
||||
$this->setNextResponse();
|
||||
$server = array('REMOTE_ADDR' => '10.0.0.1');
|
||||
$this->request('POST', '/', $server);
|
||||
|
||||
$this->assertEquals('10.0.0.1', $this->kernel->getBackendRequest()->headers->get('X-Forwarded-For'));
|
||||
}
|
||||
|
||||
public function testEsiCacheRemoveValidationHeadersIfEmbeddedResponses()
|
||||
{
|
||||
$time = \DateTime::createFromFormat('U', time());
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\HttpCache;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
|
||||
class SubRequestHandlerTest extends TestCase
|
||||
{
|
||||
private static $globalState;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
self::$globalState = $this->getGlobalState();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
foreach (self::$globalState[1] as $key => $name) {
|
||||
Request::setTrustedHeaderName($key, $name);
|
||||
}
|
||||
Request::setTrustedProxies(self::$globalState[0]);
|
||||
}
|
||||
|
||||
public function testTrustedHeadersAreKept()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'));
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'Good');
|
||||
$request->headers->set('X-Forwarded-Port', '1234');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
|
||||
$that = $this;
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) {
|
||||
$that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$that->assertSame('10.0.0.2', $request->getClientIp());
|
||||
$that->assertSame('Good', $request->headers->get('X-Forwarded-Host'));
|
||||
$that->assertSame('1234', $request->headers->get('X-Forwarded-Port'));
|
||||
$that->assertSame('https', $request->headers->get('X-Forwarded-Proto'));
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testUntrustedHeadersAreRemoved()
|
||||
{
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'Evil');
|
||||
$request->headers->set('X-Forwarded-Port', '1234');
|
||||
$request->headers->set('X-Forwarded-Proto', 'http');
|
||||
$request->headers->set('Forwarded', 'Evil2');
|
||||
|
||||
$that = $this;
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) {
|
||||
$that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$that->assertSame('10.0.0.1', $request->getClientIp());
|
||||
$that->assertFalse($request->headers->has('X-Forwarded-Host'));
|
||||
$that->assertFalse($request->headers->has('X-Forwarded-Port'));
|
||||
$that->assertFalse($request->headers->has('X-Forwarded-Proto'));
|
||||
$that->assertSame('for="10.0.0.1";host="localhost";proto=http', $request->headers->get('Forwarded'));
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame(self::$globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testTrustedForwardedHeader()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'));
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('Forwarded', 'for="10.0.0.2";host="foo.bar:1234";proto=https');
|
||||
|
||||
$that = $this;
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) {
|
||||
$that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$that->assertSame('10.0.0.2', $request->getClientIp());
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
public function testTrustedXForwardedForHeader()
|
||||
{
|
||||
Request::setTrustedProxies(array('10.0.0.1'));
|
||||
$globalState = $this->getGlobalState();
|
||||
|
||||
$request = Request::create('/');
|
||||
$request->server->set('REMOTE_ADDR', '10.0.0.1');
|
||||
$request->headers->set('X-Forwarded-For', '10.0.0.2');
|
||||
$request->headers->set('X-Forwarded-Host', 'foo.bar');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
|
||||
$that = $this;
|
||||
$kernel = new TestSubRequestHandlerKernel(function ($request, $type, $catch) use ($that) {
|
||||
$that->assertSame('127.0.0.1', $request->server->get('REMOTE_ADDR'));
|
||||
$that->assertSame('10.0.0.2', $request->getClientIp());
|
||||
$that->assertSame('foo.bar', $request->getHttpHost());
|
||||
$that->assertSame('https', $request->getScheme());
|
||||
});
|
||||
|
||||
SubRequestHandler::handle($kernel, $request, HttpKernelInterface::MASTER_REQUEST, true);
|
||||
|
||||
$this->assertSame($globalState, $this->getGlobalState());
|
||||
}
|
||||
|
||||
private function getGlobalState()
|
||||
{
|
||||
return array(
|
||||
Request::getTrustedProxies(),
|
||||
array(
|
||||
Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED),
|
||||
Request::HEADER_CLIENT_IP => Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP),
|
||||
Request::HEADER_CLIENT_HOST => Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST),
|
||||
Request::HEADER_CLIENT_PROTO => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO),
|
||||
Request::HEADER_CLIENT_PORT => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestSubRequestHandlerKernel implements HttpKernelInterface
|
||||
{
|
||||
private $assertCallback;
|
||||
|
||||
public function __construct(\Closure $assertCallback)
|
||||
{
|
||||
$this->assertCallback = $assertCallback;
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
|
||||
{
|
||||
$assertCallback = $this->assertCallback;
|
||||
$assertCallback($request, $type, $catch);
|
||||
|
||||
return new Response();
|
||||
}
|
||||
}
|
|
@ -34,19 +34,40 @@ class TestHttpKernel extends HttpKernel implements ControllerResolverInterface
|
|||
$this->status = $status;
|
||||
$this->headers = $headers;
|
||||
$this->customizer = $customizer;
|
||||
$this->trustedHeadersReflector = new \ReflectionProperty('Symfony\Component\HttpFoundation\Request', 'trustedHeaders');
|
||||
$this->trustedHeadersReflector->setAccessible(true);
|
||||
|
||||
parent::__construct(new EventDispatcher(), $this);
|
||||
}
|
||||
|
||||
public function getBackendRequest()
|
||||
public function assert(\Closure $callback)
|
||||
{
|
||||
return $this->backendRequest;
|
||||
$trustedConfig = array(Request::getTrustedProxies(), $this->trustedHeadersReflector->getValue());
|
||||
|
||||
list($trustedProxies, $trustedHeaders, $backendRequest) = $this->backendRequest;
|
||||
Request::setTrustedProxies($trustedProxies);
|
||||
$this->trustedHeadersReflector->setValue(null, $trustedHeaders);
|
||||
|
||||
try {
|
||||
$e = null;
|
||||
$callback($backendRequest);
|
||||
} catch (\Throwable $e) {
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
list($trustedProxies, $trustedHeaders) = $trustedConfig;
|
||||
Request::setTrustedProxies($trustedProxies);
|
||||
$this->trustedHeadersReflector->setValue(null, $trustedHeaders);
|
||||
|
||||
if (null !== $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = false)
|
||||
{
|
||||
$this->catch = $catch;
|
||||
$this->backendRequest = $request;
|
||||
$this->backendRequest = array($request::getTrustedProxies(), $this->trustedHeadersReflector->getValue(), $request);
|
||||
|
||||
return parent::handle($request, $type, $catch);
|
||||
}
|
||||
|
|
Reference in New Issue