Skip to content

Commit 4a7df00

Browse files
Merge branch 'codeigniter4:develop' into develop
2 parents 7b4d9b9 + cf53f0f commit 4a7df00

32 files changed

Lines changed: 540 additions & 161 deletions

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
# Changelog
22

3+
## [v4.2.11](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.11) (2022-12-21)
4+
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.10...v4.2.11)
5+
6+
### SECURITY
7+
* *Attackers may spoof IP address when using proxy* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-ghw3-5qvm-3mqc) for more information.
8+
* *Potential Session Handlers Vulnerability* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-6cq5-8cj7-g558) for more information.
9+
10+
### Fixed Bugs
11+
* fix: Request::getIPAddress() by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6820
12+
* fix: Model cannot insert when $useAutoIncrement is false by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6827
13+
* fix: View Parser regexp does not support UTF-8 by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6835
14+
* Handle key generation when key is not present in .env by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6839
15+
* Fix: Controller Test withBody() by @MGatner in https://github.com/codeigniter4/CodeIgniter4/pull/6843
16+
* fix: body assigned via options array in CURLRequest class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6854
17+
* Fix CreateDatabase leaving altered database config in connection by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6856
18+
* fix: cast to string all values except arrays in Header class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6862
19+
* add missing @method Query grouping in Model by @paul45 in https://github.com/codeigniter4/CodeIgniter4/pull/6874
20+
* fix: `composer update` might cause error "Failed to open directory" by @LeMyst in https://github.com/codeigniter4/CodeIgniter4/pull/6833
21+
* fix: required PHP extentions by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6897
22+
* fix: Use Services for the FeatureTestTrait request. by @lonnieezell in https://github.com/codeigniter4/CodeIgniter4/pull/6966
23+
* fix: FileLocator::locateFile() bug with a similar namespace name by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6964
24+
* fix: socket connection in RedisHandler class by @michalsn in https://github.com/codeigniter4/CodeIgniter4/pull/6972
25+
* fix: `spark namespaces` cannot show a namespace with mutilple paths by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6977
26+
* fix: Undefined constant "CodeIgniter\Debug\VENDORPATH" by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6985
27+
* fix: large HTTP input crashes framework by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6984
28+
* fix: empty paths for `rewrite.php` by @datamweb in https://github.com/codeigniter4/CodeIgniter4/pull/6991
29+
* fix: `PHPStan` $cols not defined in `CLI` by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6994
30+
* Fix MigrationRunnerTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6855
31+
* fix: turn off `Xdebug` note when running phpstan by @ddevsr in https://github.com/codeigniter4/CodeIgniter4/pull/6851
32+
* Fix ShowTableInfoTest to pass on Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6853
33+
* Fix MigrateStatusTest for Windows by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6866
34+
* Fix ShowTableInfoTest when migration records are numerous by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6868
35+
* Fix CreateDatabaseTest to not leave database by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6867
36+
* Fix coverage merge warning by @paulbalandan in https://github.com/codeigniter4/CodeIgniter4/pull/6885
37+
* fix: replace tabs to spaces by @zl59503020 in https://github.com/codeigniter4/CodeIgniter4/pull/6898
38+
* fix: slack links by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6907
39+
* Fix typo in database/queries.rst by @philFernandez in https://github.com/codeigniter4/CodeIgniter4/pull/6920
40+
* Fix testInsertWithSetAndEscape to make not time dependent by @sclubricants in https://github.com/codeigniter4/CodeIgniter4/pull/6974
41+
* fix: remove unnecessary global variables in rewrite.php by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6973
42+
343
## [v4.2.10](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.10) (2022-11-05)
444
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.9...v4.2.10)
545

