End2End-NTP

Für die Installation von Windows über Autopilot ist eine Verbindung zum Zeitserver time.windows.com essentiell. Sehr oft blockieren aber Firewalls den Zugriff per NTP auf diesen Internetdienst. Daher habe ich mit einen Test geschrieben, der per PowerShell mal schnell die Erreichbarkeit und Laufzeit misst.

Testdaten ermitteln

Ich habe zuerst versucht, einfach ein fast leeres Paket per UDP an den Port 123 zu senden. Das geht per PowerShell und UDP sehr einfach:

$udp = New-Object System.Net.Sockets.Udpclient
$udp.Connect("time.windows.com",123)
$udp.Send(65,1)   # send on "A"

Allerdings hat kein NTP-Server darauf geantwortet. Ich habe nur das ausgehende Paket im Wireshark gesehen. Ein Capture-Filter mit "udp port 123" beschränkt die Datenmenge. Meinen Windows-PC zuhause kann ich auch einfach per Kommandozeile anweisen mal schnell einen TimeSync zu machen.

win32tm /resync

In Wireshark war konnte die Anfrage und Antwort mitgeschnitten werden.

Sie sehen hier das erste kurze Paket und die korrekte Anfrage (Paket 2) samt Daten und die Antwort im Paket 3. Aus diesem Paket kann ich mir einfach mal die Datenbytes extrahieren. Die Nutzdaten sind für den Test aber nicht wirklich relevant, denn der Client kann ja eine komplett falsche Zeit haben. Die vier Zeitstempel sind jeweils in 8 Bytes codiert:

Das Antwortpaket enthält ebenfalls vier Zeitstempel an der gleichen Position.

Die Zeit ist bei NTP Version 3 in zwei 32-bit Werten abgelegt: Sekunden seit dem 1.1.1900. An dem Beispiel bedeutet das am 24.6.2021 gegen 23:58 Uhr:

((get-date) - (get-date 01.01.1900)).totalseconds
3833568499,82199

Ein sehr ähnliches Ergebnis sollte ich erreichen, wenn ich die ersten 4 Bytes auswerte:

PS C:\> 0xe47f*0xffff + 0x7737
3833500344

Das kann man einfach wieder in eine Zeit verwandeln.

PS C:\> (get-date 1.1.1900).addseconds(3833500344)
Donnerstag, 24. Juni 2021 05:12:24

Auf Millisekunden habe ich es erst mal nicht abgesehen.

Test-Skript

Ich habe daher einfach den Request als Byte-Array vordefiniert und sendet dieses Paket stumpf an den angegebenen NTP-Server und werte einfach die Rückmeldung aus.

param (
   [string]$ntpserver = "time.windows.com"
)

$ntprequest =(0xdb,0x00,0x0a,0xe9,0x00,0x00,0x05,0xab,0x00,0x01,
              0x33,0x4a,0x00,0x00,0x00,0x00,0xe4,0x7f,0x69,0x76,
              0x0a,0xaf,0x2f,0x2d,0x00,0x00,0x00,0x00,0x00,0x00,
              0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
              0xe4,0x7f,0x77,0x43,0x66,0x96,0xae,0x2c)

$udpclient = New-Object System.Net.Sockets.Udpclient
$RemoteIpEndPoint = New-Object System.Net.IPEndPoint([system.net.IPAddress]::Parse("0.0.0.0"),0);

$udpclient.Connect($ntpserver,123)
$udpclient.Send($ntprequest,$ntprequest.length)   # send NTP Client Query

$receive=$udp.Receive([ref]$remoteIpEndpoint)

$totalsec = ((($receive[16]*256)+$receive[17])*256+$receive[18])*256+$receive[19]
(get-date 01.01.1900).addseconds($totalsec)

Das Ergebnis ist natürlich "Greenwich Time" GMT und muss entsprechend der lokalen Zeitzone angepasst werden. Aber als Verbindungstest und Laufzeittest reicht diese einfache Lösung schon aus.

