Class: Rex::Proto::DHCP::Server

Inherits:
Object
  • Object
show all
Includes:
Socket
Defined in:
lib/rex/proto/dhcp/server.rb

Overview

DHCP Server class not completely configurable - written specifically for a PXE server

  • scriptjunkie

extended to support testing/exploiting CVE-2011-0997

  • apconole@yahoo.com

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(hash, context = {}) ⇒ Server

Returns a new instance of Server.



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/rex/proto/dhcp/server.rb', line 23

def initialize(hash, context = {})
  self.listen_host = '0.0.0.0' # clients don't already have addresses. Needs to be 0.0.0.0
  self.listen_port = 67 # mandatory (bootps)
  self.context = context
  self.sock = nil

  self.myfilename = hash['FILENAME'] || ""
  self.myfilename << ("\x00" * (128 - self.myfilename.length))

  source = hash['SRVHOST'] || Rex::Socket.source_address
  self.ipstring = Rex::Socket.addr_aton(source)

  ipstart = hash['DHCPIPSTART']
  if ipstart
    self.start_ip = Rex::Socket.addr_atoi(ipstart)
  else
    # Use the first 3 octets of the server's IP to construct the
    # default range of x.x.x.32-254
    self.start_ip = "#{self.ipstring[0..2]}\x20".unpack("N").first
  end
  self.current_ip = start_ip

  ipend = hash['DHCPIPEND']
  if ipend
    self.end_ip = Rex::Socket.addr_atoi(ipend)
  else
    # Use the first 3 octets of the server's IP to construct the
    # default range of x.x.x.32-254
    self.end_ip = "#{self.ipstring[0..2]}\xfe".unpack("N").first
  end

  # netmask
  netmask = hash['NETMASK'] || "255.255.255.0"
  self.netmaskn = Rex::Socket.addr_aton(netmask)

  # router
  router = hash['ROUTER'] || source
  self.router = Rex::Socket.addr_aton(router)

  # dns
  dnsserv = hash['DNSSERVER'] || source
  self.dnsserv = Rex::Socket.addr_aton(dnsserv)

  # broadcast
  if hash['BROADCAST']
    self.broadcasta = Rex::Socket.addr_aton(hash['BROADCAST'])
  else
    self.broadcasta = Rex::Socket.addr_itoa( self.start_ip | (Rex::Socket.addr_ntoi(self.netmaskn) ^ 0xffffffff) )
  end

  self.served = {}
  self.serveOnce = hash.include?('SERVEONCE')

  self.servePXE = (hash.include?('PXE') or hash.include?('FILENAME') or hash.include?('PXEONLY'))
  self.serveOnlyPXE = hash.include?('PXEONLY')

  # Always assume we don't give out hostnames ...
  self.give_hostname = false
  self.served_over = 0
  if (hash['HOSTNAME'])
    self.give_hostname = true
    self.served_hostname = hash['HOSTNAME']
    if ( hash['HOSTSTART'] )
      self.served_over = hash['HOSTSTART'].to_i
    end
  end

  self.leasetime = 600
  self.relayip = "\x00\x00\x00\x00" # relay ip - not currently supported
  self.pxeconfigfile = "update2"
  self.pxealtconfigfile = "update0"
  self.pxepathprefix = ""
  self.pxereboottime = 2000

  self.domain_name = hash['DOMAINNAME'] || nil
  self.url = hash['URL'] if hash.include?('URL')
end

Instance Attribute Details

#broadcastaObject

Returns the value of attribute broadcasta.



158
159
160
# File 'lib/rex/proto/dhcp/server.rb', line 158

def broadcasta
  @broadcasta
end

#contextObject

Returns the value of attribute context.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def context
  @context
end

#current_ipObject

Returns the value of attribute current_ip.



158
159
160
# File 'lib/rex/proto/dhcp/server.rb', line 158

def current_ip
  @current_ip
end

#dnsservObject

Returns the value of attribute dnsserv.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def dnsserv
  @dnsserv
end

#domain_nameObject

