Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/dhcp/server.rb
Views: 11703
# -*- coding: binary -*-12require 'rex/socket'34module Rex5module Proto6module DHCP78##9#10# DHCP Server class11# not completely configurable - written specifically for a PXE server12# - scriptjunkie13#14# extended to support testing/exploiting CVE-2011-099715# - [email protected]16##1718class Server1920include Rex::Socket2122def initialize(hash, context = {})23self.listen_host = '0.0.0.0' # clients don't already have addresses. Needs to be 0.0.0.024self.listen_port = 67 # mandatory (bootps)25self.context = context26self.sock = nil2728self.myfilename = hash['FILENAME'] || ""29self.myfilename << ("\x00" * (128 - self.myfilename.length))3031source = hash['SRVHOST'] || Rex::Socket.source_address32self.ipstring = Rex::Socket.addr_aton(source)3334ipstart = hash['DHCPIPSTART']35if ipstart36self.start_ip = Rex::Socket.addr_atoi(ipstart)37else38# Use the first 3 octets of the server's IP to construct the39# default range of x.x.x.32-25440self.start_ip = "#{self.ipstring[0..2]}\x20".unpack("N").first41end42self.current_ip = start_ip4344ipend = hash['DHCPIPEND']45if ipend46self.end_ip = Rex::Socket.addr_atoi(ipend)47else48# Use the first 3 octets of the server's IP to construct the49# default range of x.x.x.32-25450self.end_ip = "#{self.ipstring[0..2]}\xfe".unpack("N").first51end5253# netmask54netmask = hash['NETMASK'] || "255.255.255.0"55self.netmaskn = Rex::Socket.addr_aton(netmask)5657# router58router = hash['ROUTER'] || source59self.router = Rex::Socket.addr_aton(router)6061# dns62dnsserv = hash['DNSSERVER'] || source63self.dnsserv = Rex::Socket.addr_aton(dnsserv)6465# broadcast66if hash['BROADCAST']67self.broadcasta = Rex::Socket.addr_aton(hash['BROADCAST'])68else69self.broadcasta = Rex::Socket.addr_itoa( self.start_ip | (Rex::Socket.addr_ntoi(self.netmaskn) ^ 0xffffffff) )70end7172self.served = {}73self.serveOnce = hash.include?('SERVEONCE')7475self.servePXE = (hash.include?('PXE') or hash.include?('FILENAME') or hash.include?('PXEONLY'))76self.serveOnlyPXE = hash.include?('PXEONLY')7778# Always assume we don't give out hostnames ...79self.give_hostname = false80self.served_over = 081if (hash['HOSTNAME'])82self.give_hostname = true83self.served_hostname = hash['HOSTNAME']84if ( hash['HOSTSTART'] )85self.served_over = hash['HOSTSTART'].to_i86end87end8889self.leasetime = 60090self.relayip = "\x00\x00\x00\x00" # relay ip - not currently supported91self.pxeconfigfile = "update2"92self.pxealtconfigfile = "update0"93self.pxepathprefix = ""94self.pxereboottime = 20009596self.domain_name = hash['DOMAINNAME'] || nil97self.url = hash['URL'] if hash.include?('URL')98end99100def report(&block)101self.reporter = block102end103104# Start the DHCP server105def start106self.sock = Rex::Socket::Udp.create(107'LocalHost' => listen_host,108'LocalPort' => listen_port,109'Context' => context110)111112self.thread = Rex::ThreadFactory.spawn("DHCPServerMonitor", false) {113monitor_socket114}115end116117# Stop the DHCP server118def stop119self.thread.kill120self.served = {}121self.sock.close rescue nil122end123124125# Set an option126def set_option(opts)127allowed_options = [128:serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv,129:pxeconfigfile, :pxepathprefix, :pxereboottime, :router, :proxy_auto_discovery,130:give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domain_name, :url131]132133opts.each_pair { |k,v|134next if not v135if allowed_options.include?(k)136self.instance_variable_set("@#{k}", v)137end138}139end140141142# Send a single packet to the specified host143def send_packet(ip, pkt)144port = 68 # bootpc145if ip146self.sock.sendto( pkt, ip, port )147else148if not self.sock.sendto( pkt, '255.255.255.255', port )149self.sock.sendto( pkt, self.broadcasta, port )150end151end152end153154attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv155attr_accessor :domain_name, :proxy_auto_discovery156attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce157attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn158attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE159attr_accessor :give_hostname, :served_hostname, :served_over, :reporter, :url160161protected162163164# See if there is anything to do.. If so, dispatch it.165def monitor_socket166while true167rds = [@sock]168wds = []169eds = [@sock]170171r,_,_ = ::IO.select(rds,wds,eds,1)172173if (r != nil and r[0] == self.sock)174buf,host,port = self.sock.recvfrom(65535)175# Lame compatabilitiy :-/176from = [host, port]177dispatch_request(from, buf)178end179180end181end182183def dhcpoption(type, val = nil)184ret = ''185ret << [type].pack('C')186187if val188ret << [val.length].pack('C') + val189end190191ret192end193194# Dispatch a packet that we received195def dispatch_request(from, buf)196type = buf.unpack('C').first197if (type != Constants::Request)198#dlog("Unknown DHCP request type: #{type}")199return200end201202# parse out the members203_hwtype = buf[1,1]204hwlen = buf[2,1].unpack("C").first205_hops = buf[3,1]206_txid = buf[4..7]207_elapsed = buf[8..9]208_flags = buf[10..11]209clientip = buf[12..15]210_givenip = buf[16..19]211_nextip = buf[20..23]212_relayip = buf[24..27]213_clienthwaddr = buf[28..(27+hwlen)]214servhostname = buf[44..107]215_filename = buf[108..235]216magic = buf[236..239]217218if (magic != Constants::DHCPMagic)219#dlog("Invalid DHCP request - bad magic.")220return221end222223messageType = 0224pxeclient = false225226# options parsing loop227spot = 240228while (spot < buf.length - 3)229optionType = buf[spot,1].unpack("C").first230break if optionType == 0xff231232optionLen = buf[spot + 1,1].unpack("C").first233optionValue = buf[(spot + 2)..(spot + optionLen + 1)]234spot = spot + optionLen + 2235if optionType == 53236messageType = optionValue.unpack("C").first237elsif optionType == 150 or (optionType == 60 and optionValue.include? "PXEClient")238pxeclient = true239end240end241242# don't serve if only serving PXE and not PXE request243return if pxeclient == false and self.serveOnlyPXE == true244245# prepare response246pkt = [Constants::Response].pack('C')247pkt << buf[1..7] #hwtype, hwlen, hops, txid248pkt << "\x00\x00\x00\x00" #elapsed, flags249pkt << clientip250251# if this is somebody we've seen before, use the saved IP252if self.served.include?( buf[28..43] )253pkt << Rex::Socket.addr_iton(self.served[buf[28..43]][0])254else # otherwise go to next ip address255self.current_ip += 1256if self.current_ip > self.end_ip257self.current_ip = self.start_ip258end259self.served.merge!( buf[28..43] => [ self.current_ip, messageType == Constants::DHCPRequest ] )260pkt << Rex::Socket.addr_iton(self.current_ip)261end262pkt << self.ipstring #next server ip263pkt << self.relayip264pkt << buf[28..43] #client hw address265pkt << servhostname266pkt << self.myfilename267pkt << magic268pkt << "\x35\x01" #Option269270if messageType == Constants::DHCPDiscover #DHCP Discover - send DHCP Offer271pkt << [Constants::DHCPOffer].pack('C')272# check if already served an Ack based on hw addr (MAC address)273# if serveOnce & PXE, don't reply to another PXE request274# if serveOnce & ! PXE, don't reply to anything275if self.serveOnce == true and self.served.has_key?(buf[28..43]) and276self.served[buf[28..43]][1] and (pxeclient == false or self.servePXE == false)277return278end279elsif messageType == Constants::DHCPRequest #DHCP Request - send DHCP ACK280pkt << [Constants::DHCPAck].pack('C')281# now we ignore their discovers (but we'll respond to requests in case a packet was lost)282if ( self.served_over != 0 )283# NOTE: this is sufficient for low-traffic net284# for high-traffic, this will probably lead to285# hostname collision286self.served_over += 1287end288else289return # ignore unknown DHCP request290end291292# Options!293pkt << dhcpoption(Constants::OpProxyAutodiscovery, self.proxy_auto_discovery) if self.proxy_auto_discovery294pkt << dhcpoption(Constants::OpDHCPServer, self.ipstring)295pkt << dhcpoption(Constants::OpLeaseTime, [self.leasetime].pack('N'))296pkt << dhcpoption(Constants::OpSubnetMask, self.netmaskn)297pkt << dhcpoption(Constants::OpRouter, self.router)298pkt << dhcpoption(Constants::OpDns, self.dnsserv)299pkt << dhcpoption(Constants::OpDomainName, self.domain_name) if self.domain_name300301if self.servePXE # PXE options302pkt << dhcpoption(Constants::OpPXEMagic, Constants::PXEMagic)303# We already got this one, serve localboot file304if self.serveOnce == true and self.served.has_key?(buf[28..43]) and305self.served[buf[28..43]][1] and pxeclient == true306pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxealtconfigfile)307else308# We are handing out an IP and our PXE attack309if(self.reporter)310self.reporter.call(buf[28..43],self.ipstring)311end312pkt << dhcpoption(Constants::OpPXEConfigFile, self.pxeconfigfile)313end314pkt << dhcpoption(Constants::OpPXEPathPrefix, self.pxepathprefix)315pkt << dhcpoption(Constants::OpPXERebootTime, [self.pxereboottime].pack('N'))316if ( self.give_hostname == true )317send_hostname = self.served_hostname318if ( self.served_over != 0 )319# NOTE : see above comments for the 'uniqueness' of this value320send_hostname += self.served_over.to_s321end322pkt << dhcpoption(Constants::OpHostname, send_hostname)323end324end325pkt << dhcpoption(Constants::OpURL, self.url) if self.url326pkt << dhcpoption(Constants::OpEnd)327328pkt << ("\x00" * 32) #padding329330# And now we mark as requested331self.served[buf[28..43]][1] = true if messageType == Constants::DHCPRequest332333send_packet(nil, pkt)334end335336end337338end339end340end341342343