poshsecurity / Posh-SYSLOG

Send SYSLOG messages from PowerShell
http://poshsecurity.com
MIT License
92 stars 19 forks source link

Timestamp field does not comply to RFC 5424 #1

Closed dfch closed 9 years ago

dfch commented 9 years ago

The Cmdlet send-syslog.ps1 states in its description to send a syslog message as defined in RFC 5424. However the generated timestamp in the Cmdlet incorrectly formats a timestamp when none is specified by the caller, nor does it validate or convert the timestamp if specified by the caller.

As defined in 6.2.3. TIMESTAMP the timestamp MUST include a T (capital T) delimiter and MUST use - (dash) for date separation. Furthermore, the time portion of the timestamp MUST NOT be prefixed with a - (dash) and the UTC offset MUST NOT be separated by a (space) (separating the timezone with a space will result in the timezone to be treated as the 6.2.4. HOSTNAME of the message).

When invoking the following command:

Send-SyslogMessage -Server 192.168.174.161 -Message tralala -Severity Emergency -Facility local0 -UDPPort 49153 -Hostname s000000.dfch.biz

... the resulting payload (captured with Wireshark) looks like this:

<128>2015:01:11:-09:03:24 +01:00 s000000.dfch.biz tralala

... instead the timestamp MUST look like this:

<128>2015-01-11T09:03:24+01:00 s000000.dfch.biz tralala

Recommended code change for timestamp generation: Instead of:

if (($Timestamp -eq "") -or ($Timestamp -eq $null))
{
  $Timestamp = Get-Date -Format "yyyy:MM:dd:-HH:mm:ss zzz"
}

... it should be generated like this:

if (($Timestamp -eq "") -or ($Timestamp -eq $null))
{
  $Timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:sszzz"
}

Furthermore, it is recommended to change the parameter $Timestamp to a DateTime or DateTimeOffset data type, to be able to easier enforce correct dates, by converting them into a correct formt like this:

if (($Timestamp -eq "") -or ($Timestamp -eq $null))
{
  $Timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ss.ffffffzzz"
}
else
{
  $Timestamp = ($Timestamp -as [DateTime]).ToString('yyyy-MM-ddTHH:mm:ss.ffffffzzz');
}

Note: always specifying the fraction of a second (see A.4. TIME-SECFRAC Precision) will ensure the highest resolution possible, so the complete section could be rewritten like this:

PARAM
(
  # optional: [ValidateNotNullOrEmpty()]
  [DateTime] $Timestamp = [DateTime]::Now
)

...

# $null or "" check no longer necessary as it is type enforced and has a default value on its input parameter

$FullSyslogMessage = "<{0}>{1} {2} {3}" -f $Priority, $Timestamp.ToString('yyyy-MM-ddTHH:mm:ss.ffffffzzz'), $Hostname, $Message}
kjacobsen commented 9 years ago

Hi.

Thank you so much for sending through the issue! It has made me very excited that someone has even looked at using my code! You really made my weekend!

Unfortunately my timestamp seems to be a broken attempt at SYSLOG rfc3164. It seems my code is not compliant correctly to either RFC.

Whilst the modification of the timestamp might make the code compliant in terms of the time format, it introduces some other issues, which can be easily fixed.

I am working on an RFC 5424 compliant update now, which will include your updates, as well as a little extra to push it to full compliance.

dfch commented 9 years ago

I was using your module to test the Syslog UDP input adapter of Graylog2 (which seems to be rather RFC5424 compliant). If you are updating the code anyway, you could also implement a switch/option to choose between the datetime and general format of the message and implement support for different RFCs (as this is a common case of error when processing Syslog). And yes, I was glad to be able to use your script, so I would not have to write one myself. Always good to be able to reuse existing code...

kjacobsen commented 9 years ago

Just reading https://tools.ietf.org/html/rfc5424

Section 6 outlines the message layout as something like: $FullSyslogMessage = "<{0}>1 {1} {2} - - - - {3}" -f $Priority, $Timestamp.ToString('yyyy-MM-ddTHH:mm:ss.ffffffzzz'), $Hostname, $Message

The thing missing from your orginial code is the version number, which I believe is 1 for RFC 5424, and the new fields introduced for Application Name, Process ID, Message ID and structured data.

I am creating a quick fork for testing, will let you know the details in a second.

kjacobsen commented 9 years ago

Please take a look at https://github.com/kjacobsen/PowerShellSyslog/blob/RFC5424/Functions/send-syslog.ps1

Should be sending this message

<33>1 2015-01-12T00:34:47.399567+11:00 server.domain - - - - This is a test message

dfch commented 9 years ago

RFC description is a bit tricky, as it says VERSION = NONZERO-DIGIT 0*2DIGIT, which would mean that you MAY use a version field, but do not have to (as it says 0*2, meaning 0, 1 or 2 digits). See 3.6. Variable Repetition: *Rule in RFC5234 for the ABNF format. But you are certainly right, that it seems to be a good idea to include a 1 as a version number (as described in 9.1. VERSION).

kjacobsen commented 9 years ago

So I downloaded a few syslog servers (for Windows). I notice that InterActive Syslog appears to expect the version.

dfch commented 9 years ago

Not quite sure if you read my comment on your commit 4d9c0fd. For me it looks you can close the issue after merging the branch/commit.

kjacobsen commented 9 years ago

I have merged the branch back in to master, added the paameters and performed some cleanup. One thing to note is the hostname is now RFC compliant as well (to an extent). It will use FQDN - Static IP - Hostname in that order if the hostname is not specified.

kjacobsen commented 9 years ago

I worked out how I went wrong with this, I used Wikipedia for the message format specification. https://en.wikipedia.org/wiki/Syslog

dfch commented 9 years ago

yep, you are right, it shows quite an abstracted view of the actual protocol ...

I checked your code and saw that you now also have the $ApplicationName as a default input parameter. Instead of hard coding 'powershell.exe' you could use something like $Host.Name (which wold resolve to ConsoleHost or NuGet or wherever you use it) or better $MyInvocation.Parent. ... to get the caller's function name. I just mention this as the PowerShell executable itself does not really tell you much, but the caller of your function may. But this is really cosmetic (as I do not really like hard coded constants...). Again thanks for taking your time to update your code! I will now continue to use it for my Graylog tests.