Returns the value of attribute domain_name.



156
157
158
# File 'lib/rex/proto/dhcp/server.rb', line 156

def domain_name
  @domain_name
end

#end_ipObject

Returns the value of attribute end_ip.



158
159
160
# File 'lib/rex/proto/dhcp/server.rb', line 158

def end_ip
  @end_ip
end

#give_hostnameObject

Returns the value of attribute give_hostname.



160
161
162
# File 'lib/rex/proto/dhcp/server.rb', line 160

def give_hostname
  @give_hostname
end

#ipstringObject

Returns the value of attribute ipstring.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def ipstring
  @ipstring
end

#leasetimeObject

Returns the value of attribute leasetime.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def leasetime
  @leasetime
end

#listen_hostObject

Returns the value of attribute listen_host.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def listen_host
  @listen_host
end

#listen_portObject

Returns the value of attribute listen_port.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def listen_port
  @listen_port
end

#myfilenameObject

Returns the value of attribute myfilename.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def myfilename
  @myfilename
end

#netmasknObject

Returns the value of attribute netmaskn.



158
159
160
# File 'lib/rex/proto/dhcp/server.rb', line 158

def netmaskn
  @netmaskn
end

#proxy_auto_discoveryObject

Returns the value of attribute proxy_auto_discovery.



156
157
158
# File 'lib/rex/proto/dhcp/server.rb', line 156

def proxy_auto_discovery
  @proxy_auto_discovery
end

#pxealtconfigfileObject

Returns the value of attribute pxealtconfigfile.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def pxealtconfigfile
  @pxealtconfigfile
end

#pxeconfigfileObject

Returns the value of attribute pxeconfigfile.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def pxeconfigfile
  @pxeconfigfile
end

#pxepathprefixObject

Returns the value of attribute pxepathprefix.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def pxepathprefix
  @pxepathprefix
end

#pxereboottimeObject

Returns the value of attribute pxereboottime.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def pxereboottime
  @pxereboottime
end

#relayipObject

Returns the value of attribute relayip.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def relayip
  @relayip
end

#reporterObject

Returns the value of attribute reporter.



160
161
162
# File 'lib/rex/proto/dhcp/server.rb', line 160

def reporter
  @reporter
end

#routerObject

Returns the value of attribute router.



155
156
157
# File 'lib/rex/proto/dhcp/server.rb', line 155

def router
  @router
end

#servedObject

Returns the value of attribute served.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def served
  @served
end

#served_hostnameObject

Returns the value of attribute served_hostname.



160
161
162
# File 'lib/rex/proto/dhcp/server.rb', line 160

def served_hostname
  @served_hostname
end

#served_overObject

Returns the value of attribute served_over.



160
161
162
# File 'lib/rex/proto/dhcp/server.rb', line 160

def served_over
  @served_over
end

#serveOnceObject

Returns the value of attribute serveOnce.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def serveOnce
  @serveOnce
end

#serveOnlyPXEObject

Returns the value of attribute serveOnlyPXE.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def serveOnlyPXE
  @serveOnlyPXE
end

#servePXEObject

Returns the value of attribute servePXE.



159
160
161
# File 'lib/rex/proto/dhcp/server.rb', line 159

def servePXE
  @servePXE
end

#sockObject

Returns the value of attribute sock.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def sock
  @sock
end

#start_ipObject

Returns the value of attribute start_ip.



158
159
160
# File 'lib/rex/proto/dhcp/server.rb', line 158

def start_ip
  @start_ip
end

#threadObject

Returns the value of attribute thread.



157
158
159
# File 'lib/rex/proto/dhcp/server.rb', line 157

def thread
  @thread
end

#urlObject

Returns the value of attribute url.



160
161
162
# File 'lib/rex/proto/dhcp/server.rb', line 160

def url
  @url
end

Instance Method Details

#dhcpoption(type, val = nil) ⇒ Object (protected)



184
185
186
187
188
189
190
191
192
193
# File 'lib/rex/proto/dhcp/server.rb', line 184

