You shouldn’t split a socket address at the colon to separate an IP address from the port number. Doing so uncritically breaks IPv6 support. That is what just happened in Jetpack version 4.8. Jetpack is a popular plugins for WordPress.
Here is another installment of my semi-regular feature of IPv6 Shaming. This week: the Jetpack plugin for WordPress. The Jetpack plugin is one of Automattic’s hosted-services-upsell schemes for monetizing WordPress. Jetpack adds additional features to WordPress that requires other online services and partnerships. One of these add-on features is called Protect. Protect is a login brute-force protection system that can either challenge users who want to login or block IPs of suspicious users. This feature requires that Jetpack is able to correctly identify visitors’ IP address.
The changelog for Jetpack 4.8 include the following change “[…] removes port number when server returns a port alongside a stored IP address.” So lets break this down. Jetpack sources the IP address from the PHP superglobal variable
$_SERVER['REMOTE_ADDR']. This variable should be set to the source IP address, whether that be an IPv4 or IPv6 address, and shouldn’t contain any other information. However, some server somewhere incorrectly set this variable to a socket address notation, which is to say an IP address followed by a colon followed by a port number (or
<ip>:<port>). E.g. 10.20.10.20:90.
It might be very tempting to extract the IP address from the string by splitting the string at the colon. Even widely used libraries like android.net.Uri (part of the core Android operating system) make this mistake, and I’m sure you’ll find thousands of example code snippets that do the exact same thing.
The problem here is, of course, that you’ll cut off most — if not the entirety — of an IPv6 address. In most common IPv6 address notations, groups are divided by colons rather than dots. Socket address notations for IPv6 enclose the IP address with square brackets to separate the address from port numbers. E.g. [2001:db8::a11:beef:7ac0]:90. You can probably see how cutting off the address at the first colon might cause some compatibility problems for IPv6!
In other words, this was a bad fix to work around someone’s misconfigured server somewhere where PHP’s superglobals were incorrectly set. A fix for an issue that resulted from a incorrectly configured server came at the expense of IPv6 support for every other user. That is a pretty bad trade-off. However, it also highlights that Jetpack lacks tests for their IPv6 support.
If you need a quick solution to the problem of separating IP addresses and ports, add some extra checks. You can start by counting dots. If you’ve exactly four dots means you can assume [for now] thay you’ve an IPv4 address and can safely drop everything following the first colon character. If you’ve two or more colons, on the other hand, you can assume [for now] that you’ve an IPv6 address. In that case, you can check if the first character is [, strip it off, and drop everything after the first ] character.
Whenever you manipulate IP addresses like this, you’ll want to validate that the string you end up with is in fact a valid IPv4 or IPv6 address afterward. Don’t blindly trust libraries either, as demonstrated by the long-standing fault in android.net.uri! The world’s most popular operating system can neither perform the socket address splitting operation for you nor even reliably validate a valid a IP address!