Skip to content

Fix #16944: Invalid filtering of IPv6 with FILTER_FLAG_NO_RES_RANGE #17111

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
218 changes: 136 additions & 82 deletions ext/filter/logical_filters.c
Original file line number Diff line number Diff line change
Expand Up @@ -869,15 +869,120 @@ static int _php_filter_validate_ipv6(const char *str, size_t str_len, int ip[8])
}
/* }}} */

void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
/* From the tables in RFC 6890 - Special-Purpose IP Address Registriesi
* Including errata: https://www.rfc-editor.org/errata_search.php?rfc=6890&rec_status=1 */
static bool ipv4_get_status_flags(const int ip[8], bool *global, bool *reserved, bool *private)
{
*global = false;
*reserved = false;
*private = false;

if (ip[0] == 0) {
/* RFC 0791 - This network */
*reserved = true;
} else if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) {
/* RFC 1122 - This host on this network */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The errata for RFC6890 changes this, see https://www.rfc-editor.org/errata_search.php?rfc=6890&rec_status=1&presentation=records
Should instead be: /* RFC 0791 - This network */.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually splits up the record into two, so I've made this change instead:

 }
 /* }}} */
 
-/* From the tables in RFC 6890 - Special-Purpose IP Address Registries */
-static bool ipv4_get_status_flags(int ip[8], bool *global, bool *reserved, bool *private)
+/* From the tables in RFC 6890 - Special-Purpose IP Address Registriesi
+ * Including errata: https://www.rfc-editor.org/errata_search.php?rfc=6890&rec_status=1 */
+static bool ipv4_get_status_flags(const int ip[8], bool *global, bool *reserved, bool *private)
 {
        *global = false;
        *reserved = false;
        *private = false;
 
        if (ip[0] == 0) {
+               /* RFC 0791 - This network */
+               *reserved = true;
+       } else if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) {
                /* RFC 1122 - This host on this network */
                *reserved = true;
        } else if (ip[0] == 10) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good to me. The compiler will optimize that anyway so it's okay to be verbose 🙂

*reserved = true;
} else if (ip[0] == 10) {
/* RFC 1918 - Private Use */
*private = true;
} else if (ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127) {
/* RFC 6598 - Shared Address Space */
} else if (ip[0] == 127) {
/* RFC 1122 - Loopback */
*reserved = true;
} else if (ip[0] == 169 && ip[1] == 254) {
/* RFC 3927 - Link Local */
*reserved = true;
} else if (ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) {
/* RFC 1918 - Private Use */
*private = true;
} else if (ip[0] == 192 && ip[1] == 0 && ip[2] == 0) {
/* RFC 6890 - IETF Protocol Assignments */
} else if (ip[0] == 192 && ip[1] == 0 && ip[2] == 0 && ip[3] >= 0 && ip[3] <= 7) {
/* RFC 6333 - DS-Lite */
} else if (ip[0] == 192 && ip[1] == 0 && ip[2] == 2) {
/* RFC 5737 - Documentation */
} else if (ip[0] == 192 && ip[1] == 88 && ip[2] == 99) {
/* RFC 3068 - 6to4 Relay Anycast */
*global = true;
} else if (ip[0] == 192 && ip[1] == 168) {
/* RFC 1918 - Private Use */
*private = true;
} else if (ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19) {
/* RFC 2544 - Benchmarking */
} else if (ip[0] == 198 && ip[1] == 51 && ip[2] == 100) {
/* RFC 5737 - Documentation */
} else if (ip[0] == 203 && ip[1] == 0 && ip[2] == 113) {
/* RFC 5737 - Documentation */
} else if (ip[0] >= 240 && ip[1] <= 255) {
/* RFC 1122 - Reserved */
*reserved = true;
} else if (ip[0] == 255 && ip[1] == 255 && ip[2] == 255 && ip[3] == 255) {
/* RFC 0919 - Limited Broadcast, Updated by RFC 8190, 2.2. */
*reserved = true;
} else {
return false;
}

return true;
}

