Vendor: OpenJDK Project
Vendor URL: https://openjdk.java.net
Versions affected: 8-17+ (and likely earlier versions)
Systems Affected: All supported systems
Author: Jeff Dileo 
Advisory URL / CVE Identifier: TBD
Risk: Low (implicit data validation bypass)

The private static
InetAddress::getAllByName(String,InetAddress) method is
used internally and by the public static
InetAddress::getAllByName(String) to resolve host or IP
strings to IP addresses. It is also used to implement the public static
InetAddress::getByName(String) and private static
InetAddress::getByName(String,InetAddress) methods. When
these methods are passed IP address strings, they will, per the Java
documentation, validate the format of the address.

However, the OpenJDK implementation of this method does not conform
to the documented API, and does not properly validate the format of a
given IP address string, allowing arbitrary characters within IPv6
address strings, including those representing IPv4 addresses. Due to
this, any uses of this method to validate host names to protect against
injection attacks may be bypassed.

  • src/java.base/share/classes/java/net/InetAddress.java
    • private static int checkNumericZone(String)
    • private static InetAddress[] getAllByName(String,InetAddress)
    • private static InetAddress getByName(String,InetAddress)
    • public static InetAddress getByName(String)
    • public static InetAddress[] getAllByName(String)
  • src/java.base/share/classes/sun/net/util/IPAddressUtil.java
    • public static byte[] textToNumericFormatV6(String)
    • public static byte[] convertFromIPv4MappedAddress(byte[])

An attacker may trivially bypass the use of
InetAddress::getAllByName to validate inputs.

Note: As input validation is not an
appropriate mechanism to protect against injection attacks — as opposed
to output encoding and Harvard architecture-style APIs — this issue is
itself considered to be of Low risk as code relying on the documented
validation for such purposes should be considered insecure regardless of
this issue.

The static InetAddress::getAllByName method, and the
static InetAddress::getByName method it underpins, are used
to resolve host strings to IP addresses in the form of
java.net.InetAddress objects, specifically the
Inet4Address and Inet6Address classes that
subclass InetAddress.

These methods accept strings of IP addresses, and, per the Java
documentation for the methods, are expected only to validate the format
of the address:

Given the name of a host, returns an array of its IP addresses based
on the configured name service on the system.

The host name can either be a machine name, such as
“www.example.com”, or a textual representation of its IP address. If a
literal IP address is supplied, only the validity of the address format
is checked.

For host specified in literal IPv6 address, either the form defined
in RFC 2732 or the literal IPv6 address format defined in RFC 2373 is
accepted. A literal IPv6 address may also be qualified by appending a
scoped zone identifier or scope_id.

However, the underlying implementation for these methods within
OpenJDK, the official reference implementation of Java, does not
properly implement its IP address parser, specifically its handling of
IPv6 scoped address zone identifiers.

Within the InetAddress class implementation, the
underlying parsing flow will attempt to parse for IP address strings,
and fall back to host name lookup. Within this IP address parsing logic,
it will first parse for IPv4 addresses, and then if that parse fails,
treat the string as a potential IPv6 address. However, to handle zone
identifiers, if the private InetAddress::getAllByName
observes a literal percent character (%) within the string,
it will pass the string to the private
InetAddress::checkNumericZone static method.

addr = IPAddressUtil.textToNumericFormatV4(host);
if (addr == null) {
    // This is supposed to be an IPv6 literal
    // Check if a numeric or string zone id is present
    int pos;
    if ((pos=host.indexOf ('%')) != -1) {
        numericZone = checkNumericZone (host);
        if (numericZone == -1) { /* remainder of string must be an ifname */
            ifname = host.substring (pos+1);
        }
    }
    ...

src/java.base/share/classes/java/net/InetAddress.java

This method incorrectly assumes that a ] character
represents the end of the address string, but does not verify that this
is the case, only checking to ensure that the ] character
does not appear immediately after the %.

