nccgroup / log4j-jndi-be-gone

A Byte Buddy Java agent-based fix for CVE-2021-44228, the log4j 2.x "JNDI LDAP" vulnerability.
https://research.nccgroup.com/2021/12/12/log4j-jndi-be-gone-a-simple-mitigation-for-cve-2021-44228/
Apache License 2.0
71 stars 17 forks source link

Clarification for usage? #8

Closed rpopp-fni-stl-com closed 2 years ago

rpopp-fni-stl-com commented 2 years ago

Good morning, I'm having a heck of a time trying to test this to get proof positive results that it works. I have added a controller endpoint to our application called /exploitme, which only does one thing, and that's print the request's headers and then exits.

In my test case, I do a get against that endpoint with a user-agent header of ${jndi:ldap://127.0.0.1/o=reference}' and I see that being dumped by my controller, but I do not see anything in my logs that log4j-jndi-be-gone has blocked anything. For what it's worth, the javaagent is loaded, has the correct permissions and I know for a fact that my application is using a vulnerable version of log4j2.

For what it's worth, this is running on a tomcat 8 server using openjdk 1.8 on RHEL. Any insights would be greatly appreciated.

ChaosData commented 2 years ago

What version of log4j are you testing with? And can you provide log output and stderr output?

And just to confirm, when you say "print the request's headers", you mean that you are logging them with log4j?

rpopp-fni-stl-com commented 2 years ago

Yes, my method looks like:

 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;

 private static final Logger logger = LogManager.getLogger();

 @RequestMapping(value = "/custom/exploitme", method = RequestMethod.GET)
public void jndiTesting(HttpServletRequest request, @RequestParam(value = "FOO", required = false) String foo, HttpServletResponse response) {
            HttpSession httpSession = request.getSession();
    logger.info("REUBEN 00:  In jndiTesting for JNDI exploit, printing all headers for this request.");
    logger.info("REUBEN 01:  Headers:");

    Enumeration headers = request.getHeaderNames();
    while (headers.hasMoreElements()) {
        String k = (String) headers.nextElement();
        String v = request.getHeader(k);

        logger.info("\tReuben 01A:  Header name:  [" + k + "], value:  [" + v + "].");
    }
    logger.info("REUBEN 02:  End jndiTesting.");
}

As to log4j version, this is using 2.11.1

logs look like image

Edited to add, I see nothing pertinent in the error logs.

ChaosData commented 2 years ago

What version of Spring and how are you using it (e.g. Spring Boot?)?

I ask b/c you may not actually be calling into the real log4j, but org.apache.logging.log4j:log4j-to-slf4j via Spring's dependency chain (e.g. https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-logging/2.6.2).

Basically, until I manually added the dependencies of org.apache.logging.log4j:log4j-api:2.11.1 and org.apache.logging.log4j:log4j-core:2.11.1, I didn't even see the JndiLookup class in the resulting jar, and it didn't even seem like they were being used from my calls into logger.

rpopp-fni-stl-com commented 2 years ago

Understood. This is Springframework, version 5.0.8. We have some boot apps, but those are few.

ChaosData commented 2 years ago

Can you provide the dependency list in group:name:version format for me to verify? Alternatively, can you provide the output of unzip -l <jar> | grep -i jndi?

Otherwise, I'm inclined to say this is a false positive (...or false negative, which would be more accurate I guess?).

rpopp-fni-stl-com commented 2 years ago

Hm... without sounding like a complete idiot, let me make sure I'm doing this correctly. I ran the unzip command against the jar file for our app, which is under \<tomcat>/webapps/\<appname>/WEB-INF/lib. But, I didn't see anything for jndi. Is that the correct spot, or am I mistaken here?

ChaosData commented 2 years ago

Are you using Spring via classpath? then looking up what's actually being used would be trickier.

Can you try adding the following into your test code?

Class c = logger.getClass();
System.out.println("[test] logger.getClass(): " + c);
java.net.URL location = c.getResource('/' + c.getName().replace('.', '/') + ".class");
System.out.println("[test] via getResource: " + location);
System.out.println("[test] via getProtectionDomain: " + c.getProtectionDomain().getCodeSource().getLocation());

For example, with a fat jar, I get:

[test] logger.getClass(): class org.apache.logging.slf4j.SLF4JLogger
[test] via getResource: jar:file:path/to/web-tests.jar!/org/apache/logging/slf4j/SLF4JLogger.class
[test] via getProtectionDomain: file:path/to/web-tests.jar

The first line will probably be sufficient to determine if you're really using slf4j under the hood.

rpopp-fni-stl-com commented 2 years ago

Disregard this, sorry to be a bother. I got into our code/deployment and realized we had added the runtime argument to disable jndi. Turning that "off" resulted in your module working.

Thanks for being so responsive and helpful.