Windows DHCP Logs

DHCP logs can be important to collect for several reasons. One of the main reasons you want to collect DHCP logs is alerts can and will be missed and it may be a few days before an incident is noticed. If a DHCP lease has expired before we have a chance to dig into the event, the logs that identified the incident may be outdated due to a new IP address which is where DHCP logs can help correlate the origin of the event. To help configure how to send the logs from your Windows DHCP server to your SIEM, here is a simple NXlog configuration to accomplish this as well with some notation explaining what the configuration is instructing NXlog to do. If you have not installed NXlog yet, please refer to this page. Before we go over the NXlog configuration we must turn on logging for the DHCP server.

  1. Log into the DHCP server, and start the DHCP MMC console.
  2. Expand the DHCP server instance we are wanting to audit and expand the IPv4 list.
  3. Right click on IPv4 and select properties.
  4. Under the General tab there should be a check box that states "Enable DHCP audit logging", select that check box to enable auditing.
  5. After turning on DHCP audit logging, select the advanced tab and the path of where the audit logs will be created will be notated in the "Audit log file path". This location can be changed if you would like but the default location should be "C:\Windows\System32\dhcp\". After those changes are made, you are ready to start sending your DHCP logs and will need to set up Nxlog as such below.

*The following bullet-ed numbers will be referenced in the configuration file below:

  1. Defines the variable %ROOT% and sets the directory to the default installation path of NXlog at "C:\Program Files (x86)\NXlog\"
  2. Declares the path for Moduledir, Cachedir, Pidfile, SpoolDir, and LogFile, referencing the %ROOT% variable.
  3. Tells NXlog to load the xm_gelf module which will be used below in our input declaration.
  4. Tells NXlog to load the xm_csv module, which dictates to NXlog to parse a CSV. The fields function assigns a field ID to each of the corresponding entries from the DHCP log. If you refer to a typical DHCP log which is normally in "C:\Windows\System32\DHCP\*.log", at the top of each DHCP log file there is a line right before the where the logs begin which defines each "column" for the CSV format. We will use those same values in our NXlog configuration file to keep things typical. Each column of the DHCP logs are separated by a space which we also define with the "Delimiter ' ' " entry below, if we do not identify the delimiter NXlog will take the single line of the DHCP log as a single parameter instead of bunch of different values.
  5. Tells NXlog to load the xm_json module which will be referenced in our input section below.
  6. This input section is lengthy, but most of it is self explanatory. We named the Input DHCP_IN, and loaded the im_file module which loads a local log file that is specified below with the File directive as seen below. Make sure to use double \ when identifying the path of the log files, as well as we can use *.log at the end of the path to tell NXlog to look only at log files in that directory. The SavePos directive tells NXlog to remember the last place in the log file when NXlog runs so entries are not sent twice. The default value of SavePos is True, but I set this explicitly just to show that it is an option. The InputType is configured to it's default of LineBased as most log files are written/read on a single line. The message variable is set to the raw_event variable. Logic is applied to the log entry that adds additional information to the log entry depending on the ID of the log entry, and additional variables are declared to give the log entry additional information. If there is not an ID that matches one of the ones we specify, the message is dropped. If the message is not dropped the message is serialized with the to_json() function which prepares the data to be sent.
  7. An output module is created and called DHCP_Out. The output module om_udp is called, which means that any output from DHCP_Out will be sent over UDP. Because we are sending the data to a Graylog instance we define what type of format we want the output data as GELF (Graylog extended log format). The host of the Graylog instance and the port to send these DHCP logs to is also defined in this output section.
  8. Finally a path is determined which dictates to NXlog the flow of the data. We specify that the module DHCP_In is called and any value that is not dropped is sent to the DHCP_Out function that we created which then will send the DHCP log message to our Graylog Instance that we configured in DHCP_Out.

A majority of this configuration was already compiled here,, but there were some changes that were needed to be made. After you save over your current NXlog configuration file in the typical location, "C:\Program Files (x86)\nxlog\conf\nxlog.conf", you will want to restart the service so that NXlog will load the new configuration file. After you restart the service, you will want to check "C:\Program Files (x86)\nxlog\data\nxlog.log" to make sure there are no issues with your configuration.

define ROOT C:\Program Files (x86)\nxlog #1

Moduledir %ROOT%\modules 
CacheDir %ROOT%\data
Pidfile %ROOT%\data\
SpoolDir %ROOT%\data
LogFile %ROOT%\data\nxlog.log #2

<Extension gelf> 
    Module xm_gelf
</Extension> #3