for (int i=percent+1; islen; i++) {
    char c = s.charAt(i);
    if (c == ']') {
        if (i == percent+1) {
            /* empty per-cent field */
            return -1;
        }
        break;
    }
    ...

src/java.base/share/classes/java/net/InetAddress.java

This is an issue as no such validation occurs earlier within the
private InetAddress::getAllByName. Instead, it uses only a
simple check that the first and last characters are [ and
], respectively, the format for using literal IPv6
addresses within URLs, in order to remove them.

if (host.charAt(0) == '[') {
    // This is supposed to be an IPv6 literal
    if (host.length() > 2 && host.charAt(host.length()-1) == ']') {
        host = host.substring(1, host.length() -1);

src/java.base/share/classes/java/net/InetAddress.java

Following the call to InetAddress::checkNumericZone, the
IPAddressUtil::textToNumericFormatV6 static method is used
to actually parse the IPv6 address string into a byte array
representation. This method specifically ignores zone identifiers by
effectively truncating the content it parses to the last character
before the first % if one exists.

char[] srcb = src.toCharArray();
byte[] dst = new byte[INADDR16SZ];

int srcb_length = srcb.length;
int pc = src.indexOf ('%');
if (pc == srcb_length -1) {
    return null;
}

if (pc != -1) {
    srcb_length = pc;
}

src/java.base/share/classes/sun/net/util/IPAddressUtil.java

As a result of each of these components of the IPv6 address parsing
logic truncating and/or ignoring data beyond certain metacharacters,
InetAddress::getAllByName will accept invalid IPv6 address
strings such as the following:

  • ::1%1]foo.bar baz'"
  • [::1%1]foo.bar baz'"]
  • 2606:4700:4700::1111%1]foo.bar baz'"
  • [2606:4700:4700::1111%1]foo.bar baz'"]

This additionally applies to IPv4-compatible IPv6 addresses, such as
the following:

  • ::1.1.1.1%1]foo.bar baz '"
  • [::1.1.1.1%1]foo.bar baz '"]
  • ::0101:0101%1]foo.bar baz '"
  • [::0101:0101%1]foo.bar baz '"]

Furthermore, a separate issue exists in the handling of IPv4-mapped
IPv6 addresses, as, unlike IPv4-compatible IPv6 addresses, which are
parsed into Inet6Address objects, the IPv4-mapped addresses
are returned as Inet4Address objects with no concept of an
IPv6 scope. This occurs between a special case handled by the static
IPAddressUtil::textToNumericFormatV6 method:

if (j != INADDR16SZ)
    return null;
byte[] newdst = convertFromIPv4MappedAddress(dst);
if (newdst != null) {
    return newdst;
} else {
    return dst;
}

src/java.base/share/classes/sun/net/util/IPAddressUtil.java

The static IPAddressUtil::convertFromIPv4MappedAddress
method will return a byte array of size 4 (INADDR4SZ)
containing the IPv4 address bytes from the byte array representation of
the address string, should it match the structure of an IPv4-mapped IPv6
address:

public static byte[] convertFromIPv4MappedAddress(byte[] addr) {
    if (isIPv4MappedAddress(addr)) {
        byte[] newAddr = new byte[INADDR4SZ];
        System.arraycopy(addr, 12, newAddr, 0, INADDR4SZ);
        return newAddr;
    }
    return null;
}

src/java.base/share/classes/sun/net/util/IPAddressUtil.java

When such a byte array is returned back to the private
InetAddress::getAllByName static method, it will then be
used to return an Inet4Address.

InetAddress[] ret = new InetAddress[1];
if(addr != null) {
    if (addr.length == Inet4Address.INADDRSZ) {
        ret[0] = new Inet4Address(null, addr);
    } else {
        if (ifname != null) {
            ret[0] = new Inet6Address(null, addr, ifname);
        } else {
            ret[0] = new Inet6Address(null, addr, numericZone);
        }
    }
    return ret;
}

src/java.base/share/classes/java/net/InetAddress.java