@@ -41,6 +81,9 @@
4181
## [v4.2.7](https://github.com/codeigniter4/CodeIgniter4/tree/v4.2.7) (2022-10-06)
4282
[Full Changelog](https://github.com/codeigniter4/CodeIgniter4/compare/v4.2.6...v4.2.7)
4383

84+
### SECURITY
85+
* *Secure or HttpOnly flag set in Config\Cookie is not reflected in Cookies issued* was fixed. See the [Security advisory](https://github.com/codeigniter4/CodeIgniter4/security/advisories/GHSA-745p-r637-7vvp) for more information.
86+
4487
### Breaking Changes
4588
* fix: make Time::__toString() database-compatible on any locale by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6461
4689
* fix: set_cookie() does not use Config\Cookie values by @kenjis in https://github.com/codeigniter4/CodeIgniter4/pull/6544

admin/framework/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"require-dev": {
1717
"codeigniter/coding-standard": "^1.5",
1818
"fakerphp/faker": "^1.9",
19-
"friendsofphp/php-cs-fixer": "~3.13.0",
19+
"friendsofphp/php-cs-fixer": "3.13.0",
2020
"mikey179/vfsstream": "^1.6",
2121
"nexusphp/cs-config": "^3.6",
2222
"phpunit/phpunit": "^9.1",

app/Config/App.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,18 +332,21 @@ class App extends BaseConfig
332332
*
333333
* If your server is behind a reverse proxy, you must whitelist the proxy
334334
* IP addresses from which CodeIgniter should trust headers such as
335-
* HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
335+
* X-Forwarded-For or Client-IP in order to properly identify
336336
* the visitor's IP address.
337337
*
338-
* You can use both an array or a comma-separated list of proxy addresses,
339-
* as well as specifying whole subnets. Here are a few examples:
338+
* You need to set a proxy IP address or IP address with subnets and
339+
* the HTTP header for the client IP address.
340340
*
341-
* Comma-separated: '10.0.1.200,192.168.5.0/24'
342-
* Array: ['10.0.1.200', '192.168.5.0/24']
341+
* Here are some examples:
342+
* [
343+
* '10.0.1.200' => 'X-Forwarded-For',
344+
* '192.168.5.0/24' => 'X-Real-IP',
345+
* ]
343346
*
344-
* @var string|string[]
347+
* @var array<string, string>
345348
*/
346-
public $proxyIPs = '';
349+
public $proxyIPs = [];
347350

348351
/**
349352
* --------------------------------------------------------------------------

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"require-dev": {
1717
"codeigniter/coding-standard": "^1.5",
1818
"fakerphp/faker": "^1.9",
19-
"friendsofphp/php-cs-fixer": "~3.13.0",
19+
"friendsofphp/php-cs-fixer": "3.13.0",
2020
"mikey179/vfsstream": "^1.6",
2121
"nexusphp/cs-config": "^3.6",
2222
"nexusphp/tachycardia": "^1.0",

phpstan-baseline.neon.dist

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,6 @@ parameters:
500500
count: 1
501501
path: system/HTTP/RedirectResponse.php
502502

503-
-
504-
message: "#^Property CodeIgniter\\\\HTTP\\\\Request\\:\\:\\$proxyIPs \\(array\\|string\\) on left side of \\?\\? is not nullable\\.$#"
505-
count: 1
506-
path: system/HTTP/Request.php
507-
508503
-
509504
message: "#^Property CodeIgniter\\\\HTTP\\\\Request\\:\\:\\$uri \\(CodeIgniter\\\\HTTP\\\\URI\\) in empty\\(\\) is not falsy\\.$#"
510505
count: 1

system/CodeIgniter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class CodeIgniter
4747
/**
4848
* The current version of CodeIgniter Framework
4949
*/
50-
public const CI_VERSION = '4.2.10';
50+
public const CI_VERSION = '4.2.11';
5151

5252
/**
5353
* App startup time.

system/Commands/Server/rewrite.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
return;
2525
}
2626

27-
$uri = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
27+
$uri = urldecode(
28+
parse_url('https://codeigniter.com' . $_SERVER['REQUEST_URI'], PHP_URL_PATH) ?? ''
29+
);
2830

2931
// All request handle by index.php file.
3032
$_SERVER['SCRIPT_NAME'] = '/index.php';

system/HTTP/Request.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Request extends Message implements MessageInterface, RequestInterface
2323
/**
2424
* Proxy IPs
2525
*
26-
* @var array|string
26+
* @var array<string, string>
2727
*
2828
* @deprecated Check the App config directly
2929
*/

system/HTTP/RequestTrait.php

Lines changed: 87 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace CodeIgniter\HTTP;
1313

14+
use CodeIgniter\Exceptions\ConfigException;
1415
use CodeIgniter\Validation\FormatRules;
1516

1617
/**
@@ -43,7 +44,9 @@ trait RequestTrait
4344
/**
4445
* Gets the user's IP address.
4546
*
46-
* @return string IP address
47+
* @return string IP address if it can be detected, or empty string.
48+
* If the IP address is not a valid IP address,
49+
* then will return '0.0.0.0'.
4750
*/
4851
public function getIPAddress(): string
4952
{
@@ -59,93 +62,84 @@ public function getIPAddress(): string
5962
/**
6063
* @deprecated $this->proxyIPs property will be removed in the future
6164
*/
65+
// @phpstan-ignore-next-line
6266
$proxyIPs = $this->proxyIPs ?? config('App')->proxyIPs;
63-
if (! empty($proxyIPs) && ! is_array($proxyIPs)) {
64-
$proxyIPs = explode(',', str_replace(' ', '', $proxyIPs));
67+
// @phpstan-ignore-next-line
68+
if (! empty($proxyIPs) && (! is_array($proxyIPs) || is_int(array_key_first($proxyIPs)))) {
69+
throw new ConfigException(
70+
'You must set an array with Proxy IP address key and HTTP header name value in Config\App::$proxyIPs.'
71+
);
6572
}
6673

6774
$this->ipAddress = $this->getServer('REMOTE_ADDR');
6875

6976
if ($proxyIPs) {
70-
foreach (['x-forwarded-for', 'client-ip', 'x-client-ip', 'x-cluster-client-ip'] as $header) {
71-
$spoof = null;
72-
$headerObj = $this->header($header);
73-
74-
if ($headerObj !== null) {
75-
$spoof = $headerObj->getValue();
76-
77-
// Some proxies typically list the whole chain of IP
78-
// addresses through which the client has reached us.
79-
// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
80-
sscanf($spoof, '%[^,]', $spoof);
81-
82-
if (! $ipValidator($spoof)) {
83-
$spoof = null;
84-
} else {
85-
break;
86-
}
87-
}
88-
}
89-
90-
if ($spoof) {
91-
foreach ($proxyIPs as $proxyIP) {
92-
// Check if we have an IP address or a subnet
93-
if (strpos($proxyIP, '/') === false) {
94-
// An IP address (and not a subnet) is specified.
95-
// We can compare right away.
96-
if ($proxyIP === $this->ipAddress) {
77+
// @TODO Extract all this IP address logic to another class.
78+
foreach ($proxyIPs as $proxyIP => $header) {
79+
// Check if we have an IP address or a subnet
80+
if (strpos($proxyIP, '/') === false) {
81+
// An IP address (and not a subnet) is specified.
82+
// We can compare right away.
83+
if ($proxyIP === $this->ipAddress) {
84+
$spoof = $this->getClientIP($header);
85+
86+
if ($spoof !== null) {
9787
$this->ipAddress = $spoof;
9888
break;
9989
}
100-
101-
continue;
10290
}
10391

104-
// We have a subnet ... now the heavy lifting begins
105-
if (! isset($separator)) {
106-
$separator = $ipValidator($this->ipAddress, 'ipv6') ? ':' : '.';
107-
}
92+
continue;
93+
}
10894

109-
// If the proxy entry doesn't match the IP protocol - skip it
110-
if (strpos($proxyIP, $separator) === false) {
111-
continue;
112-
}
95+
// We have a subnet ... now the heavy lifting begins
96+
if (! isset($separator)) {
97+
$separator = $ipValidator($this->ipAddress, 'ipv6') ? ':' : '.';
98+
}
11399

114-
// Convert the REMOTE_ADDR IP address to binary, if needed
115-
if (! isset($ip, $sprintf)) {
116-
if ($separator === ':') {
117-
// Make sure we're have the "full" IPv6 format
118-
$ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
100+
// If the proxy entry doesn't match the IP protocol - skip it
101+
if (strpos($proxyIP, $separator) === false) {
102+
continue;
103+
}
119104

120-
for ($j = 0; $j < 8; $j++) {
121-
$ip[$j] = intval($ip[$j], 16);
122-
}
105+
// Convert the REMOTE_ADDR IP address to binary, if needed
106+
if (! isset($ip, $sprintf)) {
107+
if ($separator === ':') {
108+
// Make sure we're having the "full" IPv6 format
109+
$ip = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($this->ipAddress, ':')), $this->ipAddress));
123110

124-
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
125-
} else {
126-
$ip = explode('.', $this->ipAddress);
127-
$sprintf = '%08b%08b%08b%08b';
111+
for ($j = 0; $j < 8; $j++) {
112+
$ip[$j] = intval($ip[$j], 16);
128113
}
129114

130-
$ip = vsprintf($sprintf, $ip);
115+
$sprintf = '%016b%016b%016b%016b%016b%016b%016b%016b';
116+
} else {
117+
$ip = explode('.', $this->ipAddress);
118+
$sprintf = '%08b%08b%08b%08b';
131119
}
132120

133-
// Split the netmask length off the network address
134-
sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
121+
$ip = vsprintf($sprintf, $ip);
122+
}
135123

136-
// Again, an IPv6 address is most likely in a compressed form
137-
if ($separator === ':') {
138-
$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
124+
// Split the netmask length off the network address
125+
sscanf($proxyIP, '%[^/]/%d', $netaddr, $masklen);
139126

140-
for ($i = 0; $i < 8; $i++) {
141-
$netaddr[$i] = intval($netaddr[$i], 16);
142-
}
143-
} else {
144-
$netaddr = explode('.', $netaddr);
127+
// Again, an IPv6 address is most likely in a compressed form
128+
if ($separator === ':') {
129+
$netaddr = explode(':', str_replace('::', str_repeat(':', 9 - substr_count($netaddr, ':')), $netaddr));
130+
131+
for ($i = 0; $i < 8; $i++) {
132+
$netaddr[$i] = intval($netaddr[$i], 16);
145133
}
134+
} else {
135+
$netaddr = explode('.', $netaddr);
136+
}
137+
138+
// Convert to binary and finally compare
139+
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
140+
$spoof = $this->getClientIP($header);
146141

147-
// Convert to binary and finally compare
148-
if (strncmp($ip, vsprintf($sprintf, $netaddr), $masklen) === 0) {
142+
if ($spoof !== null) {
149143
$this->ipAddress = $spoof;
150144
break;
151145
}
@@ -160,6 +154,34 @@ public function getIPAddress(): string
160154
return empty($this->ipAddress) ? '' : $this->ipAddress;
161155
}
162156

157+
/**
158+
* Gets the client IP address from the HTTP header.
159+
*/
160+
private function getClientIP(string $header): ?string
161+
{
162+
$ipValidator = [
163+
new FormatRules(),
164+
'valid_ip',
165+
];
166+
$spoof = null;
167+
$headerObj = $this->header($header);
168+
169+
if ($headerObj !== null) {
170+
$spoof = $headerObj->getValue();
171+
172+
// Some proxies typically list the whole chain of IP
173+
// addresses through which the client has reached us.
174+
// e.g. client_ip, proxy_ip1, proxy_ip2, etc.
175+
sscanf($spoof, '%[^,]', $spoof);
176+
177+
if (! $ipValidator($spoof)) {
178+
$spoof = null;
179+
}
180+
}
181+
182+
return $spoof;
183+
}
184+
163185
/**
164186
* Fetch an item from the $_SERVER array.
165187
*

0 commit comments

Comments
 (0)