TrustProxiesTest.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. <?php
  2. namespace Illuminate\Tests\Http\Middleware;
  3. use Illuminate\Http\Middleware\TrustProxies;
  4. use Illuminate\Http\Request;
  5. use PHPUnit\Framework\TestCase;
  6. class TrustProxiesTest extends TestCase
  7. {
  8. /**
  9. * A list of all proxy headers.
  10. *
  11. * @var int
  12. */
  13. protected $headerAll = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB;
  14. /**
  15. * Test that Symfony does indeed NOT trust X-Forwarded-*
  16. * headers when not given trusted proxies.
  17. *
  18. * This re-tests Symfony's Request class, but hopefully provides
  19. * some clarify to developers looking at the tests.
  20. */
  21. public function test_request_does_not_trust()
  22. {
  23. $req = $this->createProxiedRequest();
  24. $this->assertEquals('192.168.10.10', $req->getClientIp(), 'Assert untrusted proxy x-forwarded-for header not used');
  25. $this->assertEquals('http', $req->getScheme(), 'Assert untrusted proxy x-forwarded-proto header not used');
  26. $this->assertEquals('localhost', $req->getHost(), 'Assert untrusted proxy x-forwarded-host header not used');
  27. $this->assertEquals(8888, $req->getPort(), 'Assert untrusted proxy x-forwarded-port header not used');
  28. }
  29. /**
  30. * Test that Symfony DOES indeed trust X-Forwarded-*
  31. * headers when given trusted proxies.
  32. *
  33. * Again, this re-tests Symfony's Request class.
  34. */
  35. public function test_does_trust_trusted_proxy()
  36. {
  37. $req = $this->createProxiedRequest();
  38. $req->setTrustedProxies(['192.168.10.10'], $this->headerAll);
  39. $this->assertEquals('173.174.200.38', $req->getClientIp(), 'Assert trusted proxy x-forwarded-for header used');
  40. $this->assertEquals('https', $req->getScheme(), 'Assert trusted proxy x-forwarded-proto header used');
  41. $this->assertEquals('serversforhackers.com', $req->getHost(), 'Assert trusted proxy x-forwarded-host header used');
  42. $this->assertEquals(443, $req->getPort(), 'Assert trusted proxy x-forwarded-port header used');
  43. }
  44. /**
  45. * Test the next most typical usage of TrustedProxies:
  46. * Trusted X-Forwarded-For header, wilcard for TrustedProxies.
  47. */
  48. public function test_trusted_proxy_sets_trusted_proxies_with_wildcard()
  49. {
  50. $trustedProxy = $this->createTrustedProxy($this->headerAll, '*');
  51. $request = $this->createProxiedRequest();
  52. $trustedProxy->handle($request, function ($request) {
  53. $this->assertEquals('173.174.200.38', $request->getClientIp(), 'Assert trusted proxy x-forwarded-for header used with wildcard proxy setting');
  54. });
  55. }
  56. /**
  57. * Test the next most typical usage of TrustedProxies:
  58. * Trusted X-Forwarded-For header, wilcard for TrustedProxies.
  59. */
  60. public function test_trusted_proxy_sets_trusted_proxies_with_double_wildcard_for_backwards_compat()
  61. {
  62. $trustedProxy = $this->createTrustedProxy($this->headerAll, '**');
  63. $request = $this->createProxiedRequest();
  64. $trustedProxy->handle($request, function ($request) {
  65. $this->assertEquals('173.174.200.38', $request->getClientIp(), 'Assert trusted proxy x-forwarded-for header used with wildcard proxy setting');
  66. });
  67. }
  68. /**
  69. * Test the most typical usage of TrustProxies:
  70. * Trusted X-Forwarded-For header.
  71. */
  72. public function test_trusted_proxy_sets_trusted_proxies()
  73. {
  74. $trustedProxy = $this->createTrustedProxy($this->headerAll, ['192.168.10.10']);
  75. $request = $this->createProxiedRequest();
  76. $trustedProxy->handle($request, function ($request) {
  77. $this->assertEquals('173.174.200.38', $request->getClientIp(), 'Assert trusted proxy x-forwarded-for header used');
  78. });
  79. }
  80. /**
  81. * Test X-Forwarded-For header with multiple IP addresses.
  82. */
  83. public function test_get_client_ips()
  84. {
  85. $trustedProxy = $this->createTrustedProxy($this->headerAll, ['192.168.10.10']);
  86. $forwardedFor = [
  87. '192.0.2.2',
  88. '192.0.2.2, 192.0.2.199',
  89. '192.0.2.2, 192.0.2.199, 99.99.99.99',
  90. '192.0.2.2,192.0.2.199',
  91. ];
  92. foreach ($forwardedFor as $forwardedForHeader) {
  93. $request = $this->createProxiedRequest(['HTTP_X_FORWARDED_FOR' => $forwardedForHeader]);
  94. $trustedProxy->handle($request, function ($request) use ($forwardedForHeader) {
  95. $ips = $request->getClientIps();
  96. $this->assertEquals('192.0.2.2', end($ips), 'Assert sets the '.$forwardedForHeader);
  97. });
  98. }
  99. }
  100. /**
  101. * Test X-Forwarded-For header with multiple IP addresses, with some of those being trusted.
  102. */
  103. public function test_get_client_ip_with_muliple_ip_addresses_some_of_which_are_trusted()
  104. {
  105. $trustedProxy = $this->createTrustedProxy($this->headerAll, ['192.168.10.10', '192.0.2.199']);
  106. $forwardedFor = [
  107. '192.0.2.2',
  108. '192.0.2.2, 192.0.2.199',
  109. '99.99.99.99, 192.0.2.2, 192.0.2.199',
  110. '192.0.2.2,192.0.2.199',
  111. ];
  112. foreach ($forwardedFor as $forwardedForHeader) {
  113. $request = $this->createProxiedRequest(['HTTP_X_FORWARDED_FOR' => $forwardedForHeader]);
  114. $trustedProxy->handle($request, function ($request) use ($forwardedForHeader) {
  115. $this->assertEquals('192.0.2.2', $request->getClientIp(), 'Assert sets the '.$forwardedForHeader);
  116. });
  117. }
  118. }
  119. /**
  120. * Test X-Forwarded-For header with multiple IP addresses, with * wildcard trusting of all proxies.
  121. */
  122. public function test_get_client_ip_with_muliple_ip_addresses_all_proxies_are_trusted()
  123. {
  124. $trustedProxy = $this->createTrustedProxy($this->headerAll, '*');
  125. $forwardedFor = [
  126. '192.0.2.2',
  127. '192.0.2.199, 192.0.2.2',
  128. '192.0.2.199,192.0.2.2',
  129. '99.99.99.99,192.0.2.199,192.0.2.2',
  130. ];
  131. foreach ($forwardedFor as $forwardedForHeader) {
  132. $request = $this->createProxiedRequest(['HTTP_X_FORWARDED_FOR' => $forwardedForHeader]);
  133. $trustedProxy->handle($request, function ($request) use ($forwardedForHeader) {
  134. $this->assertEquals('192.0.2.2', $request->getClientIp(), 'Assert sets the '.$forwardedForHeader);
  135. });
  136. }
  137. }
  138. /**
  139. * Test distrusting a header.
  140. */
  141. public function test_can_distrust_headers()
  142. {
  143. $trustedProxy = $this->createTrustedProxy(Request::HEADER_FORWARDED, ['192.168.10.10']);
  144. $request = $this->createProxiedRequest([
  145. 'HTTP_FORWARDED' => 'for=173.174.200.40:443; proto=https; host=serversforhackers.com',
  146. 'HTTP_X_FORWARDED_FOR' => '173.174.200.38',
  147. 'HTTP_X_FORWARDED_HOST' => 'svrs4hkrs.com',
  148. 'HTTP_X_FORWARDED_PORT' => '80',
  149. 'HTTP_X_FORWARDED_PROTO' => 'http',
  150. ]);
  151. $trustedProxy->handle($request, function ($request) {
  152. $this->assertEquals('173.174.200.40', $request->getClientIp(),
  153. 'Assert trusted proxy used forwarded header for IP');
  154. $this->assertEquals('https', $request->getScheme(),
  155. 'Assert trusted proxy used forwarded header for scheme');
  156. $this->assertEquals('serversforhackers.com', $request->getHost(),
  157. 'Assert trusted proxy used forwarded header for host');
  158. $this->assertEquals(443, $request->getPort(), 'Assert trusted proxy used forwarded header for port');
  159. });
  160. }
  161. /**
  162. * Test that only the X-Forwarded-For header is trusted.
  163. */
  164. public function test_x_forwarded_for_header_only_trusted()
  165. {
  166. $trustedProxy = $this->createTrustedProxy(Request::HEADER_X_FORWARDED_FOR, '*');
  167. $request = $this->createProxiedRequest();
  168. $trustedProxy->handle($request, function ($request) {
  169. $this->assertEquals('173.174.200.38', $request->getClientIp(),
  170. 'Assert trusted proxy used forwarded header for IP');
  171. $this->assertEquals('http', $request->getScheme(),
  172. 'Assert trusted proxy did not use forwarded header for scheme');
  173. $this->assertEquals('localhost', $request->getHost(),
  174. 'Assert trusted proxy did not use forwarded header for host');
  175. $this->assertEquals(8888, $request->getPort(), 'Assert trusted proxy did not use forwarded header for port');
  176. });
  177. }
  178. /**
  179. * Test that only the X-Forwarded-Host header is trusted.
  180. */
  181. public function test_x_forwarded_host_header_only_trusted()
  182. {
  183. $trustedProxy = $this->createTrustedProxy(Request::HEADER_X_FORWARDED_HOST, '*');
  184. $request = $this->createProxiedRequest(['HTTP_X_FORWARDED_HOST' => 'serversforhackers.com:8888']);
  185. $trustedProxy->handle($request, function ($request) {
  186. $this->assertEquals('192.168.10.10', $request->getClientIp(),
  187. 'Assert trusted proxy did not use forwarded header for IP');
  188. $this->assertEquals('http', $request->getScheme(),
  189. 'Assert trusted proxy did not use forwarded header for scheme');
  190. $this->assertEquals('serversforhackers.com', $request->getHost(),
  191. 'Assert trusted proxy used forwarded header for host');
  192. $this->assertEquals(8888, $request->getPort(), 'Assert trusted proxy did not use forwarded header for port');
  193. });
  194. }
  195. /**
  196. * Test that only the X-Forwarded-Port header is trusted.
  197. */
  198. public function test_x_forwarded_port_header_only_trusted()
  199. {
  200. $trustedProxy = $this->createTrustedProxy(Request::HEADER_X_FORWARDED_PORT, '*');
  201. $request = $this->createProxiedRequest();
  202. $trustedProxy->handle($request, function ($request) {
  203. $this->assertEquals('192.168.10.10', $request->getClientIp(),
  204. 'Assert trusted proxy did not use forwarded header for IP');
  205. $this->assertEquals('http', $request->getScheme(),
  206. 'Assert trusted proxy did not use forwarded header for scheme');
  207. $this->assertEquals('localhost', $request->getHost(),
  208. 'Assert trusted proxy did not use forwarded header for host');
  209. $this->assertEquals(443, $request->getPort(), 'Assert trusted proxy used forwarded header for port');
  210. });
  211. }
  212. /**
  213. * Test that only the X-Forwarded-Proto header is trusted.
  214. */
  215. public function test_x_forwarded_proto_header_only_trusted()
  216. {
  217. $trustedProxy = $this->createTrustedProxy(Request::HEADER_X_FORWARDED_PROTO, '*');
  218. $request = $this->createProxiedRequest();
  219. $trustedProxy->handle($request, function ($request) {
  220. $this->assertEquals('192.168.10.10', $request->getClientIp(),
  221. 'Assert trusted proxy did not use forwarded header for IP');
  222. $this->assertEquals('https', $request->getScheme(),
  223. 'Assert trusted proxy used forwarded header for scheme');
  224. $this->assertEquals('localhost', $request->getHost(),
  225. 'Assert trusted proxy did not use forwarded header for host');
  226. $this->assertEquals(8888, $request->getPort(), 'Assert trusted proxy did not use forwarded header for port');
  227. });
  228. }
  229. /**
  230. * Test a combination of individual X-Forwarded-* headers are trusted.
  231. */
  232. public function test_x_forwarded_multiple_individual_headers_trusted()
  233. {
  234. $trustedProxy = $this->createTrustedProxy(
  235. Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST |
  236. Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO,
  237. '*'
  238. );
  239. $request = $this->createProxiedRequest();
  240. $trustedProxy->handle($request, function ($request) {
  241. $this->assertEquals('173.174.200.38', $request->getClientIp(),
  242. 'Assert trusted proxy used forwarded header for IP');
  243. $this->assertEquals('https', $request->getScheme(),
  244. 'Assert trusted proxy used forwarded header for scheme');
  245. $this->assertEquals('serversforhackers.com', $request->getHost(),
  246. 'Assert trusted proxy used forwarded header for host');
  247. $this->assertEquals(443, $request->getPort(), 'Assert trusted proxy used forwarded header for port');
  248. });
  249. }
  250. /**
  251. * Test to ensure it's reading text-based configurations and converting it correctly.
  252. */
  253. public function test_is_reading_text_based_configurations()
  254. {
  255. $request = $this->createProxiedRequest();
  256. // trust *all* "X-Forwarded-*" headers
  257. $trustedProxy = $this->createTrustedProxy('HEADER_X_FORWARDED_ALL', '192.168.1.1, 192.168.1.2');
  258. $trustedProxy->handle($request, function (Request $request) {
  259. $this->assertEquals($request->getTrustedHeaderSet(), $this->headerAll,
  260. 'Assert trusted proxy used all "X-Forwarded-*" header');
  261. $this->assertEquals($request->getTrustedProxies(), ['192.168.1.1', '192.168.1.2'],
  262. 'Assert trusted proxy using proxies as string separated by comma.');
  263. });
  264. // or, if your proxy instead uses the "Forwarded" header
  265. $trustedProxy = $this->createTrustedProxy('HEADER_FORWARDED', '192.168.1.1, 192.168.1.2');
  266. $trustedProxy->handle($request, function (Request $request) {
  267. $this->assertEquals($request->getTrustedHeaderSet(), Request::HEADER_FORWARDED,
  268. 'Assert trusted proxy used forwarded header');
  269. $this->assertEquals($request->getTrustedProxies(), ['192.168.1.1', '192.168.1.2'],
  270. 'Assert trusted proxy using proxies as string separated by comma.');
  271. });
  272. // or, if you're using AWS ELB
  273. $trustedProxy = $this->createTrustedProxy('HEADER_X_FORWARDED_AWS_ELB', '192.168.1.1, 192.168.1.2');
  274. $trustedProxy->handle($request, function (Request $request) {
  275. $this->assertEquals($request->getTrustedHeaderSet(), Request::HEADER_X_FORWARDED_AWS_ELB,
  276. 'Assert trusted proxy used AWS ELB header');
  277. $this->assertEquals($request->getTrustedProxies(), ['192.168.1.1', '192.168.1.2'],
  278. 'Assert trusted proxy using proxies as string separated by comma.');
  279. });
  280. }
  281. /**
  282. * Fake an HTTP request by generating a Symfony Request object.
  283. *
  284. * @param array $serverOverRides
  285. * @return \Symfony\Component\HttpFoundation\Request
  286. */
  287. protected function createProxiedRequest($serverOverRides = [])
  288. {
  289. // Add some X-Forwarded headers and over-ride
  290. // defaults, simulating a request made over a proxy
  291. $serverOverRides = array_replace([
  292. 'HTTP_X_FORWARDED_FOR' => '173.174.200.38', // X-Forwarded-For -- getClientIp()
  293. 'HTTP_X_FORWARDED_HOST' => 'serversforhackers.com', // X-Forwarded-Host -- getHosts()
  294. 'HTTP_X_FORWARDED_PORT' => '443', // X-Forwarded-Port -- getPort()
  295. 'HTTP_X_FORWARDED_PROTO' => 'https', // X-Forwarded-Proto -- getScheme() / isSecure()
  296. 'SERVER_PORT' => 8888,
  297. 'HTTP_HOST' => 'localhost',
  298. 'REMOTE_ADDR' => '192.168.10.10',
  299. ], $serverOverRides);
  300. // Create a fake request made over "http", one that we'd get over a proxy
  301. // which is likely something like this:
  302. $request = Request::create('http://localhost:8888/tag/proxy', 'GET', [], [], [], $serverOverRides, null);
  303. // Need to make sure these haven't already been set
  304. $request->setTrustedProxies([], $this->headerAll);
  305. return $request;
  306. }
  307. /**
  308. * Create an anonymous middleware class.
  309. *
  310. * @param null|string|int $trustedHeaders
  311. * @param null|array|string $trustedProxies
  312. * @return \Illuminate\Http\Middleware\TrustProxies
  313. */
  314. protected function createTrustedProxy($trustedHeaders, $trustedProxies)
  315. {
  316. return new class($trustedHeaders, $trustedProxies) extends TrustProxies
  317. {
  318. public function __construct($trustedHeaders, $trustedProxies)
  319. {
  320. $this->headers = $trustedHeaders;
  321. $this->proxies = $trustedProxies;
  322. }
  323. };
  324. }
  325. }