def dhcpoption(type, val = nil)
  ret = ''
  ret << [type].pack('C')

  if val
    ret << [val.length].pack('C') + val
  end

  ret
end

#dispatch_request(from, buf) ⇒ Object (protected)

Dispatch a packet that we received



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/rex/proto/dhcp/server.rb', line 196

def dispatch_request(from, buf)
  type = buf.unpack('C').first
  if (type != Constants::Request)
    #dlog("Unknown DHCP request type: #{type}")
    return
  end

  # parse out the members
  _hwtype = buf[1,1]
  hwlen = buf[2,1].unpack("C").first
  _hops = buf[3,1]
  _txid = buf[4..7]
  _elapsed = buf[8..9]
  _flags = buf[10..11]
  clientip = buf[12..15]
  _givenip = buf[16..19]
  _nextip = buf[20..23]
  _relayip = buf[24..27]
  _clienthwaddr = buf[28..(27+hwlen)]
  servhostname = buf[44..107]
  _filename = buf[108..235]
  magic = buf[236..239]

  if (magic != Constants::DHCPMagic)
    #dlog("Invalid DHCP request - bad magic.")
    return
  end

  messageType = 0
  pxeclient = false

  # options parsing loop
  spot = 240
  while (spot < buf.length - 3)
    optionType = buf[spot,1].unpack("C").first
    break if optionType == 0xff

    optionLen = buf[spot + 1,1].unpack("C").first
    optionValue = buf[(spot + 2)..(spot + optionLen + 1)]
    spot = spot + optionLen + 2
    if optionType == 53
      messageType = optionValue.unpack("C").first
    elsif optionType == 150 or (optionType == 60 and optionValue.include? "PXEClient")
      pxeclient = true
    end
  end

  # don't serve if only serving PXE and not PXE request
  return if pxeclient == false and self.serveOnlyPXE == true

  # prepare response
  pkt = [Constants::Response].pack('C')
  pkt << buf[1..7] #hwtype, hwlen, hops, txid
  pkt << "\x00\x00\x00\x00"  #elapsed, flags
  pkt << clientip

  # if this is somebody we've seen before, use the saved IP
  if self.served.include?( buf[28..43] )
    pkt << Rex::Socket.addr_iton(self.served[buf[28..43]][0])
  else # otherwise go to next ip address
    self.current_ip += 1
    if self.current_ip > self.end_ip
      self.current_ip = self.start_ip
    end
    self.served.merge!( buf[28..43] => [ self.current_ip, messageType == Constants::DHCPRequest ] )
    pkt << Rex::Socket.addr_iton(self.current_ip)
  end
  pkt << self.ipstring #next server ip
  pkt << self.relayip
  pkt << buf[28..43] #client hw address
  pkt << servhostname
  pkt << self.myfilename
  pkt << magic
  pkt << "\x35\x01" #Option

  if messageType == Constants::DHCPDiscover  #DHCP Discover - send DHCP Offer
    pkt << [Constants::DHCPOffer].pack('C')
    # check if already served an Ack based on hw addr (MAC address)
    # if serveOnce & PXE, don't reply to another PXE request
    # if serveOnce & ! PXE, don't reply to anything
    if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
        self.served[buf[28..43]][1] and (pxeclient == false or self.servePXE == false)
      return
    end
  elsif messageType == Constants::DHCPRequest #DHCP Request - send DHCP ACK
    pkt << [Constants::DHCPAck].pack('C')
    # now we ignore their discovers (but we'll respond to requests in case a packet was lost)
    if ( self.served_over != 0 )
      # NOTE: this is sufficient for low-traffic net
      # for high-traffic, this will probably lead to
      # hostname collision
      self.served_over += 1
    end
  else
    return  # ignore unknown DHCP request
  end

  # Options!
  pkt << dhcpoption(Constants::OpProxyAutodiscovery, self.proxy_auto_discovery) if self.proxy_auto_discovery
  pkt << dhcpoption(Constants::OpDHCPServer, self.ipstring)
  pkt << dhcpoption(Constants::OpLeaseTime, [self.leasetime].pack('N'))
  pkt << dhcpoption(Constants::OpSubnetMask, self.netmaskn)
  pkt << dhcpoption(Constants::OpRouter, self.router)
  pkt << dhcpoption(Constants::OpDns, self.dnsserv)
  pkt << dhcpoption(Constants::OpDomainName, self.domain_name) if self.domain_name

  if self.servePXE  # PXE options
    pkt << dhcpoption(Constants::OpPXEMagic, Constants::PXEMagic)
    # We already got this one, serve localboot file
    if self.serveOnce == true and self.served.has_key?(buf[28..43]) and
        self.served[buf[28..43]][1] and pxeclient == true
      pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxealtconfigfile)
    else
      # We are handing out an IP and our PXE attack
      if(self.reporter)
        self.reporter.call(buf[28..43],self.ipstring)
      end
      pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxeconfigfile)
    end
    pkt << dhcpoption(Constants::OpPXEPathPrefix, self.pxepathprefix)
    pkt << dhcpoption(Constants::OpPXERebootTime, [self.pxereboottime].pack('N'))
    if ( self.give_hostname == true )
      send_hostname = self.served_hostname
      if ( self.served_over != 0 )
        # NOTE : see above comments for the 'uniqueness' of this value
        send_hostname += self.served_over.to_s
      end
      pkt << dhcpoption(Constants::OpHostname, send_hostname)
    end
  end
  pkt << dhcpoption(Constants::OpURL, self.url) if self.url
  pkt << dhcpoption(Constants::OpEnd)

  pkt << ("\x00" * 32) #padding

  # And now we mark as requested
  self.served[buf[28..43]][1] = true if messageType == Constants::DHCPRequest

  send_packet(nil, pkt)