/* From the tables in RFC 6890 - Special-Purpose IP Address Registries */
static bool ipv6_get_status_flags(const int ip[8], bool *global, bool *reserved, bool *private)
{
/* validates an ipv4 or ipv6 IP, based on the flag (4, 6, or both) add a
* flag to throw out reserved ranges; multicast ranges... etc. If both
* allow_ipv4 and allow_ipv6 flags flag are used, then the first dot or
* colon determine the format */
*global = false;
*reserved = false;
*private = false;

if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 0) {
/* RFC 4291 - Unspecified Address */
*reserved = true;
} else if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && ip[7] == 1) {
/* RFC 4291 - Loopback Address */
*reserved = true;
} else if (ip[0] == 0x0064 && ip[1] == 0xff9b) {
/* RFC 6052 - IPv4-IPv6 Translation */
*global = true;
} else if (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) {
/* RFC 4291 - IPv4-mapped Address */
*reserved = true;
} else if (ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) {
/* RFC 6666 - Discard-Only Address Block */
} else if (ip[0] == 0x2001 && ip[1] == 0x0000) {
/* RFC 4380 - TEREDO */
} else if (ip[0] == 0x2001 && ip[1] <= 0x01ff) {
/* RFC 2928 - IETF Protocol Assignments */
} else if (ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) {
/* RFC 5180 - Benchmarking */
} else if (ip[0] == 0x2001 && ip[1] == 0x0db8) {
/* RFC 3849 - Documentation */
} else if (ip[0] == 0x2001 && ip[1] >= 0x0010 && ip[1] <= 0x001f) {
/* RFC 4843 - ORCHID */
} else if (ip[0] == 0x2002) {
/* RFC 3056 - 6to4 */
} else if (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) {
/* RFC 4193 - Unique-Local */
*private = true;
} else if (ip[0] >= 0xfe80 && ip[0] <= 0xfebf) {
/* RFC 4291 - Linked-Scoped Unicast */
*reserved = true;
} else {
return false;
}

int ip[8];
int mode;
return true;
}

/* Validates an ipv4 or ipv6 IP, based on the flag (4, 6, or both) add a flag
* to throw out reserved ranges; multicast ranges... etc. If both allow_ipv4
* and allow_ipv6 flags flag are used, then the first dot or colon determine
* the format */
void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
{
int ip[8];
int mode;
bool flag_global, flag_reserved, flag_private; /* flags for ranges as determined by RFC 6890 */

if (memchr(Z_STRVAL_P(value), ':', Z_STRLEN_P(value))) {
mode = FORMAT_IPV6;
Expand All @@ -895,86 +1000,35 @@ void php_filter_validate_ip(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
RETURN_VALIDATION_FAILED
}

switch (mode) {
case FORMAT_IPV4:
if (!_php_filter_validate_ipv4(Z_STRVAL_P(value), Z_STRLEN_P(value), ip)) {
RETURN_VALIDATION_FAILED
}
if (mode == FORMAT_IPV4) {
if (!_php_filter_validate_ipv4(Z_STRVAL_P(value), Z_STRLEN_P(value), ip)) {
RETURN_VALIDATION_FAILED
}

/* Check flags */
if (flags & FILTER_FLAG_NO_PRIV_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
(ip[0] == 10) ||
(ip[0] == 172 && ip[1] >= 16 && ip[1] <= 31) ||
(ip[0] == 192 && ip[1] == 168)
) {
RETURN_VALIDATION_FAILED
}
}
if (!ipv4_get_status_flags(ip, &flag_global, &flag_reserved, &flag_private)) {
return; /* no special block */
}
}
else if (mode == FORMAT_IPV6) {
if (_php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value), ip) < 1) {
RETURN_VALIDATION_FAILED
}

if (flags & FILTER_FLAG_NO_RES_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
(ip[0] == 0) ||
(ip[0] >= 240) ||
(ip[0] == 127) ||
(ip[0] == 169 && ip[1] == 254)
) {
RETURN_VALIDATION_FAILED
}
}
if (!ipv6_get_status_flags(ip, &flag_global, &flag_reserved, &flag_private)) {
return; /* no special block */
}
}

if (flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
(ip[0] == 100 && ip[1] >= 64 && ip[1] <= 127 ) ||
(ip[0] == 192 && ip[1] == 0 && ip[2] == 0 ) ||
(ip[0] == 192 && ip[1] == 0 && ip[2] == 2 ) ||
(ip[0] == 198 && ip[1] >= 18 && ip[1] <= 19 ) ||
(ip[0] == 198 && ip[1] == 51 && ip[2] == 100 ) ||
(ip[0] == 203 && ip[1] == 0 && ip[2] == 113 )
) {
RETURN_VALIDATION_FAILED
}
}
if ((flags & FILTER_FLAG_GLOBAL_RANGE) && flag_global != true) {
RETURN_VALIDATION_FAILED
}

break;
if ((flags & FILTER_FLAG_NO_PRIV_RANGE) && flag_private == true) {
RETURN_VALIDATION_FAILED
}

