Cyber Defense

PowerShell Function to Send UDP SYSLOG Message Packets

-

The syslog protocol is very widely used for sending UDP log messages over the network to a centralized logging server, typically running UNIX or Linux. Today, virtually every Security Information Event Management (SIEM) product supports inbound syslog as well, despite the security shortcomings of the protocol.

About the only major operating system that does not have built-in support for sending syslog packets is Microsoft Windows. Fortunately, Windows includes PowerShell, and PowerShell can easily use the .NET Framework to send UDP packets to syslog servers.

Below is one of the many scripts on the SEC505 zip file (look in the \Day1 folder inside the zip) for the six-day SANS Securing Windows and PowerShell Automation course (course number SEC505). It is a simple implementation of RFC 3164 syslog (but not RFC 5424) that scripters new to PowerShell can easily modify, e.g., turn into an advanced function, etc. As with all the other SEC505 scripts, it's in the public domain.

Even better would be to have a built-in SendTo-SysLog cmdlet with proper error handling and the other bells and whistles. Please vote for this suggestion on Microsoft's User Voice feedback site!

 

####################################################################################
#.Synopsis
# Send syslog UDP messages (RFC3164, but not RFC5424).
#
#.Description
# Send syslog UDP message with chosen facility, severity, content and tag. These
# messages are typically sent to UNIX/Linux syslog servers, log consolidation
# servers, or perhaps to a segment with an IDS configured to examine their payloads
# for your own custom alerting scheme with integration with a SIEM.
#
#.Parameter IP
# Name or IP of the syslog server. Defaults to "127.0.0.1".
#
#.Parameter Facility
# Any of the standard facility names, e.g., kernel, user, mail, authpriv, etc.
# See the switch statement in the function for the matching regex patterns.
# Defaults to Local7.
#
#.Parameter Severity
# Any of the standard severity names: Emergency, Alert, Critical, Error, Warning,
# Notice, Informational, or Debug. Defaults to Notice.
#
#.Parameter Content
# Payload of the syslog message. Will be truncated if too long.
#
#.Parameter SourceHostName
# Apparent name of host sending the message. Defaults to the local computer name.
#
#.Parameter Tag
# Usually identifies the process which created the message, but can be anything.
# The tag comes after the source hostname and before the content payload. If
# desired, choose a custom tag which will later assist in alerting or extraction.
# Defaults to "PowerShell".
#
#.Parameter Port
# Destination UDP port. Defaults to 514. TCP and TLS not supported.
#
#
#
#Requires -Version 2.0
#
#.Notes
# Author: Jason Fossen, Enclave Consulting LLC (http://www.sans.org/sec505)
# Version: 1.0
# Updated: 7.Jun.2013
# LEGAL: PUBLIC DOMAIN. SCRIPT PROVIDED "AS IS" WITH NO WARRANTIES OR GUARANTEES OF
# ANY KIND, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY AND/OR FITNESS FOR
# A PARTICULAR PURPOSE. ALL RISKS OF DAMAGE REMAINS WITH THE USER, EVEN IF
# THE AUTHOR, SUPPLIER OR DISTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF
# ANY SUCH DAMAGE. IF YOUR STATE DOES NOT PERMIT THE COMPLETE LIMITATION OF
# LIABILITY, THEN DELETE THIS FILE SINCE YOU ARE NOW PROHIBITED TO HAVE IT.
####################################################################################

Param ($IP = "127.0.0.1", $Facility = "local7", $Severity = "notice", $Content = "Your payload...", $SourceHostname = $env:computername, $Tag = "PowerShell", $Port = 514)

