Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/tftp/tftp_transfer_util.rb
19591 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Rex::Proto::TFTP
8
include Msf::Auxiliary::Report
9
10
def initialize
11
super(
12
'Name' => 'TFTP File Transfer Utility',
13
'Description' => %q{
14
This module will transfer a file to or from a remote TFTP server.
15
Note that the target must be able to connect back to the Metasploit system,
16
and NAT traversal for TFTP is often unsupported.
17
18
Two actions are supported: "Upload" and "Download," which behave as one might
19
expect -- use 'set action Actionname' to use either mode of operation.
20
21
If "Download" is selected, at least one of FILENAME or REMOTE_FILENAME
22
must be set. If "Upload" is selected, either FILENAME must be set to a valid path to
23
a source file, or FILEDATA must be populated. FILENAME may be a fully qualified path,
24
or the name of a file in the Msf::Config.local_directory or Msf::Config.data_directory.
25
},
26
'Author' => [ 'todb' ],
27
'References' => [
28
['URL', 'http://www.faqs.org/rfcs/rfc1350.html'],
29
['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm']
30
],
31
'Actions' => [
32
[ 'Download', { 'Description' => 'Download REMOTE_FILENAME as FILENAME from the server.' }],
33
[ 'Upload', { 'Description' => 'Upload FILENAME as REMOTE_FILENAME to the server.' }]
34
],
35
'DefaultAction' => 'Upload',
36
'License' => MSF_LICENSE,
37
'Notes' => {
38
'Stability' => [CRASH_SAFE],
39
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
40
'Reliability' => []
41
}
42
)
43
register_options([
44
OptString.new('FILENAME', [false, 'The local filename' ]),
45
OptString.new('FILEDATA', [false, 'Data to upload in lieu of a real local file.' ]),
46
OptString.new('REMOTE_FILENAME', [false, 'The remote filename']),
47
OptAddress.new('RHOST', [true, 'The remote TFTP server']),
48
OptPort.new('LPORT', [false, 'The local port the TFTP client should listen on (default is random)' ]),
49
OptAddressLocal.new('LHOST', [false, 'The local address the TFTP client should bind to']),
50
OptString.new('MODE', [false, 'The TFTP mode; usual choices are netascii and octet.', 'octet']),
51
Opt::RPORT(69)
52
])
53
end
54
55
def mode
56
datastore['MODE'] || 'octet'
57
end
58
59
def remote_file
60
return datastore['REMOTE_FILENAME'] if datastore['REMOTE_FILENAME']
61
return ::File.split(datastore['FILENAME']).last if datastore['FILENAME']
62
end
63
64
def rport
65
datastore['RPORT'] || 69
66
end
67
68
def rhost
69
datastore['RHOST']
70
end
71
72
# Used only to store loot, doesn't actually have any semantic meaning
73
# for the TFTP protocol.
74
def datatype
75
case datastore['MODE']
76
when 'netascii'
77
'text/plain'
78
else
79
'application/octet-stream'
80
end
81
end
82
83
def file
84
if action.name == 'Upload'
85
fdata = datastore['FILEDATA'].to_s
86
fname = datastore['FILENAME'].to_s
87
if !fdata.empty?
88
"DATA:#{datastore['FILEDATA']}"
89
elsif ::File.readable? fname
90
fname
91
else
92
fname_local = ::File.join(Msf::Config.local_directory, fname)
93
fname_data = ::File.join(Msf::Config.data_directory, fname)
94
return fname_local if ::File.file?(fname_local) && ::File.readable?(fname_local)
95
return fname_data if ::File.file?(fname_data) && ::File.readable?(fname_data)
96
97
return nil # Couldn't find it, giving up.
98
end
99
else # "Download"
100
begin
101
::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last
102
rescue StandardError
103
nil
104
end
105
end
106
end
107
108
# Experimental message prepending thinger. Might make it up into the
109
# standard Metasploit lib like vprint_status and friends.
110
def rtarget(ip = nil)
111
if (ip || rhost) && rport
112
[ip || rhost, rport].map(&:to_s).join(':') << ' '
113
elsif ip || rhost
114
"#{rhost} "
115
else
116
''
117
end
118
end
119
120
# This all happens before run(), and should give an idea on how to use
121
# the TFTP client mixin. Essentially, you create an instance of the
122
# Rex::Proto::TFTP::Client class, fill it up with the relevant host and
123
# file data, set it to either :upload or :download, then kick off the
124
# transfer as you like.
125
def setup
126
@lport = datastore['LPORT'] || (1025 + rand(0xffff - 1025))
127
@lhost = datastore['LHOST'] || '0.0.0.0'
128
@local_file = file
129
@remote_file = remote_file
130
131
@tftp_client = Rex::Proto::TFTP::Client.new(
132
'LocalHost' => @lhost,
133
'LocalPort' => @lport,
134
'PeerHost' => rhost,
135
'PeerPort' => rport,
136
'LocalFile' => @local_file,
137
'RemoteFile' => @remote_file,
138
'Mode' => mode,
139
'Context' => { 'Msf' => framework, 'MsfExploit' => self },
140
'Action' => action.name.to_s.downcase.intern
141
)
142
end
143
144
def run
145
case action.name
146
when 'Upload'
147
if file
148
run_upload
149
else
150
print_error('Need at least a local file name or file data to upload.')
151
return
152
end
153
when 'Download'
154
if remote_file
155
run_download
156
else
157
print_error('Need at least a remote file name to download.')
158
return
159
end
160
else
161
print_error "Unknown action: '#{action.name}'"
162
end
163
164
until @tftp_client.complete
165
select(nil, nil, nil, 1)
166
print_status [rtarget, 'TFTP transfer operation complete.'].join
167
save_downloaded_file if action.name == 'Download'
168
end
169
end
170
171
# Run in case something untoward happened with the connection and the
172
# client object didn't get stopped on its own. This can happen with
173
# transfers that got interrupted or malformed (like sending a 0 byte
174
# file).
175
def cleanup
176
if @tftp_client && @tftp_client.respond_to?(:complete)
177
until @tftp_client.complete
178
select(nil, nil, nil, 1)
179
vprint_status 'Cleaning up the TFTP client ports and threads.'
180
@tftp_client.stop
181
end
182
end
183
end
184
185
def run_upload
186
print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'"
187
@tftp_client.send_write_request { |msg| print_tftp_status(msg) }
188
end
189
190
def run_download
191
print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'"
192
@tftp_client.send_read_request { |msg| print_tftp_status(msg) }
193
end
194
195
def save_downloaded_file
196
print_status "Saving #{remote_file} as '#{file}'"
197
fh = @tftp_client.recv_tempfile
198
data = begin
199
File.open(fh, 'rb') { |f| f.read f.stat.size }
200
rescue StandardError
201
nil
202
end
203
if data && !data.empty?
204
unless framework.db.active
205
print_status 'No database connected, so not actually saving the data:'
206
print_line data
207
end
208
this_service = report_service(
209
host: rhost,
210
port: rport,
211
name: 'tftp',
212
proto: 'udp'
213
)
214
store_loot('tftp.file', datatype, rhost, data, file, remote_file, this_service)
215
else
216
print_status [rtarget, 'Did not find any data, so nothing to save.'].join
217
end
218
begin
219
fh.unlink
220
rescue StandardError
221
nil
222
end
223
end
224
225
def print_tftp_status(msg)
226
case msg
227
when /Aborting/, /errors.$/
228
print_error [rtarget, msg].join
229
when /^WRQ accepted/, /^Sending/, /complete!$/
230
print_good [rtarget, msg].join
231
else
232
vprint_status [rtarget, msg].join
233
end
234
end
235
end
236
237