<Extension ParseDHCP>
    Module  xm_csv
        Fields $ID ,$Date ,$Time ,$Description ,$IPAddress ,$ReportedHostname ,$MACAddress ,$UserName ,$TransactionID ,$QResult ,$Probationtime ,$CorrelationID ,$Dhcid ,$VendorClass(Hex) ,$VendorClass(ASCII) ,$UserClass(Hex) ,$UserClass(ASCII) ,$RelayAgentInformation ,$DnsRegError
    Delimiter   ','
</Extension> #4

<Extension json> 
    Module xm_json
</Extension> #5

<Input DHCP_IN> 
    Module  im_file
    File    "C:\\Windows\Sysnative\\dhcp\\DhcpSrvLog-*.log"
    SavePos TRUE
    InputType   LineBased
    Exec    $Message = $raw_event;
    Exec if $raw_event =~ /^[0-9][0-9],/                        \
            {                                                       \
                ParseDHCP->parse_csv();                             \
                if $raw_event =~ /^00/ $IDdef = "The log was started."; \
                if $raw_event =~ /^01/ $IDdef = "The log was stopped."; \
                if $raw_event =~ /^02/ $IDdef = "The log was temporarily paused due to low disk space.";    \
                if $raw_event =~ /^10/ $IDdef = "A new IP address was leased to a client."; \
                if $raw_event =~ /^11/ $IDdef = "A lease was renewed by a client."; \
                if $raw_event =~ /^12/ $IDdef = "A lease was released by a client.";    \
                if $raw_event =~ /^13/ $IDdef = "An IP address was found to be in use on the network."; \
                if $raw_event =~ /^14/ $IDdef = "A lease request could not be satisfied because the scope's address pool was exhausted.";   \
                if $raw_event =~ /^15/ $IDdef = "A lease was denied.";  \
                if $raw_event =~ /^16/ $IDdef = "A lease was deleted."; \
                if $raw_event =~ /^17/ $IDdef = "A lease was expired and DNS records for an expired leases have not been deleted."; \
                if $raw_event =~ /^18/ $IDdef = "A lease was expired and DNS records were deleted.";    \
                if $raw_event =~ /^20/ $IDdef = "A BOOTP address was leased to a client.";  \
                if $raw_event =~ /^21/ $IDdef = "A dynamic BOOTP address was leased to a client.";  \
                if $raw_event =~ /^22/ $IDdef = "A BOOTP request could not be satisfied because the scope's address pool for BOOTP was exhausted."; \
                if $raw_event =~ /^23/ $IDdef = "A BOOTP IP address was deleted after checking to see it was not in use.";  \
                if $raw_event =~ /^24/ $IDdef = "IP address cleanup operation has began.";  \
                if $raw_event =~ /^25/ $IDdef = "IP address cleanup statistics.";   \
                if $raw_event =~ /^30/ $IDdef = "DNS update request to the named DNS server.";  \
                if $raw_event =~ /^31/ $IDdef = "DNS update failed.";   \
                if $raw_event =~ /^32/ $IDdef = "DNS update successful.";   \
                if $raw_event =~ /^33/ $IDdef = "Packet dropped due to NAP policy.";    \
                if $raw_event =~ /^34/ $IDdef = "DNS update request the DNS update request queue limit exceeded.";    \
                if $raw_event =~ /^35/ $IDdef = "DNS update request failed.";   \
                if $raw_event =~ /^36/ $IDdef = "Packet dropped because the server is in failover standby role or the hash of the client ID does not match.";   \
                if $raw_event =~ /^[5-9][0-9]/ $IDdef = "Codes above 50 are used for Rogue Server Detection information.";  \
                if $raw_event =~ /^.+,.+,.+,.+,.+,.+,.+,.+,0,/ $QResultDef = "NoQuarantine";    \
                if $raw_event =~ /^.+,.+,.+,.+,.+,.+,.+,.+,1,/ $QResultDef = "Quarantine";  \
                if $raw_event =~ /^.+,.+,.+,.+,.+,.+,.+,.+,2,/ $QResultDef = "Drop Packet"; \
                if $raw_event =~ /^.+,.+,.+,.+,.+,.+,.+,.+,3,/ $QResultDef = "Probation";   \
                if $raw_event =~ /^.+,.+,.+,.+,.+,.+,.+,.+,6,/ $QResultDef = "No Quarantine Information ProbationTime:Year-Month-Day Hour:Minute:Second:MilliSecond.";  \
                $host           =   hostname_fqdn();                \
                $EventTime      =   parsedate($Date + " " + $Time); \
                $SourceName     =   "DHCPEvents";                   \
            $Message        =   to_json();                      \
                }                                                       \
                else                                                    \

</Input> #6

<Output DHCP_Out>
    Module      om_udp
        OutputType  GELF
        Port        514 *(This number can be changed to whatever input your SIEM is listening on.)*
</Output> #7

<Route DHCP>
    Path        DHCP_IN => DHCP_OUT
</Route> #8