Vendor: OpenJDK Project Vendor URL: https://openjdk.java.net Versions affected: 8-17+ (and likely earlier versions) Systems Affected: All supported systems Author: Jeff DileoAdvisory URL / CVE Identifier: TBD Risk: Low (implicit data validation bypass)
Summary
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.
Location
- 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[])
Impact
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.
Details
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 address1:
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; i<slen; 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'"]
Technical Recommendation
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.234567 It
is worth noting that the Android implementation additionally validates
interface name IPv6 scoped zone identifiers against the system network
interfaces,8 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.
Developer Recommendation
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 theid
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)
Vendor Communication
2/17/22: NCC Group disclosed vulnerability to the security email of the OpenJDK project, vuln-report@openjdk.java.net, using their PGP key. 2/17/22: NCC Group receives a reply from Oracle's Security Alerts team (secalert_us@oracle.com) 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 secalert_us@oracle.com 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 secalert_us@oracle.com 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 secalert_us@oracle.com 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 secalert_us@oracle.com 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 secalert_us@oracle.com 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 secalert_us@oracle.com 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 secalert_us@oracle.com and vuln-report@openjdk.java.net 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 vuln-report@openjdk.java.net 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 vuln-report@openjdk.java.net 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 secalert_us@oracle.com 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 vuln-report@openjdk.java.net list that the advisory has been published.
Thanks to
Jennifer Fernick and Dave Goldsmith for their support throughout the disclosure process.
About NCC Group
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.
https://github.com/openjdk/jdk/blob/jdk-17+35/src/java.base/share/classes/java/net/InetAddress.java#L1261↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:libcore/ojluni/src/main/java/java/net/InetAddress.java;l=1148↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:libcore/ojluni/src/main/java/java/net/Inet6AddressImpl.java;l=90↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:libcore/ojluni/src/main/java/java/net/Inet6AddressImpl.java;l=113;bpv=1;bpt=1↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:bionic/libc/dns/net/getaddrinfo.c;l=586↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:bionic/libc/dns/net/getaddrinfo.c;l=1015↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:bionic/libc/dns/net/getaddrinfo.c;l=1245↩︎
https://cs.android.com/android/platform/superproject/+/android-12.0.0_r31:bionic/libc/bionic/net_if.cpp;drc=android-12.0.0_r31;l=56↩︎