case FORMAT_IPV6:
{
int res = 0;
res = _php_filter_validate_ipv6(Z_STRVAL_P(value), Z_STRLEN_P(value), ip);
if (res < 1) {
RETURN_VALIDATION_FAILED
}
/* Check flags */
if (flags & FILTER_FLAG_NO_PRIV_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) {
if (ip[0] >= 0xfc00 && ip[0] <= 0xfdff) {
RETURN_VALIDATION_FAILED
}
}
if (flags & FILTER_FLAG_NO_RES_RANGE || flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
(ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0 && ip[6] == 0 && (ip[7] == 0 || ip[7] == 1)) ||
(ip[0] == 0x5f) ||
(ip[0] >= 0xfe80 && ip[0] <= 0xfebf) ||
(ip[0] == 0x2001 && (ip[1] == 0x0db8 || (ip[1] >= 0x0010 && ip[1] <= 0x001f))) ||
(ip[0] == 0x3ff3)
) {
RETURN_VALIDATION_FAILED
}
}
if (flags & FILTER_FLAG_GLOBAL_RANGE) {
if (
(ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0 && ip[4] == 0 && ip[5] == 0xffff) ||
(ip[0] == 0x0100 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0) ||
(ip[0] == 0x2001 && ip[1] <= 0x01ff) ||
(ip[0] == 0x2001 && ip[1] == 0x0002 && ip[2] == 0) ||
(ip[0] >= 0xfc00 && ip[0] <= 0xfdff)
) {
RETURN_VALIDATION_FAILED
}
}
}
break;
if ((flags & FILTER_FLAG_NO_RES_RANGE) && flag_reserved == true) {
RETURN_VALIDATION_FAILED
}
}
/* }}} */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
PMOPB-45-2007:PHP ext/filter Email Validation Vulnerability
CVE-2007-1900: PHP ext/filter Email Validation Vulnerability
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug42718-2.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Bug #42718 - 2 (unsafe_raw filter not applied when configured as default filter)
Bug #42718 (unsafe_raw filter not applied when configured as default filter)
--EXTENSIONS--
filter
--INI--
Expand Down
12 changes: 2 additions & 10 deletions ext/filter/tests/bug47435.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ var_dump(filter_var("2001:0010::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("2001:0010::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("240b:0010::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("240b:0010::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("5f::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("5f::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
var_dump(filter_var("3ff3::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
var_dump(filter_var("3ff3::1", FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE));
?>
--EXPECT--
string(7) "FC00::1"
Expand All @@ -33,12 +29,8 @@ bool(false)
string(11) "fe80:5:6::1"
bool(false)
string(12) "2001:0db8::1"
bool(false)
string(12) "2001:0db8::1"
string(12) "2001:0010::1"
string(12) "2001:0010::1"
bool(false)
string(12) "240b:0010::1"
string(12) "240b:0010::1"
string(5) "5f::1"
bool(false)
string(7) "3ff3::1"
bool(false)
2 changes: 1 addition & 1 deletion ext/filter/tests/bug49274.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
#49274, fatal error when an object does not implement toString
Bug #49274 (fatal error when an object does not implement toString)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug49510.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Bug #49510 boolean validation fails with FILTER_NULL_ON_FAILURE
Bug #49510 (boolean validation fails with FILTER_NULL_ON_FAILURE)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug50632.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
bug 50632, filter_input() does not return default value if the variable does not exist
Bug #50632 (filter_input() does not return default value if the variable does not exist)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug51192.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
bug 51192, FILTER_VALIDATE_URL will invalidate a hostname that includes '-'
Bug #51192 (FILTER_VALIDATE_URL will invalidate a hostname that includes '-')
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug51368.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
FR #51368 (php_filter_float does not allow custom thousand separators)
Bug #51368 (php_filter_float does not allow custom thousand separators)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug64441.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
bug 64441, FILTER_VALIDATE_URL will invalidate a hostname that ended by dot
Bug #64441 (FILTER_VALIDATE_URL will invalidate a hostname that ended by dot)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug67167.01.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Bug #67167: object with VALIDATE_BOOLEAN and NULL_ON_FAILURE
Bug #67167 (object with VALIDATE_BOOLEAN and NULL_ON_FAILURE)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug67167.02.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Bug #67167: filter_var(null,FILTER_VALIDATE_BOOLEAN,FILTER_NULL_ON_FAILURE) returns null
Bug #67167 (filter_var(null,FILTER_VALIDATE_BOOLEAN,FILTER_NULL_ON_FAILURE) returns null)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug7715.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
bug 7715, floats value with integer or incomplete input
Bug #7715 (floats value with integer or incomplete input)
--INI--
precision=14
--EXTENSIONS--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug7733.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
filter_var() Float exponential weird result
Bug #7733 (filter_var() Float exponential weird result)
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug80584.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
Bug #80584: "0x" and "0X" are considered valid hex numbers by filter_var()
Bug #80584 ("0x" and "0X" are considered valid hex numbers by filter_var())
--EXTENSIONS--
filter
--FILE--
Expand Down
2 changes: 1 addition & 1 deletion ext/filter/tests/bug8315.phpt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
--TEST--
bug 8315, NULL values halt the validation
Bug #8315 (NULL values halt the validation)
--EXTENSIONS--
filter
--FILE--
Expand Down
10 changes: 10 additions & 0 deletions ext/filter/tests/gh16944.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Bug GH-16944 (Invalid filtering of IPv6 with FILTER_FLAG_NO_RES_RANGE)
--EXTENSIONS--
filter
--FILE--
<?php
var_dump(filter_var("::ffff:0:1", FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE));
?>
--EXPECT--
bool(false)
Loading