@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make it easier to parse "X-Forwarded-For" with one or more load balancers

Summary:
Fixes T13392. If you have 17 load balancers in sequence, Phabricator will receive requests with at least 17 "X-Forwarded-For" components in the header.

We want to select the 17th-from-last element, since prior elements are not trustworthy.

This currently isn't very easy/obvious, and you have to add a kind of sketchy piece of custom code to `preamble.php` to do any "X-Forwarded-For" parsing. Make handling this correctly easier.

Test Plan:
- Ran unit tests.
- Configured my local `preamble.php` to call `preamble_trust_x_forwarded_for_header(4)`, then made `/debug/` dump the header and the final value of `REMOTE_ADDR`.

```
$ curl http://local.phacility.com/debug/
<pre>

HTTP_X_FORWARDED_FOR =
FINAL REMOTE_ADDR = 127.0.0.1
</pre>
```

```
$ curl -H 'X-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6' http://local.phacility.com/debug/
<pre>

HTTP_X_FORWARDED_FOR = 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4, 5.5.5.5, 6.6.6.6
FINAL REMOTE_ADDR = 3.3.3.3
</pre>
```

```
$ curl -H 'X-Forwarded-For: 5.5.5.5, 6.6.6.6' http://local.phacility.com/debug/
<pre>

HTTP_X_FORWARDED_FOR = 5.5.5.5, 6.6.6.6
FINAL REMOTE_ADDR = 5.5.5.5
</pre>
```

Maniphest Tasks: T13392

Differential Revision: https://secure.phabricator.com/D20785