function SendTo-SysLog ($IP = "127.0.0.1", $Facility = "local7", $Severity = "notice", $Content = "Your payload...", $SourceHostname = $env:computername, $Tag = "PowerShell", $Port = 514)
{
switch -regex ($Facility)
{
'kern' {$Facility = 0 * 8 ; break }
'user' {$Facility = 1 * 8 ; break }
'mail' {$Facility = 2 * 8 ; break }
'system' {$Facility = 3 * 8 ; break }
'auth' {$Facility = 4 * 8 ; break }
'syslog' {$Facility = 5 * 8 ; break }
'lpr' {$Facility = 6 * 8 ; break }
'news' {$Facility = 7 * 8 ; break }
'uucp' {$Facility = 8 * 8 ; break }
'cron' {$Facility = 9 * 8 ; break }
'authpriv' {$Facility = 10 * 8 ; break }
'ftp' {$Facility = 11 * 8 ; break }
'ntp' {$Facility = 12 * 8 ; break }
'logaudit' {$Facility = 13 * 8 ; break }
'logalert' {$Facility = 14 * 8 ; break }
'clock' {$Facility = 15 * 8 ; break }
'local0' {$Facility = 16 * 8 ; break }
'local1' {$Facility = 17 * 8 ; break }
'local2' {$Facility = 18 * 8 ; break }
'local3' {$Facility = 19 * 8 ; break }
'local4' {$Facility = 20 * 8 ; break }
'local5' {$Facility = 21 * 8 ; break }
'local6' {$Facility = 22 * 8 ; break }
'local7' {$Facility = 23 * 8 ; break }
default {$Facility = 23 * 8 } #Default is local7
}

switch -regex ($Severity)
{
'^em' {$Severity = 0 ; break } #Emergency
'^a' {$Severity = 1 ; break } #Alert
'^c' {$Severity = 2 ; break } #Critical
'^er' {$Severity = 3 ; break } #Error
'^w' {$Severity = 4 ; break } #Warning
'^n' {$Severity = 5 ; break } #Notice
'^i' {$Severity = 6 ; break } #Informational
'^d' {$Severity = 7 ; break } #Debug
default {$Severity = 5 } #Default is Notice
}

$pri = "<" + ($Facility + $Severity) + ">"

# Note that the timestamp is local time on the originating computer, not UTC.
if ($(get-date).day -lt 10) { $timestamp = $(get-date).tostring("MMM d HH:mm:ss") } else { $timestamp = $(get-date).tostring("MMM dd HH:mm:ss") }

# Hostname does not have to be in lowercase, and it shouldn't have spaces anyway, but lowercase is more traditional.
# The name should be the simple hostname, not a fully-qualified domain name, but the script doesn't enforce this.
$header = $timestamp + " " + $sourcehostname.tolower().replace(" ","").trim() + " "

#Cannot have non-alphanumerics in the TAG field or have it be longer than 32 characters.
if ($tag -match '[^a-z0-9]') { $tag = $tag -replace '[^a-z0-9]','' } #Simply delete the non-alphanumerics
if ($tag.length -gt 32) { $tag = $tag.substring(0,31) } #and truncate at 32 characters.

$msg = $pri + $header + $tag + ": " + $content

# Convert message to array of ASCII bytes.
$bytearray = $([System.Text.Encoding]::ASCII).getbytes($msg)

# RFC3164 Section 4.1: "The total length of the packet MUST be 1024 bytes or less."
# "Packet" is not "PRI + HEADER + MSG", and IP header = 20, UDP header = 8, hence:
if ($bytearray.count -gt 996) { $bytearray = $bytearray[0..995] }

# Send the message...
$UdpClient = New-Object System.Net.Sockets.UdpClient
$UdpClient.Connect($IP,$Port)
$UdpClient.Send($ByteArray, $ByteArray.length) | out-null
}

 

1 Comments

Posted June 1, 2016 at 10:02 PM | Permalink | Reply

Kieran Jacobsen

Hi,
I am the maintainer of the Posh-SYSLOG module. It probably will do everything you need it to do, and more.
https://www.powershellgallery.com/packages/Posh-SYSLOG/

Post a Comment






Captcha


* Indicates a required field.