Manchmal hat der NTP-Server nicht gleich beim ersten Mal schnell geantwortet. Mehrere Aufrufe mit Mittelwert helfen hier weiter.

Weiterverwendung

Für den Code sind mehrere Verwendungen denkbar bzw. ich verwende ihn bei:

  • Monitoring NTP
    Der erste Einsatzzweck ist natürlich die generelle Überwachung eines NTP-Servers hinsichtlich der Erreichbarkeit und korrekten Uhrzeit. Ds können die meisten Monitoring-Systeme aber schon mit eingebauten Sensoren.
  • Network Assessment mit Erreichbarkeit time.windows.com
    Ein Windows Client, der nicht in einer Domäne ist, bezieht sich seine Zeit per NTP von time.windows.com. Das gilt auch für Systeme, die z.B. in einem Firmen-LAN per Autopilot installiert werden. Wenn die Firewall hier den Zugriff nicht erlaubt, hat der PC vielleicht eine falsche Uhrzeit oder ein komplett falsches Datum. Damit wird die Überprüfung von Zertifikaten deutlich erschwert
  • DMZ-Uhrzeit
    Es gibt viele Systeme in einer DMZ, die nicht Mitglied einer Domain sind, z.B. Exchange Edge Server oder Skype for Business Edge Server. Auch dieses Systeme brauchen eine gültige Zeit um TLS-Verbindungen abzusichern und Logs korrekt zu protokollieren.
    Natürlich kann die Konfiguration von W32TIME derart eingerichtet werden, dass ein NTP-Server der Firma gefragt wird aber dennoch sollten Sie die korrekte Uhrzeit des Systems überprüfen
  • Latenzzeittests
    Sie sollten keine DoS-Attacke gegen NTP-Server anderer Firmen fahren. Aber wenn 1000 PCs hinter einer Firewall immer mal wieder bei time.windows.com anfragen, dann kann das ein Skript über eine beschränkte Zeit durchaus auch einmal machen, um so die Laufzeit zu erkennen.

Neben dem reinen NTP-Monitoring gibt als durchaus weitere Möglichkeiten um Einsetzen eines solchen Tests.

Zeit per HTTP

Wenn Sie übrigens nicht die Uhrzeit per NNTP von einem NNTP-Server abrufen wollen und es nicht um die letzte Sekunde geht, dann kann auch ein Webserver als Zeitquelle missbraucht werde. Die meisten Webeserver liefern auch eine UTC-Uhrzeit als Header kostenfrei mit aus:

(Invoke-WebRequest $url).headers.date

Getestet habe ich

URL Datum

https://www.google.de

Tue, 29 Jun 2021 22:30:52 GMT

https://www.microsoft.com

Tue, 29 Jun 2021 22:31:05 GMT

https://www.twitter.com

Tue, 29 Jun 2021 22:31:23 GMT

https://www.msxfaq.de

Tue, 29 Jun 2021 22:31:48 GMT

Es sieht so aus, als ob wirklich jeder Webserver so eine Zeit ausliefert, die im wesentlichen sogar an die echte Zeit heran kommt. Sie können ja mal die Diskrepanz zu ihrer hoffentlich per NTP korrekt gesetzten Zeit ausrechnen:

( `
   ( `
      (Invoke-WebRequest https://outlook.office.com `
         -Method head `
         -SkipCertificateCheck `
         -SkipHttpErrorCheck).headers.date `
      | get-date) `
    - (get-date) `
).totalmilliseconds

Die Zeit wich bei meinen Tests durchaus mit bis zu 2 Sekunden ab, je nachdem wie lange wohl der TCP und TLS-Aufbau benötigt hat. Sehr zuverlässig ist dies nicht aber doch ausreichend, um einen komplett aus der Spur laufenden Server über ein Monitoring-Skript zu erkennen.

Weitere Links