+187 -23
+2
src/__phutil_library_map__.php
··· 4201 4201 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', 4202 4202 'PhabricatorPolicyType' => 'applications/policy/constants/PhabricatorPolicyType.php', 4203 4203 'PhabricatorPonderApplication' => 'applications/ponder/application/PhabricatorPonderApplication.php', 4204 + 'PhabricatorPreambleTestCase' => 'infrastructure/util/__tests__/PhabricatorPreambleTestCase.php', 4204 4205 'PhabricatorPrimaryEmailUserLogType' => 'applications/people/userlog/PhabricatorPrimaryEmailUserLogType.php', 4205 4206 'PhabricatorProfileMenuEditEngine' => 'applications/search/editor/PhabricatorProfileMenuEditEngine.php', 4206 4207 'PhabricatorProfileMenuEditor' => 'applications/search/editor/PhabricatorProfileMenuEditor.php', ··· 10681 10682 ), 10682 10683 'PhabricatorPolicyType' => 'PhabricatorPolicyConstants', 10683 10684 'PhabricatorPonderApplication' => 'PhabricatorApplication', 10685 + 'PhabricatorPreambleTestCase' => 'PhabricatorTestCase', 10684 10686 'PhabricatorPrimaryEmailUserLogType' => 'PhabricatorUserLogType', 10685 10687 'PhabricatorProfileMenuEditEngine' => 'PhabricatorEditEngine', 10686 10688 'PhabricatorProfileMenuEditor' => 'PhabricatorApplicationTransactionEditor',
+28 -23
src/docs/user/configuration/configuring_preamble.diviner
··· 15 15 environment and some parts of Phabricator's configuration in order to fix these 16 16 problems and set up the environment which Phabricator expects. 17 17 18 - NOTE: This is an advanced feature. Most installs should not need to configure 19 - a preamble script. 20 18 21 - = Creating a Preamble Script = 19 + Creating a Preamble Script 20 + ========================== 22 21 23 22 To create a preamble script, write a file to: 24 23 ··· 37 36 request, allowing you to adjust the environment. For common adjustments and 38 37 examples, see the next sections. 39 38 39 + 40 40 Adjusting Client IPs 41 41 ==================== 42 42 ··· 44 44 all requests as originating from the load balancer, rather than from the 45 45 correct client IPs. 46 46 47 - If this is the case and some other header (like `X-Forwarded-For`) is known to 48 - be trustworthy, you can read the header and overwrite the `REMOTE_ADDR` value 49 - so Phabricator can figure out the client IP correctly. 47 + In common cases where networks are configured like this, the `X-Forwarded-For` 48 + header will have trustworthy information about the real client IP. You 49 + can use the function `preamble_trust_x_forwarded_for_header()` in your 50 + preamble to tell Phabricator that you expect to receive requests from a 51 + load balancer or proxy which modifies this header: 52 + 53 + ```name="Trust X-Forwarded-For Header", lang=php 54 + preamble_trust_x_forwarded_for_header(); 55 + ``` 50 56 51 57 You should do this //only// if the `X-Forwarded-For` header is known to be 52 58 trustworthy. In particular, if users can make requests to the web server ··· 54 60 spoof an arbitrary client IP. 55 61 56 62 The `X-Forwarded-For` header may also contain a list of addresses if a request 57 - has been forwarded through multiple loadbalancers. Using a snippet like this 58 - will usually handle most situations correctly: 63 + has been forwarded through multiple load balancers. If you know that requests 64 + on your network are routed through `N` trustworthy devices, you can specify 65 + that `N` to tell the function how many layers of `X-Forwarded-For` to discard: 59 66 67 + ```name="Trust X-Forwarded-For Header, Multiple Layers", lang=php 68 + preamble_trust_x_forwarded_for_header(3); 60 69 ``` 61 - name=Overwrite REMOTE_ADDR with X-Forwarded-For 62 - <?php 63 70 64 - // Overwrite REMOTE_ADDR with the value in the "X-Forwarded-For" HTTP header. 71 + If you have an unusual network configuration (for example, the number of 72 + trustworthy devices depends on the network path) you can also implement your 73 + own logic. 65 74 66 - // Only do this if you're certain the request is coming from a loadbalancer! 67 - // If the request came directly from a client, doing this will allow them to 68 - // them spoof any remote address. 75 + Note that this is very odd, advanced, and easy to get wrong. If you get it 76 + wrong, users will most likely be able to spoof any client address. 69 77 70 - // The header may contain a list of IPs, like "1.2.3.4, 4.5.6.7", if the 71 - // request the load balancer received also had this header. 78 + ```name="Custom X-Forwarded-For Handling", lang=php 72 79 73 80 if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 74 - $forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR']; 75 - if ($forwarded_for) { 76 - $forwarded_for = explode(',', $forwarded_for); 77 - $forwarded_for = end($forwarded_for); 78 - $forwarded_for = trim($forwarded_for); 79 - $_SERVER['REMOTE_ADDR'] = $forwarded_for; 80 - } 81 + $raw_header = $_SERVER['X_FORWARDED_FOR']; 82 + 83 + $real_address = your_custom_parsing_function($raw_header); 84 + 85 + $_SERVER['REMOTE_ADDR'] = $raw_header; 81 86 } 82 87 ``` 83 88
+5
src/infrastructure/env/PhabricatorEnv.php
··· 135 135 // TODO: Add a "locale.default" config option once we have some reasonable 136 136 // defaults which aren't silly nonsense. 137 137 self::setLocaleCode('en_US'); 138 + 139 + // Load the preamble utility library if we haven't already. On web 140 + // requests this loaded earlier, but we want to load it for non-web 141 + // requests so that unit tests can call these functions. 142 + require_once $phabricator_path.'/support/startup/preamble-utils.php'; 138 143 } 139 144 140 145 public static function beginScopedLocale($locale_code) {
+74
src/infrastructure/util/__tests__/PhabricatorPreambleTestCase.php
··· 1 + <?php 2 + 3 + final class PhabricatorPreambleTestCase 4 + extends PhabricatorTestCase { 5 + 6 + /** 7 + * @phutil-external-symbol function preamble_get_x_forwarded_for_address 8 + */ 9 + public function testXForwardedForLayers() { 10 + $tests = array( 11 + // This is normal behavior with one load balancer. 12 + array( 13 + 'header' => '1.2.3.4', 14 + 'layers' => 1, 15 + 'expect' => '1.2.3.4', 16 + ), 17 + 18 + // In this case, the LB received a request which already had an 19 + // "X-Forwarded-For" header. This might be legitimate (in the case of 20 + // a CDN request) or illegitimate (in the case of a client making 21 + // things up). We don't want to trust it. 22 + array( 23 + 'header' => '9.9.9.9, 1.2.3.4', 24 + 'layers' => 1, 25 + 'expect' => '1.2.3.4', 26 + ), 27 + 28 + // Multiple layers of load balancers. 29 + array( 30 + 'header' => '9.9.9.9, 1.2.3.4', 31 + 'layers' => 2, 32 + 'expect' => '9.9.9.9', 33 + ), 34 + 35 + // Multiple layers of load balancers, plus a client-supplied value. 36 + array( 37 + 'header' => '8.8.8.8, 9.9.9.9, 1.2.3.4', 38 + 'layers' => 2, 39 + 'expect' => '9.9.9.9', 40 + ), 41 + 42 + // Multiple layers of load balancers, but this request came from 43 + // somewhere inside the network. 44 + array( 45 + 'header' => '1.2.3.4', 46 + 'layers' => 2, 47 + 'expect' => '1.2.3.4', 48 + ), 49 + 50 + array( 51 + 'header' => 'A, B, C, D, E, F, G, H, I', 52 + 'layers' => 7, 53 + 'expect' => 'C', 54 + ), 55 + ); 56 + 57 + foreach ($tests as $test) { 58 + $header = $test['header']; 59 + $layers = $test['layers']; 60 + $expect = $test['expect']; 61 + 62 + $actual = preamble_get_x_forwarded_for_address($header, $layers); 63 + 64 + $this->assertEqual( 65 + $expect, 66 + $actual, 67 + pht( 68 + 'Address after stripping %d layers from: %s', 69 + $layers, 70 + $header)); 71 + } 72 + } 73 + 74 + }
+77
support/startup/preamble-utils.php
··· 1 + <?php 2 + 3 + /** 4 + * Parse the "X_FORWARDED_FOR" HTTP header to determine the original client 5 + * address. 6 + * 7 + * @param int Number of devices to trust. 8 + * @return void 9 + */ 10 + function preamble_trust_x_forwarded_for_header($layers = 1) { 11 + if (!is_int($layers) || ($layers < 1)) { 12 + echo 13 + 'preamble_trust_x_forwarded_for_header(<layers>): '. 14 + '"layers" parameter must an integer larger than 0.'."\n"; 15 + echo "\n"; 16 + exit(1); 17 + } 18 + 19 + if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 20 + return; 21 + } 22 + 23 + $forwarded_for = $_SERVER['HTTP_X_FORWARDED_FOR']; 24 + if (!strlen($forwarded_for)) { 25 + return; 26 + } 27 + 28 + $address = preamble_get_x_forwarded_for_address($forwarded_for, $layers); 29 + 30 + $_SERVER['REMOTE_ADDR'] = $address; 31 + } 32 + 33 + function preamble_get_x_forwarded_for_address($raw_header, $layers) { 34 + // The raw header may be a list of IPs, like "1.2.3.4, 4.5.6.7", if the 35 + // request the load balancer received also had this header. In particular, 36 + // this happens routinely with requests received through a CDN, but can also 37 + // happen illegitimately if the client just makes up an "X-Forwarded-For" 38 + // header full of lies. 39 + 40 + // We can only trust the N elements at the end of the list which correspond 41 + // to network-adjacent devices we control. Usually, we're behind a single 42 + // load balancer and "N" is 1, so we want to take the last element in the 43 + // list. 44 + 45 + // In some cases, "N" may be more than 1, if the network is configured so 46 + // that that requests are routed through multiple layers of load balancers 47 + // and proxies. In this case, we want to take the Nth-to-last element of 48 + // the list. 49 + 50 + $addresses = explode(',', $raw_header); 51 + 52 + // If we have more than one trustworthy device on the network path, discard 53 + // corresponding elements from the list. For example, if we have 7 devices, 54 + // we want to discard the last 6 elements of the list. 55 + 56 + // The final device address does not appear in the list, since devices do 57 + // not append their own addresses to "X-Forwarded-For". 58 + 59 + $discard_addresses = ($layers - 1); 60 + 61 + // However, we don't want to throw away all of the addresses. Some requests 62 + // may originate from within the network, and may thus not have as many 63 + // addresses as we expect. If we have fewer addresses than trustworthy 64 + // devices, discard all but one address. 65 + 66 + $max_discard = (count($addresses) - 1); 67 + 68 + $discard_count = min($discard_addresses, $max_discard); 69 + if ($discard_count) { 70 + $addresses = array_slice($addresses, 0, -$discard_count); 71 + } 72 + 73 + $original_address = end($addresses); 74 + $original_address = trim($original_address); 75 + 76 + return $original_address; 77 + }
+1
webroot/index.php
··· 85 85 require_once $root.'/support/startup/PhabricatorClientLimit.php'; 86 86 require_once $root.'/support/startup/PhabricatorClientRateLimit.php'; 87 87 require_once $root.'/support/startup/PhabricatorClientConnectionLimit.php'; 88 + require_once $root.'/support/startup/preamble-utils.php'; 88 89 89 90 // If the preamble script exists, load it. 90 91 $t_preamble = microtime(true);