CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/dhcp/server.rb
Views: 11703
1
# -*- coding: binary -*-
2
3
require 'rex/socket'
4
5
module Rex
6
module Proto
7
module DHCP
8
9
##
10
#
11
# DHCP Server class
12
# not completely configurable - written specifically for a PXE server
13
# - scriptjunkie
14
#
15
# extended to support testing/exploiting CVE-2011-0997
16
# - [email protected]
17
##
18
19
class Server
20
21
include Rex::Socket
22
23
def initialize(hash, context = {})
24
self.listen_host = '0.0.0.0' # clients don't already have addresses. Needs to be 0.0.0.0
25
self.listen_port = 67 # mandatory (bootps)
26
self.context = context
27
self.sock = nil
28
29
self.myfilename = hash['FILENAME'] || ""
30
self.myfilename << ("\x00" * (128 - self.myfilename.length))
31
32
source = hash['SRVHOST'] || Rex::Socket.source_address
33
self.ipstring = Rex::Socket.addr_aton(source)
34
35
ipstart = hash['DHCPIPSTART']
36
if ipstart
37
self.start_ip = Rex::Socket.addr_atoi(ipstart)
38
else
39
# Use the first 3 octets of the server's IP to construct the
40
# default range of x.x.x.32-254
41
self.start_ip = "#{self.ipstring[0..2]}\x20".unpack("N").first
42
end
43
self.current_ip = start_ip
44
45
ipend = hash['DHCPIPEND']
46
if ipend
47
self.end_ip = Rex::Socket.addr_atoi(ipend)
48
else
49
# Use the first 3 octets of the server's IP to construct the
50
# default range of x.x.x.32-254
51
self.end_ip = "#{self.ipstring[0..2]}\xfe".unpack("N").first
52
end
53
54
# netmask
55
netmask = hash['NETMASK'] || "255.255.255.0"
56
self.netmaskn = Rex::Socket.addr_aton(netmask)
57
58
# router
59
router = hash['ROUTER'] || source
60
self.router = Rex::Socket.addr_aton(router)
61
62
# dns
63
dnsserv = hash['DNSSERVER'] || source
64
self.dnsserv = Rex::Socket.addr_aton(dnsserv)
65
66
# broadcast
67
if hash['BROADCAST']
68
self.broadcasta = Rex::Socket.addr_aton(hash['BROADCAST'])
69
else
70
self.broadcasta = Rex::Socket.addr_itoa( self.start_ip | (Rex::Socket.addr_ntoi(self.netmaskn) ^ 0xffffffff) )
71
end
72
73
self.served = {}
74
self.serveOnce = hash.include?('SERVEONCE')
75
76
self.servePXE = (hash.include?('PXE') or hash.include?('FILENAME') or hash.include?('PXEONLY'))
77
self.serveOnlyPXE = hash.include?('PXEONLY')
78
79
# Always assume we don't give out hostnames ...
80
self.give_hostname = false
81
self.served_over = 0
82
if (hash['HOSTNAME'])
83
self.give_hostname = true
84
self.served_hostname = hash['HOSTNAME']
85
if ( hash['HOSTSTART'] )
86
self.served_over = hash['HOSTSTART'].to_i
87
end
88
end
89
90
self.leasetime = 600
91
self.relayip = "\x00\x00\x00\x00" # relay ip - not currently supported
92
self.pxeconfigfile = "update2"
93
self.pxealtconfigfile = "update0"
94
self.pxepathprefix = ""
95
self.pxereboottime = 2000
96
97
self.domain_name = hash['DOMAINNAME'] || nil
98
self.url = hash['URL'] if hash.include?('URL')
99
end
100
101
def report(&block)
102
self.reporter = block
103
end
104
105
# Start the DHCP server
106
def start
107
self.sock = Rex::Socket::Udp.create(
108
'LocalHost' => listen_host,
109
'LocalPort' => listen_port,
110
'Context' => context
111
)
112
113
self.thread = Rex::ThreadFactory.spawn("DHCPServerMonitor", false) {
114
monitor_socket
115
}
116
end
117
118
# Stop the DHCP server
119
def stop
120
self.thread.kill
121
self.served = {}
122
self.sock.close rescue nil
123
end
124
125
126
# Set an option
127
def set_option(opts)
128
allowed_options = [
129
:serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv,
130
:pxeconfigfile, :pxepathprefix, :pxereboottime, :router, :proxy_auto_discovery,
131
:give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domain_name, :url
132
]
133
134
opts.each_pair { |k,v|
135
next if not v
136
if allowed_options.include?(k)
137
self.instance_variable_set("@#{k}", v)
138
end
139
}
140
end
141
142
143
# Send a single packet to the specified host
144
def send_packet(ip, pkt)
145
port = 68 # bootpc
146
if ip
147
self.sock.sendto( pkt, ip, port )
148
else
149
if not self.sock.sendto( pkt, '255.255.255.255', port )
150
self.sock.sendto( pkt, self.broadcasta, port )
151
end
152
end
153
end
154
155
attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv
156
attr_accessor :domain_name, :proxy_auto_discovery
157
attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce
158
attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn
159
attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE
160
attr_accessor :give_hostname, :served_hostname, :served_over, :reporter, :url
161
162
protected
163
164
165
# See if there is anything to do.. If so, dispatch it.
166
def monitor_socket
167
while true
168
rds = [@sock]
169
wds = []
170
eds = [@sock]
171
172
r,_,_ = ::IO.select(rds,wds,eds,1)
173
174
if (r != nil and r[0] == self.sock)
175
buf,host,port = self.sock.recvfrom(65535)
176
# Lame compatabilitiy :-/
177
from = [host, port]
178
dispatch_request(from, buf)
179
end
180
181
end
182
end
183
184
def dhcpoption(type, val = nil)
185
ret = ''
186
ret << [type].pack('C')
187
188
if val
189
ret << [val.length].pack('C') + val
190
end
191
192
ret
193
end
194
195
# Dispatch a packet that we received
196
def dispatch_request(from, buf)
197
type = buf.unpack('C').first
198
if (type != Constants::Request)
199
#dlog("Unknown DHCP request type: #{type}")
200
return
201
end
202
203
# parse out the members
204
_hwtype = buf[1,1]
205
hwlen = buf[2,1].unpack("C").first
206
_hops = buf[3,1]
207
_txid = buf[4..7]
208
_elapsed = buf[8..9]
209
_flags = buf[10..11]
210
clientip = buf[12..15]
211
_givenip = buf[16..19]
212
_nextip = buf[20..23]
213
_relayip = buf[24..27]
214
_clienthwaddr = buf[28..(27+hwlen)]
215
servhostname = buf[44..107]
216
_filename = buf[108..235]
217
magic = buf[236..239]
218
219
if (magic != Constants::DHCPMagic)
220
#dlog("Invalid DHCP request - bad magic.")
221
return
222
end
223
224
messageType = 0
225
pxeclient = false
226
227
# options parsing loop
228
spot = 240
229
while (spot < buf.length - 3)
230
optionType = buf[spot,1].unpack("C").first
231
break if optionType == 0xff
232
233
optionLen = buf[spot + 1,1].unpack("C").first
234
optionValue = buf[(spot + 2)..(spot + optionLen + 1)]
235
spot = spot + optionLen + 2
236
if optionType == 53
237
messageType = optionValue.unpack("C").first
238
elsif optionType == 150 or (optionType == 60 and optionValue.include? "PXEClient")
239
pxeclient = true
240
end
241
end
242
243
# don't serve if only serving PXE and not PXE request
244
return if pxeclient == false and self.serveOnlyPXE == true
245
246
# prepare response
247
pkt = [Constants::Response].pack('C')
248
pkt << buf[1..7] #hwtype, hwlen, hops, txid
249
pkt << "\x00\x00\x00\x00" #elapsed, flags
250
pkt << clientip
251
252
# if this is somebody we've seen before, use the saved IP
253
if self.served.include?( buf[28..43] )
254
pkt << Rex::Socket.addr_iton(self.served[buf[28..43]][0])
255
else # otherwise go to next ip address
256
self.current_ip += 1
257
if self.current_ip > self.end_ip
258
self.current_ip = self.start_ip
259
end
260
self.served.merge!( buf[28..43] => [ self.current_ip, messageType == Constants::DHCPRequest ] )
261
pkt << Rex::Socket.addr_iton(self.current_ip)
262
end
263
pkt << self.ipstring #next server ip
264
pkt << self.relayip
265
pkt << buf[28..43] #client hw address
266
pkt << servhostname
267
pkt << self.myfilename
268
pkt << magic
269
pkt << "\x35\x01" #Option
270
271
if messageType == Constants::DHCPDiscover #DHCP Discover - send DHCP Offer
272
pkt << [Constants::DHCPOffer].pack('C')
273
# check if already served an Ack based on hw addr (MAC address)
274
# if serveOnce & PXE, don't reply to another PXE request
275
# if serveOnce & ! PXE, don't reply to anything
276
if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
277
self.served[buf[28..43]][1] and (pxeclient == false or self.servePXE == false)
278
return
279
end
280
elsif messageType == Constants::DHCPRequest #DHCP Request - send DHCP ACK
281
pkt << [Constants::DHCPAck].pack('C')
282
# now we ignore their discovers (but we'll respond to requests in case a packet was lost)
283
if ( self.served_over != 0 )
284
# NOTE: this is sufficient for low-traffic net
285
# for high-traffic, this will probably lead to
286
# hostname collision
287
self.served_over += 1
288
end
289
else
290
return # ignore unknown DHCP request
291
end
292
293
# Options!
294
pkt << dhcpoption(Constants::OpProxyAutodiscovery, self.proxy_auto_discovery) if self.proxy_auto_discovery
295
pkt << dhcpoption(Constants::OpDHCPServer, self.ipstring)
296
pkt << dhcpoption(Constants::OpLeaseTime, [self.leasetime].pack('N'))
297
pkt << dhcpoption(Constants::OpSubnetMask, self.netmaskn)
298
pkt << dhcpoption(Constants::OpRouter, self.router)
299
pkt << dhcpoption(Constants::OpDns, self.dnsserv)
300
pkt << dhcpoption(Constants::OpDomainName, self.domain_name) if self.domain_name
301
302
if self.servePXE # PXE options
303
pkt << dhcpoption(Constants::OpPXEMagic, Constants::PXEMagic)
304
# We already got this one, serve localboot file
305
if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
306
self.served[buf[28..43]][1] and pxeclient == true
307
pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxealtconfigfile)
308
else
309
# We are handing out an IP and our PXE attack
310
if(self.reporter)
311
self.reporter.call(buf[28..43],self.ipstring)
312
end
313
pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxeconfigfile)
314
end
315
pkt << dhcpoption(Constants::OpPXEPathPrefix, self.pxepathprefix)
316
pkt << dhcpoption(Constants::OpPXERebootTime, [self.pxereboottime].pack('N'))
317
if ( self.give_hostname == true )
318
send_hostname = self.served_hostname
319
if ( self.served_over != 0 )
320
# NOTE : see above comments for the 'uniqueness' of this value
321
send_hostname += self.served_over.to_s
322
end
323
pkt << dhcpoption(Constants::OpHostname, send_hostname)
324
end
325
end
326
pkt << dhcpoption(Constants::OpURL, self.url) if self.url
327
pkt << dhcpoption(Constants::OpEnd)
328
329
pkt << ("\x00" * 32) #padding
330
331
# And now we mark as requested
332
self.served[buf[28..43]][1] = true if messageType == Constants::DHCPRequest
333
334
send_packet(nil, pkt)
335
end
336
337
end
338
339
end
340
end
341
end
342
343