Due to this, any arbitrary scope value can be provided, as the
ifname variable would only be validated in the
Inet6Address(String,byte[],String) constructor, regardless
of if it being set due to InetAddress::checkNumericZone
rejecting the address string. As a result,
InetAddress::getAllByName will additionally accept invalid
IPv4-mapped IPv6 address strings such as the following:

  • ::ffff:1.1.1.1%1]foo.bar baz'"
  • [::ffff:1.1.1.1%1]foo.bar baz'"]
  • ::ffff:0101:0101%1]foo.bar baz'"
  • [::ffff:0101:0101%1]foo.bar baz'"]
  • ::ffff:1.1.1.1%1foo.bar baz'"
  • [::ffff:1.1.1.1%1foo.bar baz'"]
  • ::ffff:0101:0101%1foo.bar baz'"
  • [::ffff:0101:0101%1foo.bar baz'"]

Modify the InetAddress::checkNumericZone static method
to remove the iteration check for ] characters as it should
never be passed a string containing [ or ]
characters. This will force all characters after the % to
be parsed as a non-negative base 10 integer, or rejected.

Additionally, modify the private
InetAddress::getAllByName static method to handle length 4
byte arrays returned by
IPAddressUtil::textToNumericFormatV4 and
IPAddressUtil::textToNumericFormatV6 differently, such that
those returned by the latter do not contain any %
characters.

Additionally, or alternatively to the above remediations, consider
reimplementing the entire public
InetAddress::{getAllByName,getByName} interface along the
lines of the Android implementation, which parses IP addresses extremely
strictly, and allows interface name IPv6 scoped zone identifiers only
for link-local addresses. It
is worth noting that the Android implementation additionally validates
interface name IPv6 scoped zone identifiers against the system network
interfaces, such a construction is, while not
invalid per the InetAddress and Inet6Address
Java documentation, arguably not in the spirit of them either as these
APIs are intended for general-purpose IP address operations, including
address representations that do not necessarily refer to the interfaces
of the host operating on them. Instead, consider introducing an
additional API for the InetAddress class whereby a
getAllByName or getByName operation is
performed with such additional, host-specific validation.

Ensure that hostname and IP address values are handled securely and
output-encoded or sanitized in a context appropriate manner. Do not rely
on methods such as InetAddress::getByName(String) or
InetAddress::getAllByName(String) to validate or sanitize
external inputs.

An example demonstrating vulnerable code
relying on InetAddress::getByName(String) is included for
reference:

Note: When run, an injection will occur in the
ping(String) function, resulting in a file,
/tmp/id2, being created with the output of the
id program on Unix-based systems.

import java.net.InetAddress;

class Ping {
  public static boolean validateHost(String host) {
    try {
      InetAddress address = InetAddress.getByName(host);
    } catch (Throwable t) {
      return false;
    }
    return true;
  }

  public static int ping(String host) {
    try {
      Process p = new ProcessBuilder(
        "/bin/sh", "-c", "ping -c 1 '" + host + "'"
      ).start();
      p.waitFor();
      return p.exitValue();
    } catch (Throwable t) {
      t.printStackTrace();
      return -1;
    }
  }

  public static void test(String[] hosts) {
    for (String host : hosts) {
      System.out.println("  testing `" + host + "`:");
      boolean valid = validateHost(host);
      System.out.println("    valid?: " + valid);
      if (valid) {
        int retcode = ping(host);
        boolean reachable = 0 == retcode;
        System.out.println(
          "    reachable?: " + reachable + " (" + retcode + ")"
        );
      }
    }
  }

  public static void main(String[] argv) throws Throwable {
    String[] good_inputs = new String[]{
      "127.0.0.1", "wikipedia.org"
    };
    String[] bad_inputs = new String[]{
      "https://wikipedia.org", "127.0.0.1; id>/tmp/id"
    };
    String[] evil_inputs = new String[]{
      "::1%1]foo.bar baz'; id>/tmp/id2; exit '42"
    };
    System.out.println("testing good inputs: (these should work)");
    test(good_inputs);
    System.out.println("testing bad inputs: (these should not work)");
    test(bad_inputs);
    System.out.println("testing evil inputs: (these work, but shouldn't)");
    test(evil_inputs);
  }
}
$ java Ping
testing good inputs: (these should work)
  testing `127.0.0.1`:
    valid?: true
    reachable?: true (0)
  testing `wikipedia.org`:
    valid?: true
    reachable?: true (0)
testing bad inputs: (these should not work)
  testing `https://wikipedia.org`:
    valid?: false
  testing `127.0.0.1; id>/tmp/id`:
    valid?: false
testing evil inputs: (these work, but shouldn't)
  testing `::1%1]foo.bar baz'; id>/tmp/id2; exit '42`:
    valid?: true
    reachable?: false (42)
2/17/22: NCC Group disclosed vulnerability to the security email of the OpenJDK
         project, [email protected], using their PGP key.
2/17/22: NCC Group receives a reply from Oracle's Security Alerts team
         ([email protected]) indicating that they have received the
         disclosure and will get back to NCC Group on it.
2/18/22: The Oracle Security Alerts team emails NCC Group asking about
         NCC Group's 30 day disclosure policy and notes that they release
         "Critical Patch Updates 4 times in a year," and requests an extension
         to after the upcoming one on April 19, 2022 (i.e. the July 2022
         update).
2/19/22: NCC Group replies, indicating a willingness to wait until April 19th.
2/22/22: The Oracle Security Alerts team replies, thanking NCC Group for the
         extension.
2/24/22: NCC Group receives an automated status report email from the
         [email protected] issue tracker, with the description
         "Weak Parsing Logic in java.net.InetAddress and Related Classes" and a
         status of "Issue addressed in future release, backports in progress
         for supported releases, scheduled for a future CPU"
3/3/22:  The Oracle Security Alerts team replies indicating that they consider
         the vulnerability to be "Security-in-Depth issue", and additionally
         that "the CVSS score for this issue is zero." They state that it will
         be addressed in a future update and then that because they are locking
         down changes for the April update, they request an extension to
         "postpone the fix to July CPU, to allow more time for testing."
3/24/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Issue addressed in future
         release, backports in progress for supported releases, scheduled for a
         future CPU".
4/24/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Issue addressed in future
         release, backports in progress for supported releases, scheduled for a
         future CPU".
5/24/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Issue addressed in future
         release, backports in progress for supported releases, scheduled for a
         future CPU".
6/24/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Issue addressed in future
         release, backports in progress for supported releases, scheduled for a
         future CPU".
7/24/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Closed: Alert or CPU issued"
         and an additional note of "Addressed in: Pipeline for CPU".
8/11/22: NCC Group reviews the July 2022 CPU update
         (https://www.oracle.com/security-alerts/cpujul2022.html) and does not
         find any mention of the disclosed vulnerability. In further reviewing
         associated updates for Java 8 (8u341), 11 (11.0.16), 17 (17.0.4), and
         18 (18.0.2), NCC Group identifies a change named "Update
         java.net.InetAddress to Detect Ambiguous IPv4 Address Literals" within
         the "Other Notes" sections, which refer to a non-public issue,
         "JDK-8277608" (https://bugs.openjdk.org/browse/JDK-8277608). NCC Group
         identifies and reviews the commit introducing the change to the public
         https://github.com/openjdk/jdk repository,
         `cdc1582d1d7629c2077f6cd19786d23323111018`, and determines that the
         vulnerability has not been fixed and that the commit appears
         unrelated, simply introducing a non-security relevant breaking change
         that disables alternate numerical textual representations of IP
         addresses, such as hexadecimal and octal radixes referred to as
         "BSD-style". This change causes IP address strings such as
         "0x7f.016.0.0xa" (127.14.0.10), "0x7f000001" (127.0.0.1), or
         "017700000001" (127.0.0.1) to be rejected by default unless the
         `-Djdk.net.allowAmbiguousIPAddressLiterals=true` option is passed to
         `java`. It should be noted that this validation does not restrict
         purely numeric text representations such as "2130706433" or
         "02130706433" (both parsed to 127.0.0.1). Single segment octal
         representations are restricted when they cannot be parsed into valid
         addresses as decimal. This is due to Java's longtime improper handling
         of octal-based IP addresses, which requires at least one segment to be
         larger than the maximum value when parsed as decimal to trigger an
         octal parse. Due to this, octal-based IP addressed are often parsed
         as decimal by Java.
8/12/22: NCC Group emails both the [email protected] and
         [email protected] lists asking for the current timeline for
         the resolution of the issue, and provides the internal issue tracker
         ID. In the email, NCC Group includes a brief analysis of the "Update
         java.net.InetAddress to Detect Ambiguous IPv4 Address Literals"
         change, stating that it does not appear to be related to the disclosed
         vulnerability, which is still active in the updated releases of Java.
         Lastly, NCC Group states their intention to publish an advisory with
         with guidance for developers instead of waiting for a later CPU to
         resolve the vulnerability as the Oracle Security Alerts team had rated
         it with a CVSS score of 0.
9/14/22: The Oracle Security Alerts team replies to the previous email
         informing NCC Group and the [email protected] list that
         they "revisited the original report and learned that the issue
         reported was not addressed by the fixes released in the July CPU."
         They also stated that it was "too late to the fix into the upcoming
         2022 October CPU", and that they were "targeting the fix for the 2023
         January CPU." They additionally sought to determine if NCC Group would
         delay disclosure until after the January CPU was published.
9/22/22: NCC Group replies to Oracle Security Alerts team and the
         [email protected] list that waiting another 4-5 months far
         exceeds our disclosure policy. NCC Group also states their intention
         to publish an advisory on Sept 26, 2022, so that developers can
         mitigate the vulnerability within their codebases without an upstream
         patch.
9/23/22: The Oracle Security Alerts team replies on the thread thanking
         NCC Group for informing them of the decision to publish an advisory.
9/23/22: NCC Group receives an automated issue tracker update email from
         [email protected] with a status of "Under investigation / Being
         addressed in future and supported releases".
9/23/22: Late in the day, the Oracle Security Alerts team replies to
         NCC Group's most recent email, requesting additional time until
         noon PT on Wednesday, Sept 28, 2022, so that they can "work on a plan
         to get the fix into Oct CPU".
9/26/22: Early in the morning, NCC Group North America CTO, Dave Goldsmith,
         replies, stating that NCC Group tries "our best to work positively
         with vendors when disclosing vulnerabilities," and "that we've been
         pretty flexible" in handling the disclosure for this vulnerability.
         He offers an extension until Wednesday, Sept 28, 2022, at noon PT,
         but requests that the Oracle Security Alerts team re-evaluates the
         0.0 CVSS score of the vulnerability, as, if that remains Oracle's
         calculation, "then we don’t think it will be contentious to publish
         without a patch."
9/28/22: At 10:40am PT, the Oracle Security Alerts team replies stating that
         they "have confirmed the issue based on the report that was
         submitted" and that "the issue is a client side issue and there are
         ample best practices on input validation." They include reference
         links to an Oracle secure coding guide for Java
         (https://www.oracle.com/java/technologies/javase/seccodeguide.html),
         and the OWASP Top 10 entry on injection vulnerabilities
         (https://owasp.org/Top10/A03_2021-Injection/), the latter of which
         states the following as its second bullet on prevention: "Use positive
         server-side input validation. This is not a complete defense".
         Additionally, the Oracle Security Alerts requested a proof-of-concept
         demonstrating a server-side attack to recalculate the CVSS score.
         However, the reply did not contain any mention of "a plan to to get
         the fix into Oct CPU" per the Oracle Security Alerts team's 9/23/22
         email. It should be noted that NCC Group considers this vulnerability
         to impact code that takes untrusted hostname strings as input, a group
         that primarily includes server-side applications and services, as it
         enables a trivial bypass to official Java input validation routines
         used to protect against injection-type issues.
10/4/22: NCC Group North America CTO, Dave Goldsmith, replies, providing
         examples of how server-side input validation based on the vulnerable
         API would result in server-side systems being exploitable, including
         an example of such vulnerable code implementing input validation for
         `ping`. He requests a clear answer from the Oracle Security Alerts on
         both re-evaluating the CVSS score given the provided examples, and a
         commitment to fix the vulnerability in the October CPU. He
         additionally states that if both are provided by close of business on
         Wednesday, October 5, 2022, NCC Group will hold off on publishing the
         advisory until the October CPU is published; otherwise, the advisory
         will be published on Thursday, October 6, 2022.
10/6/22: The Oracle Security Alerts team replies at 12:10am PT, stating that
         their "evaluation is that this is an input validation issue and we are
         scoring it as a CVSS 0" and that "[a]s mentioned earlier we are
         targeting to release defense-in-depth fixes in the January 2022
         Critical Patch Update."
10/6/22: NCC Group publishes this security advisory.
10/6/22: NCC Group replies on the thread informing the Oracle Security Alerts
         team and the [email protected] list that the advisory has
         been published.

Jennifer Fernick and Dave Goldsmith for their support throughout the
disclosure process.

NCC Group is a global expert in cyber security and risk mitigation,
working with businesses to protect their brand, value and reputation
against the ever-evolving threat landscape.

With our knowledge, experience and global footprint, we are best
placed to help businesses identify, assess, mitigate and respond to the
risks they face.

We are passionate about making the Internet safer and revolutionizing
the way in which organizations think about cybersecurity.