end

#monitor_socketObject (protected)

See if there is anything to do.. If so, dispatch it.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rex/proto/dhcp/server.rb', line 166

def monitor_socket
  while true
    rds = [@sock]
    wds = []
    eds = [@sock]

    r,_,_ = ::IO.select(rds,wds,eds,1)

    if (r != nil and r[0] == self.sock)
      buf,host,port = self.sock.recvfrom(65535)
      # Lame compatabilitiy :-/
      from = [host, port]
      dispatch_request(from, buf)
    end

  end
end

#report(&block) ⇒ Object



101
102
103
# File 'lib/rex/proto/dhcp/server.rb', line 101

def report(&block)
  self.reporter = block
end

#send_packet(ip, pkt) ⇒ Object

Send a single packet to the specified host



144
145
146
147
148
149
150
151
152
153
# File 'lib/rex/proto/dhcp/server.rb', line 144

def send_packet(ip, pkt)
  port = 68 # bootpc
  if ip
    self.sock.sendto( pkt, ip, port )
  else
    if not self.sock.sendto( pkt, '255.255.255.255', port )
      self.sock.sendto( pkt, self.broadcasta, port )
    end
  end
end

#set_option(opts) ⇒ Object

Set an option



127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/rex/proto/dhcp/server.rb', line 127

def set_option(opts)
  allowed_options = [
    :serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv,
    :pxeconfigfile, :pxepathprefix, :pxereboottime, :router, :proxy_auto_discovery,
    :give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domain_name, :url
  ]

  opts.each_pair { |k,v|
    next if not v
    if allowed_options.include?(k)
      self.instance_variable_set("@#{k}", v)
    end
  }
end

#startObject

Start the DHCP server



106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rex/proto/dhcp/server.rb', line 106

def start
  self.sock = Rex::Socket::Udp.create(
    'LocalHost' => listen_host,
    'LocalPort' => listen_port,
    'Context'   => context
  )

  self.thread = Rex::ThreadFactory.spawn("DHCPServerMonitor", false) {
    monitor_socket
  }
end

#stopObject

Stop the DHCP server



119
120
121
122
123
# File 'lib/rex/proto/dhcp/server.rb', line 119

def stop
  self.thread.kill
  self.served = {}
  self.sock.close rescue nil
end