Datenhaufen zu IT und Elektronik.

Kategorie: IT-Security (Seite 2 von 6)

Notizen & Praxis zur IT-Sicherheit – von Responsible Disclosure bis Härtung.

WordPress wp-cron.php: Ist die angebliche Sicherheitslücke real?

Picture of an hacker checken for wordpress vulnerability

In letzter Zeit begegnen mir immer wieder sogenannte „Vulnerability Report Scams“. Klar, mit Angst und Unwissenheit kann man Geld verdienen – also wird es auch jemand tun. Besonders fällt mir das im Zusammenhang mit der wp-cron.php auf.

Ich habe häufig Reports gesehen, die in etwa so aussehen:

Critical Vulnerability Report- {Critical BUG #P1} - https://www.example.com/ - vulnerable to attack via wp-cron.php

Hello  Security team,

I am a Security Engineer, Cyber Security Researcher, Bug Bounty Hunter  & Ethical Hacker. While testing your domain https://www.example.com/ I have found some important vulnerabilities in your site. 

Vulnerability Name:   https://www.example.com/  -  vulnerable to DoS attack via wp-cron.php

Vulnerable Domain:  https://www.example.com/wp-cron.php

Description: 

The WordPress application is vulnerable to a Denial of Service (DoS) attack via the wp-cron.php script. This script is used by WordPress to perform scheduled tasks, such as publishing scheduled posts, checking for updates, and running plugins.
An attacker can exploit this vulnerability by sending a large number of requests to the wp-cron.php script, causing it to consume excessive resources and overload the server. This can lead to the application becoming unresponsive or crashing, potentially causing data loss and downtime.

I found this vulnerability at https://www.example.com/wp-cron.php endpoint.


Steps to Reproduce: reference- https://hackerone.com/reports/1888723
 
navigate to: https://www.example.com/wp-cron.php
intercept the request through the burp suite
right click on the request and send it to the repeater
Now send a request, and you will see the response as  200 OK

---

this can be also done by the curl command given below

curl -I "https://www.example.com/wp-cron.php"



POC: Attached 

Impact:

If successful, this misconfigured wp-cron.php file can cause lots of damage to the site, such as:

Potential Denial of Service (DoS) attacks, resulting in unavailability of the application.
Server overload and increased resource usage, leading to slow response times or application crashes.
Potential data loss and downtime of the site.
Hackers can exploit the misconfiguration to execute malicious tasks, leading to security breaches.

Exploitation: 
Exploitation can be done through a GitHub tool called doser.go https://github.com/Quitten/doser.go
I did not do that as this can impact your website.
Get the doser.py script at https://github.com/Quitten/doser.py
Use this command to run the script: python3 doser.py -t 999 -g 'https://www.example.com/wp-cron.php'
Go after https://www.example.com/ 1000 requests of the doser.py script.
The site returns code 502.

Suggested Mitigation/Remediation Actions:

To mitigate this vulnerability, it is recommended to disable the default WordPress wp-cron.php script and set up a server-side cron job instead. Here are the steps to disable the default wp-cron.php script and set up a server-side cron job:
Access your website's root directory via FTP or cPanel File Manager.
Locate the wp-config.php file and open it for editing.
Add the following line of code to the file, just before the line that says "That's all, stop editing! Happy publishing.":

1define('DISABLE_WP_CRON', true);

Save the changes to the wp-config.php file.
Set up a server-side cron job to run the wp-cron.php script at the desired interval. This can be done using the server's control panel or by editing the server's crontab file.
References:

For more information about this vulnerability, please refer to the following resources:

https://hackerone.com/reports/1888723

https://medium.com/@mayank_prajapati/what-is-wp-cron-php-0dd4c31b0fee

Cron
Fix Them ----- I have protected your company and saved it from a big loss so give me some appreciation Bounty Reward. I am sharing my PayPal ID with you. Paypal ID: woop woop Current Market Value Minimum Bounty Reward for Critical BUG P1 Type. The bug I reported is part of type P1 Vulnerability severity Bug bounty reward amount (in USD) P1 (Critical) $2500 P2 (High) $1500 P3 (Medium) $1000 P4 (Low) $500 Please feel free to let me know if you have any other questions or need further information. I am happy to secure it. I hope this will be fixed soon. Feel free to let me know if you have any other questions. Thanks & Regards

Ist das nun ein echtes Problem oder nicht?

Nun… Ja und Nein. In der Nachricht wird korrekt beschrieben, was die wp-cron.php tut und warum sie wichtig ist. Auch die Tatsache, dass sie extern unendlich oft aufgerufen werden kann und dadurch potenziell eine Überlastung auslösen könnte, ist nicht falsch. Selbst der Tipp, auf eine lokale Crontab-Version umzusteigen, ist nicht verkehrt. Allerdings muss man das Ganze in den richtigen Kontext setzen: wp-cron.php ist standardmäßig in WordPress aktiviert und wird für geplante Aufgaben genutzt. Die geplanten Aufgaben werden in der Datenbank abgelegt, gibt es etwas zutun und die wp-cron.php wird aufgerufen, dann wird auch gearbeitet, gibt es nichts zutun, dann gibt es auch keine Arbeit. Die Empfehlung, sie zu deaktivieren und durch einen serverseitigen Cron-Job zu ersetzen, ist eher eine Performance-Optimierung als eine echte Sicherheitsmaßnahme.

Es handelt sich hierbei nicht um einen Zero-Day-Exploit und es gibt keine direkte Gefahr eines Datenabflusses. Falls es wirklich zu Performance-Problemen kommt, gibt es einfache Gegenmaßnahmen. Sollte tatsächlich jemand versuchen, die wp-cron.php gezielt anzugreifen, hilft ein simples Rate Limiting – entweder über die Firewall oder direkt mit mod_security (Apache) bzw. limit_req (nginx).

Ein Beispiel für nginx:

limit_req_zone $binary_remote_addr zone=cronlimit:10m rate=1r/s;

server {
    location = /wp-cron.php {
        limit_req zone=cronlimit burst=3 nodelay;
        limit_req_status 429;
    }
}

Das begrenzt die Anfragen auf 1 Anfrage pro Sekunde, mit maximal 3 Anfragen in kurzer Zeit.

Sollte man wp-cron.php einfach deaktivieren?

Nicht unbedingt. Klar, im Fall eines Angriffs kann das als erste Maßnahme helfen. Besser ist es aber, wp-cron.php lokal auszuführen und den Zugriff darauf über den Webserver auf bestimmte IP-Adressen zu beschränken – zum Beispiel die lokale des Webservers. Anschließend kann man einen Cronjob anlegen, der alle 15 Minuten ausgeführt wird:

*/15 * * * * wget -q -O - https://www.example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

Zugriff per nginx einschränken:

location ~* ^/wp-cron.php$ {
    allow 1.2.3.4;  # Ersetze mit deiner IP
    deny all;
}

Fazit:

Das ist ganz sicher kein P1-Bug. Und wenn der Report direkt eine Preistabelle mitliefert, ist das schon ein ziemlich eindeutiges Zeichen für einen Scam.

Zusammengefasst:

  • Ja, wp-cron.php könnte unter bestimmten Umständen zu Problemen führen.
  • Nein, es ist kein echter Sicherheits-Bug.
  • Wer weiß, was er tut, hat bereits die richtigen Maßnahmen getroffen.

Also: Keine Panik. Stattdessen lieber kurz die eigene Konfiguration prüfen und gut ist.

Wer auf das nginx Rate Limit setzt und dieses einfach testen möchte, kann das folgende Bash-Script nutzen: rate_limit_test.sh


08-03-2025 kleines Update. Das script ist zu github gewechselt.

FreeBSD SSH-Server absichern: MFA mit Google Authenticator einrichten​

Dass man eigentlich keinen reinen Kennwort-Login für seine Anmeldung an einem SSH-Server haben möchte, ist sicherlich bei fast allen angekommen. Kennwörter lassen sich einfacher mittels eines Brute-Force-Angriffes herausfinden. Ebenso gehen diese auch mal verloren. SSH-Keys werden die meisten ebenfalls bereits aufseiten des Clients mit einem zweiten Faktor geschützt haben. Dies kann ein MFA-Token sein oder einfach eine Passphrase.

Hin und wieder lässt es sich aber nicht vermeiden, dass man seinen Login nur mit einer einfachen Kombination aus Benutzername und Kennwort sichert. Um dieses dennoch etwas aufzuwerten, lässt sich dieses ebenfalls mit MFA ausstatten. In diesem kurzen Beispiel geht es dabei um einen SSH-Server auf einem FreeBSD-System, welches nach der Authentifizierung mittels Benutzername/Kennwort noch nach einem Auth-Code vom Google Authenticator fragt.

Clientseitig ist eigentlich nichts weiter zu beachten. Auf Serverseite muss das Paket pam_google_authenticator installiert werden:

pkg install pam_google_authenticator

Ist die Installation abgeschlossen, müssen wir nun unsere PAM-Konfiguration für den SSHD-Password-Login erweitern. Oh, ja… Auf demselben Weg lässt sich dieses ebenfalls für den normalen Login an der Konsole, für su, ftp usw. einbinden. Selbstverständlich ebenfalls für den Login per SSH-Keys. Wir bleiben aber hier nun beim Login mit User/Pass. Meine /etc/pam.d/sshd sieht damit wie folgt aus:

#
#
# PAM configuration for the "sshd" service
#

# auth
#auth		sufficient	pam_krb5.so		no_warn try_first_pass
#auth		sufficient	pam_ssh.so		no_warn try_first_pass
auth		required	pam_unix.so		no_warn try_first_pass
auth            required        /usr/local/lib/pam_google_authenticator.so

# account
account		required	pam_nologin.so
#account	required	pam_krb5.so
account		required	pam_login_access.so
account		required	pam_unix.so

# session
#session	optional	pam_ssh.so		want_agent
session		required	pam_permit.so

# password
#password	sufficient	pam_krb5.so		no_warn try_first_pass
password	required	pam_unix.so		no_warn try_first_pass

Ebenfalls muss die folgende Option in der /etc/ssh/sshd_config aktiviert sein:

ChallengeResponseAuthentication yes

Das war es auch schon fast. Wenn man nun auf seinem Smartphone noch schnell den Google Authenticator installiert, können wir schon damit beginnen, den zweiten Faktor zu erstellen. Dafür einfach mit dem gewünschten Nutzer in dessen Home-Verzeichnis: „cd ~“ den google-authenticator aufrufen und den Anweisungen folgen:

Dieses nur noch mit der Authenticator-App am Smartphone scannen, den Code einmal eingeben und schon wird man bei jedem Kennwort-Login nach seinem aktuellen Code gefragt.

Oh, sehr ähnlich ist die Einrichtung unter Linux 🙂

DNSSEC unter Linux Mint & Ubuntu: SSH-Hostkey-Verifikation erklärt​

Heute habe ich versucht, mich von meiner neuen Linux Mint Installation aus mit einem meiner SSH-Server zu verbinden. Mein SSH-Client hat mich direkt mit der Frage begrüßt, ob ich dem neuen Hostkey vertrauen möchte oder nicht.

ssh username@hostname.kernel-error.org
The authenticity of host 'hostname.kernel-error.org (2a01:5a8:362:4416::32)' can't be established.
ED25519 key fingerprint is SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Für viele mag diese Meldung bekannt und vollkommen normal erscheinen. In der Regel antwortet man initial mit „yes“ und sieht sie nie wieder. Aber diese Meldung hat ihren Grund. Beim initialen Aufbau einer Verbindung zu einem SSH-Server wird einem der Fingerprint des HostKeys angezeigt. So hat man die Möglichkeit, den Fingerprint mit dem erwarteten Fingerprint abzugleichen, um sicherzustellen, dass man sich wirklich mit dem gewünschten SSH-Server verbindet und nicht etwa ein Angreifer Zugangsdaten abfischt. Wenn man eh immer nur „JA“ sagt, könnte man diesen Check auch direkt in seiner ~/.ssh/config mit folgendem Eintrag deaktivieren:

Host *
    StrictHostKeyChecking no

Warum erzähle ich das alles? Nun, weil es für mich eigentlich nicht normal ist, diese Meldung zu sehen. Denn es gibt die Möglichkeit, die Fingerprints der erwarteten HostKeys in seiner DNS-Zone zu hinterlegen und seinen SSH-Client mit der folgenden Konfiguration in seiner ~/.ssh/config anzuweisen, dies einfach selbst zu überprüfen, sofern der SSH-Client eine vertrauenswürdige Antwort vom DNS-Server erhält.

Host *
   VerifyHostKeyDNS yes

Vertrauenswürdige Antwort vom DNS-Server… Hier sind wir schon bei DNSSEC angekommen. Meine DNS-Server, einschließlich des lokalen Resolvers auf meinem Router, unterstützen alle DNSSEC. Meine SSH-Client-Konfiguration ist korrekt und dennoch erscheint die Meldung…. Also habe ich den Verbindungsaufbau mit etwas mehr Debugging-Output gestartet, was bei ssh einfach die zusätzliche Option -vvv bedeutet:

ssh usermane@hostname.kernel-error.org -vvv
[...]
debug1: SSH2_MSG_KEX_ECDH_REPLY received
debug1: Server host key: ssh-ed25519 SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu
debug3: verify_host_key_dns
debug1: found 2 insecure fingerprints in DNS
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 1
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 1
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 2
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 2
debug1: matching host key fingerprint found in DNS
[...]

Zu meiner Überraschung sehe ich:

debug1: found 2 insecure fingerprints in DNS

Hm… „insecure“… Er hat also die passenden Einträge in der DNS-Zone gefunden, kann diesen aber nicht vertrauen, weil… ja, warum? Die Antwort des DNS-Servers ist nicht vertrauenswürdig? OK, das lässt sich einfach mit dig und der Option +dnssec testen. Wir suchen einfach im Header nach „ad„:

dig +dnssec hostname.kernel-error.org @8.8.8.8

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> +dnssec hostname.kernel-error.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 48645
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
[...]

@8.8.8.8 gibt an, dass direkt der öffentliche DNS-Server von Google gefragt wird. Dieser wird natürlich meine Server fragen usw., einfach um sicherzugehen, dass meine eigentlichen DNS-Server, die für die Zone zuständig sind, sauber konfiguriert sind. Ich sehe ein „ad„, also ist dort schon mal alles gut. Im Anschluss habe ich den Test noch mit meinem lokalen DNS-Resolver auf dem Router durchgeführt. Also einfach @192.168.0.1 oder was auch immer euer lokaler Router ist. Gleiches Ergebnis…. Aber warum will dann mein Linux Mint nicht? Sollte Linux Mint etwa kein DNSSEC können?

dig +dnssec hostname.kernel-error.org

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> +dnssec hostname.kernel-error.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1789
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
[...]

Öhhh ähhh… Ja, öhm, geht nicht… Aber warum? Was steht denn in meiner /etc/resolv.conf? 127.0.0.53? Ohhhhhhhh, stimmt! systemd-resolv! OK, ok… Ich könnte also in meiner /etc/systemd/resolved.conf nun einfach DNSSEC=yes setzen und mit einem systemctl restart systemd-resolved sollte dann… Nope, leider nicht. Nun geht überhaupt keine DNS-Auflösung mehr. Es scheint am eingesetzten Stub-Resolver zu liegen, den man ebenfalls noch ändern kann usw… Nennt mich etwas oldschool, aber für meine Zwecke reicht der klassische Weg über die vom NetworkManager gepflegte resolv.conf. Um also systemd-resolved zu deaktivieren und auf den NetworkManager zu wechseln, sind die folgenden Schritte nötig:

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf

Dann in die Konfigurationsdatei vom NetworkManager /etc/NetworkManager/NetworkManager.conf in der [main]-Sektion die folgende Option setzen:

dns=default

Nun nur noch den NetworkManager neu starten und schon sollte die /etc/resolv.conf mit den DNS-Informationen gefüttert werden:

sudo systemctl restart NetworkManager
cat /etc/resolv.conf
# Generated by NetworkManager
search kernel-error.local
nameserver 10.10.88.1
nameserver fd00:424e:6eff:f525:454e:6eff:f525:4241

Perfekt! Also los, noch ein Versuch mit dem SSH-Client und… nichts… DNS-Auflösung funktioniert, aber es ist noch immer „insecure„. Stimmt! Es fehlt etwas in meiner resolv.conf. Wir brauchen bestimmt noch die folgende Option:

options edns0

Jetzt aber! HA, dig ist schon mal glücklich, ich sehe ein „ad“. Ähm, aber der SSH-Client noch immer nicht?! Was zum… OK, OK… Irgendwas um SSH muss ich vergessen haben. Aber was? Wie macht SSH das überhaupt? Vielleicht gibt mir das eine Idee. Also, mal kurz in den Code geschaut, C bekomme ich gerade noch hin:

        /* Check for authenticated data */
        if (ldns_pkt_ad(pkt)) {
                rrset->rri_flags |= RRSET_VALIDATED;
        } else { /* AD is not set, try autonomous validation */
                ldns_rr_list * trusted_keys = ldns_rr_list_new();

                debug2("ldns: trying to validate RRset");
                /* Get eventual sigs */
                rrsigs = ldns_pkt_rr_list_by_type(pkt, LDNS_RR_TYPE_RRSIG,
                    LDNS_SECTION_ANSWER);

                rrset->rri_nsigs = ldns_rr_list_rr_count(rrsigs);
                debug2("ldns: got %u signature(s) (RRTYPE %u) from DNS",
                       rrset->rri_nsigs, LDNS_RR_TYPE_RRSIG);

                if ((err = ldns_verify_trusted(ldns_res, rrdata, rrsigs,
                     trusted_keys)) == LDNS_STATUS_OK) {
                        rrset->rri_flags |= RRSET_VALIDATED;
                        debug2("ldns: RRset is signed with a valid key");
                } else {
                        debug2("ldns: RRset validation failed: %s",
                            ldns_get_errorstr_by_id(err));
                }

                ldns_rr_list_deep_free(trusted_keys);
        }

Aaaaaaaaaaaaaaaaaaaaaaa… ldns… woher bekommt er wohl die Trust Keys? Genau… woher? Da fehlt also NOCH etwas in meiner resolv.conf:

options edns0 trust-ad

Und? genau… geht 😀

ssh usermane@hostname.kernel-error.org -vvv
[...]
debug1: SSH2_MSG_KEX_ECDH_REPLY received
debug1: Server host key: ssh-ed25519 SHA256:kTRGVCMRLiHfvJunW2CbW5H3NZmn3Wkx2KnHJXl3iJu
debug3: verify_host_key_dns
debug1: found 2 secure fingerprints in DNS
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 1
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 1
debug3: verify_host_key_dns: checking SSHFP type 4 fptype 2
debug1: verify_host_key_dns: matched SSHFP type 4 fptype 2
debug1: matching host key fingerprint found in DNS
[...]

Pfff… nun habe ich natürlich die Optionen von Hand in meine resolv.conf eingetragen, der NetworkManager wird diese Option also spätestens beim nächsten Boot rauswerfen. Also muss ich noch meinem NetworkManager beibringen, dass er bitte diese Option ebenfalls in meine resolv.conf schreibt, wenn das jeweilige Netzwerkprofil aktiviert ist. Dazu gibt es aber leider keinen Menüpunkt in der GUI vom NetworkManager, also muss das per CLI gemacht werden. Dieses für IPv4 und IPv6 gleichermaßen, sonst greift es leider nicht!

nmcli conn modify DEINE-PROFIL-UUID ipv4.dns-options edns0,trust-ad
nmcli conn modify DEINE-PROFIL-UUID ipv6.dns-options edns0,trust-ad

Oh, ein nmcli conn show listet die bestehenden und vor allem das aktive Profil inkl. der UUID auf. Davon abgesehen, klappt es nun so und ist rebootfest.

So und nun ihr! Ich bin mit meinem FreeBSD-Wissen an das Thema herangegangen. Wie macht man das als Hardcore-Linux-User und mit systemd-resolved richtig und funktionierend?

Cloudflare Deal: Günstige YubiKeys für 10–11 USD im Oktober 2022

Es gibt aktuell einen EEEECCCHHHTTTT guten Deal um günstig an bis zu 10 YubiKeys 5 NFC und/oder 5C NFC zu kommen.

Man benötigt dafür einen Cloudflare Account, der ist ja schnell geklickt. Dann klickt man ein paar Links und wartet auf eine E-Mail mit seinem Discount Code.

Da die Keys in der Regel etwas um die 50€ Kosten, ist dieses schon eine extreme Ersparnis.

Oh der Link: https://www.reddit.com/r/yubikey/comments/xrcly7/cloudflare_deal_for_1011_keys/

Screenshot der Bestellübersicht für den YubiKey 5 NFC und YubiKey 5C NFC

DNS over TLS mit BIND, Stunnel und Android 9: Eigener DoT-Server​

Über die Techniken DoT (DNS over TLS) habe ich bereits im Zusammenhang mit Bind 9.16 geschrieben. Ebenfalls DoH (DNS over HTTPS) gibt es einen kleinen Beitrag.

Bilder der Bind 9 TLS Konfiguration

Zu diesem Zeitpunkt bracht BIND 9 die Unterstützung für DoH und DoT noch nicht selbst mit. Daher waren zu diesem Zeitpunkt noch Umwege über stunnel oder nginx zusammen mit doh-proxy nötig.

Zum Glück kommt die letzte stable Version 9.18.0 (26. Januar 2022) mit dem nötigen Support.

named now supports securing DNS traffic using Transport Layer Security (TLS). TLS is used by both DNS over TLS (DoT) and DNS over HTTPS (DoH).

Warum möchte man noch gleich DoH oder DoT benutzen? Ganz einfach… Über diese Techniken werden DNS Abfragen verschlüsselt übertragen. Dieses ist ein weiterer Schutz davor manipulierte Antworten zu bekommen und selbstverständlich, damit die eigenen DNS Abfragen erst überhaupt nicht mitgelesen werden. Denn wenn von einem Gerät im Netzwerk die DNS Abfrage zu z.B.: www.tagesschau.de kommt, könnte man davon bereits Dinge ableiten.

Wie die meisten Bind Konfigurationen ist dieses ebenfalls straightforward. Ab Version 9.18 bringt Bind alles Nötige mit. Da wir nun TLS mit dem Bind sprechen möchten, benötigen wir natürlich ein gültiges Zertifikat, wie z.B. beim nginx für seine Webseite.

Ebenfalls sollte man ein paar frische Diffie-Hellmann Parameter generieren:

openssl dhparam -out dhparam.pem 4096

Die eigentliche bind Konfiguration kann in der named.conf.options geschehen:

options {
        [...]
        listen-on port 853 tls local-tls { 37.120.183.220; };
        listen-on-v6 port 853 tls local-tls { 2a03:4000:38:20e::853; };
        listen-on port 443 tls local-tls http default { 37.120.183.220;  };
        listen-on-v6 port 443 tls local-tls http default { 2a03:4000:38:20e::853; };
        [...]
        allow-recursion-on { 127.0.0.0/8; ::1/128; 2a03:4000:38:20e::853; 37.120.183.220; };
        [...]
};

Da der bind auf weiteren Ports lauschen soll erweitert man diese für IPv4 und IPv6. Der Default Port für DoH ist dabei 443 und der default Port für DoT ist 853, beides TCP.

listen-on sowie listen-on-v6 sind wohl selbsterklärend.
port ist der TCP Port und erklärt sich ebenfalls.
tls sagt dem Bind das wir tls sprechen möchten.
local-tls verweißt auf den gleichnamigen tls Block über welchen man seine TLS Konfiguration vornimmt.
http ist für DoH.
default gibt den eigentlichen endpoint für die DoH Abfragen an, im default ist es /dns-query

Da der Server unsere DNS Abfragen erledigen soll, müssen wir ihm dieses noch per allow-recursion-on auf den jeweiligen Adressen erlauben.

Als nächstes wird die eigentliche TLS Terminierung konfiguriert (das lässt sich ebenfalls auslagern, wenn gewünscht). Dafür wird der folgende Block, außerhalb der Options Blocks, ergänzt:

tls local-tls {
    cert-file "/usr/local/etc/ssl/wild.kernel-error.de/2022/ecp/chain.crt";
    key-file "/usr/local/etc/ssl/wild.kernel-error.de/2022/ecp/http.key";
    dhparam-file "/usr/local/etc/ssl/dhparam.pem";
    protocols { TLSv1.2; TLSv1.3; };
    ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256";
    prefer-server-ciphers yes;
    session-tickets no;
};

local-tls ist dabei der name des Blocks. Auf diesen verweisen wir oben.
cert-file ist der Pfad zum Zertifikat. Ich habe dort nicht nur das Zertifikat, sondern die gesamte Chain, also mit Intermediate und Root.
key-file ist der Pfad zum Key des Zertifikates.
dhparam-file ist der Pfad zu den Diffie-Hellman Parametern.
protocols definiert die zu verwendenden TLS Protokolle. In diesem Beispiel TLS1.2 sowie TLS1.3.
ciphers definiert die zu verwendenden cipher. Es soll ja „sicher“ bleiben.
prefer-server-ciphers übermittelt dem Client die Information, in welcher Reihenfolge protokoll/cipher Kombinationen probiert werden sollen um einen Match zu finden. Erst das vermeintlich sicherste und dann immer „schlechter“.
session-tickets regelt ob eine Wiederaufnahme von TLS Sessions erlaubt ist oder nicht. Da ich forward secrecy nutzen möchte, ist es deaktiviert.

Damit ist die Konfiguration schon abgeschlossen (Firewall ggf. nicht vergessen!). Also testen….

Ein einfaches Tool dafür ist dog, oder natürlich dig aus den bind-tools aber Version 9.18. Für bind gibt es dann die Optionen +https oder auch +tls

dig +https @dns.kernel-error.de www.kernel-error.de A
dig +tls @dns.kernel-error.de www.kernel-error.de A

Der gleiche Test mit dog, sieht wie folgt aus:

dog www.kernel-error.de --tls "@dns.kernel-error.de"
A www.kernel-error.de. 6h00m00s   148.251.40.23
dog www.kernel-error.de --https "@https://dns.kernel-error.de/dns-query"
A www.kernel-error.de. 6h00m00s   148.251.40.23

Das war es auch schon! Viele Spaß mit einem „besseren“ DNS und wenn es noch Fragen gibt, einfach fragen 🙂

Jetzt mit HTTP/3 und QUIC: Schnelleres Surfen leicht gemacht

Von QUIC habt ihr sicher alle schon gehört, seit knapp Mitte 2021 ist dieser neue Standard fertig und in einem recht einfach zu merkendem RFC 9000 beschrieben.

Im Grunde geht es darum, HTTP Verbindungen schneller zu machen und dabei sogar UDP zum Einsatz zu bringen. Nicht ganz korrekt ist es einfach eine Weiterentwicklung von SPDY.

Um zu testen, ob eine Webseite bereits HTTP/3 also QUIC unterstüzt kann ich euch http3check.net ans Herz legen. Diese gibt, wenn gewünscht, sogar noch ein paar Detailinformationen aus.

Wer sehen möchte, ob sein Browser QUIC „macht“, kann auch nginx.org nutzen. Steht oben „Congratulations! You’re connected over QUIC.“ Dann ist man ein Gewinner.

Die Konfiguration am NGINX ist wie immer sehr einfach und ein sehr gutes Beispiel findet sich direkt von nginx.

Mein NGINX spricht dieses nun ebenfalls, mal sehen ob es Probleme gibt.

FreeBSD 13: Unverschlüsseltes ZFS-Dataset in verschlüsseltes Dataset migrieren​

Bild der Bücher FreeBSD Mastery ZFS und FreeBSD Mastery Advanced ZFS

Mit FreeBSD 13 lässt sich die native ZFS Verschlüsselung direkt nutzen. Dabei muss man zwischen einem komplett verschlüsselten zpool und verschlüsselten Datasets unterscheiden. Hat man einen komplett verschlüsselten zpool, bedeutet es nicht, dass damit auch alle Datasets verschlüsselt sein müssen, so wie es auch nicht bedeutet, dass man Datasets nur verschlüsseln kann, wenn auch der komplette ZFS Pool verschlüsselt ist.

Wer nun also sein FreeBSD auf Version 13 gehoben hat, möchte ggf. einzelne ZFS Datasets verschlüsseln. In der Praxis trifft dieses sicher oft auf Jails zu. Diese liegen in der Regel in eigenen ZFS Datasets und mit dem folgenden Beispiel lassen sich diese und andere Datasets nachträglich verschlüsseln.

Im Beispiel haben wir den Pool zroot und in diesem das Dataset varta. Weder der zpool noch das Dataset sind aktuell verschlüsselt:

root@testtest:/ # zfs list zroot/varta
NAME          USED  AVAIL     REFER  MOUNTPOINT
zroot/varta   100M  12.0G      100M  /zroot/varta
root@testtest:/ # zfs get encryption zroot/varta
NAME         PROPERTY    VALUE        SOURCE
zroot/varta  encryption  off          default
root@testtest:/ #

Aktiviert man bei ZFS Dinge wie Komprimierung oder Deduplikation sind diese immer nur ab dem Moment der Aktivierung und bis genau zur Deaktivierung aktiv. Dieses hat viele Vorteile aber auch Nachteile. So greift dieses nur für Daten, welche neu geschrieben werden. Möchte man dieses nachträglich auf alle Daten anwenden, muss man die Daten komplett neu schreiben. Dieses lässt sich am einfachsten und schnellsten per zfs send und zfs receive erledigen. Wenn man also sein bestehendes Dataset verschlüsseln möchte, dann geht dieses faktisch nicht, sondern man erstellt im Grunde ein neues verschlüsseltes Dataset und schreibt seine Daten dort rein.

Bevor wir nun mit der Migration starten, müssen wir noch eine Kleinigkeit wissen…. Zum Verschlüsseln der Daten benötigen wir noch ein Geheimnis, einen Schlüssel/Key. Dieser kann bei ZFS in verschiedenster Form und an verschiedensten Orten liegen. So könnte man den Key zur Ver- und Entschlüsselung auf einen USB-Stick ablegen. Nur wenn dieser auch im System steckt usw. usw.. Der eingängigste Weg ist sicher ein Passphrase welches per prompt abgefragt wird. Will man sein verschlüsseltes Dataset öffnen, wird man nach einem Kennwort gefragt, welches sich das System bis zum nächsten Reboot oder dem manuellen „Schließen“ des Datasets merkt. Diesen Zustand wollen wir nach der Migration, in diesem Beispiel, erreichen.

Zur Verdeutlichung erstellen wir kurz ein neues verschlüsseltes Dataset:

root@testtest:/ # zfs create -o encryption=on -o keyformat=passphrase -o keylocation=prompt zroot/enc-beispiel
Enter passphrase:
Re-enter passphrase:

Damit haben wir ein neues Dataset welches sofort benutzt werden kann, alles was wir in dieses legen, ist verschlüsselt.

root@testtest:/ # zfs list zroot/enc-beispiel
NAME                 USED  AVAIL     REFER  MOUNTPOINT
zroot/enc-beispiel   200K  12.0G      200K  /zroot/enc-beispiel

Schauen wir in die Optionen des Datasets ist die Verschlüsselung aktiviert und der Schlüssel wird per Prompt vom Benutzer abgefragt:

root@testtest:/ # zfs get encryption,keylocation,keyformat zroot/enc-beispiel
NAME                PROPERTY     VALUE        SOURCE
zroot/enc-beispiel  encryption   aes-256-gcm  -
zroot/enc-beispiel  keylocation  prompt       local
zroot/enc-beispiel  keyformat    passphrase   -

Wie immer wird das Dataset sofort eingehangen:

root@testtest:/ # mount |grep enc-beispiel
zroot/enc-beispiel on /zroot/enc-beispiel (zfs, local, noatime, nfsv4acls)

Nach einem reboot, wird das Dataset nicht automatisch eingehangen, da ZFS den Schlüssel nicht hat. Wenn wir es nun einhängen und ZFS anweisen, den Schlüssel zu laden (Option -l), dann werden wir zur Eingabe des Kennwortes aufgefordert und können das Dataset im Anschluss wieder nutzen:

root@testtest:~ # mount | grep enc-beispiel
root@testtest:~ # zfs get encryption,keylocation,keyformat zroot/enc-beispiel
NAME                PROPERTY     VALUE        SOURCE
zroot/enc-beispiel  encryption   aes-256-gcm  -
zroot/enc-beispiel  keylocation  prompt       local
zroot/enc-beispiel  keyformat    passphrase   -
root@testtest:~ # mount | grep enc-beispiel
root@testtest:~ # zfs mount -l zroot/enc-beispiel
Enter passphrase for 'zroot/enc-beispiel':
root@testtest:~ # mount | grep enc-beispiel
zroot/enc-beispiel on /zroot/enc-beispiel (zfs, local, noatime, nfsv4acls)

Gut gut… So viel zu den Basics. Damit ist nun auch klar, warum im nun folgenden zfs send / zfs reveive Beispiel, der Schlüssel einen Umweg nehmen wird. Denn durch das pipen kommen wir so schlecht an die stdin heran, um das Passphrase einzugeben 😉 Wir sind nun also wieder zurück bei unserem unverschlüsselten Dataset varta und dessen Migration in einen verschlüsselten Zustand. Als erstes legen wir nun das gewünschte Passphrase in einer Datei ab:

root@testtest:~ # echo 'Tolles-Kennwort' > /kennwort.txt
root@testtest:~ # cat /kennwort.txt 
Tolles-Kennwort

Ebenfalls erstellen wir einen snapshot vom Dataset varta, welchen wir zur Migration nutzen:

root@testtest:~ # zfs snapshot zroot/varta@migration
root@testtest:~ # zfs list -t snapshot
NAME                    USED  AVAIL     REFER  MOUNTPOINT
zroot/varta@migration     0B      -      100M  -

Jetzt kann die eigentliche Migration starten:

root@testtest:~ # zfs send zroot/varta@migration | zfs receive -F -o encryption=on -o keyformat=passphrase -o keylocation=file:///kennwort.txt zroot/en-varta
root@testtest:~ # zfs list zroot/varta zroot/en-varta
NAME             USED  AVAIL     REFER  MOUNTPOINT
zroot/en-varta   100M  11.8G      100M  /zroot/en-varta
zroot/varta      100M  11.8G      100M  /zroot/varta
root@testtest:~ # zfs list -t snapshot
NAME                       USED  AVAIL     REFER  MOUNTPOINT
zroot/en-varta@migration   112K      -      100M  -
zroot/varta@migration        0B      -      100M  -

Das ist schnell und einfach, oder? Natürlich liegt nun noch immer das Passphrase offen in einer Datei im root des Systems. Wir müssen nun also den Ort des Schlüssels auf prompt ändern:

root@testtest:~ # zfs get keylocation zroot/en-varta
NAME            PROPERTY     VALUE                 SOURCE
zroot/en-varta  keylocation  file:///kennwort.txt  local
root@testtest:~ # zfs set keylocation=prompt zroot/en-varta
root@testtest:~ # zfs get keylocation zroot/en-varta
NAME            PROPERTY     VALUE        SOURCE
zroot/en-varta  keylocation  prompt       local

Damit kann die Datei mit dem Passphrase gelöscht werden:

root@testtest:~ # rm /kennwort.txt

Ebenfalls kann nun auch das unverschlüsselte Dataset weg:

root@testtest:~ # zfs destroy -r zroot/varta

Wenn man nun möchte, kann man das neue Dataset natürlich an die gleiche Stelle mounten oder direkt komplett gleich dem alten benennen:

root@testtest:~ # zfs rename zroot/en-varta zroot/varta

Damit ist die Migration fertig und das Dataset ist verschlüsselt:

root@testtest:~ # zfs list zroot/varta
NAME          USED  AVAIL     REFER  MOUNTPOINT
zroot/varta   100M  11.9G      100M  /zroot/varta
root@testtest:~ # zfs get encryption,keylocation,keyformat zroot/varta
NAME         PROPERTY     VALUE        SOURCE
zroot/varta  encryption   aes-256-gcm  -
zroot/varta  keylocation  prompt       local
zroot/varta  keyformat    passphrase   -

Es sieht nun nach sehr viel aus, ist es aber nicht und es lässt sich sogar automatisieren.

Fragen? Dann fragen!

Linux Mint 20.1: Verschlüsselten ZFS-Root-Pool während der Installation einrichten​

Zu ZFS und warum man es haben will, muss ich hier sicher nicht mehr viel schreiben. Ebenfalls wird sich niemand mehr die Frage stellen, warum man seine Festplatte verschlüsseln möchte.

ABER wenn man nun Linux, ZFS und Verschlüsselung kombinieren möchte, dann gibt es etwas, worüber es sich zu schreiben lohnt. Wie man nun also sehr einfach unter sein Linux Mint >= 20.x einen native encryptet zfs root pool bekommt, darum geht es hier.

Linux ZFS Encryption enter password.

Linux Mint bringt seit Version 20 bereits alles mit um dieses ohne besonderen Aufwand zu erledigen. Natürlich ging es bereits schon früher, nur war es dann fast nur über den Weg möglich, die Festplatte inkl. Grub vollständig von Hand vor und während der Installation selbst fertig zu machen. Dieses ist… Sagen wir mal… „Für den erweiterten Anwender schon fast nicht zu machen“.

Zurück zu Linux Mint 20.x!

Wie erwähnt bringt diese Version alles Nötige mit, nur der Installer ist noch nicht ganz so weit und man muss ihm mit zwei Kleinigkeiten unter die Arme greifen. Einmal muss man die nötigen Pakete im Live-System nachinstallieren. Also einfach die gewünschte Version von Linux mit downloaden und davon booten. Ist man im Live-System angekommen, öffnen man ein Terminal und wirft die Installation der Pakete mit folgendem Befehl an:

sudo aptitude -y install libzfs2linux zfs-initramfs zfsutils-linux zfs-zed

Möchte man nun einfach nur sein Linux Mint auf einem ZFS Pool installieren, ist man schon fertig. Einfach den Installer starten und beim Punkt, auf welche Festplatte man sein neues System installieren möchte auf Advanced features… klicken und unten auf EXPERIMENTAL: Erase disk and use ZFS. Fertig ist!

Möchte man noch seinen zroot Pool verschlüsseln fehlt noch die zweite Kleinigkeit. Da der Installer den Wunsch und somit ebenfalls das Kennwort zur Verschlüsselung des ZFS Pools nicht abfragt, muss man es dem Installer mitgeben, bevor er mit seiner Arbeit startet. Dazu bleibt man im Terminal und editiert das Installerscript wie folgt:

sudo vi /usr/share/ubiquity/zsys-setup

Hier sucht man nun die Sektion, in welcher es ums Anlegen der ZFS Pools geht ?zpool create wäre eine gute Suche 😉

Hat man dieses gefunden (derzeit sollte es genau zwei Mal im Installerscript auftauchen) setzt man einfach vor das zpool create des rpools ein:

echo 'SUPER-SECURE' | 

Die Hochkomma bleiben dabei stehen und ihr ersetzt SUPER-SECURE durch euer gewünschtes Kennwort, was sich natürlich schon jeder gedacht hat. Damit wird dem zpool create rpool später im Installationsprozess euer Kennwort übergeben.

Fehlen nur noch die Optionen beim Erstellen des Pools, damit er ihn auch verschlüsselt. Dafür bitte folgendes vor die Zeile -O mountpoint=/ -R „${target}“ rpool „${partrpool}“ eintragen:

-O encryption=aes-256-gcm \
-O keylocation=prompt \
-O keyformat=passphrase \

Am Ende sieht der Ausschnitt also beispielhaft wie folgt aus:

echo 'Kennwort!' | zpool create -f \
        -o ashift=12 \
        -O compression=lz4 \
        -O acltype=posixacl \
        -O xattr=sa \
        -O relatime=on \
        -O normalization=formD \
        -O mountpoint=/ \
        -O canmount=off \
        -O dnodesize=auto \
        -O sync=disabled \
        -O encryption=aes-256-gcm \
        -O keylocation=prompt \
        -O keyformat=passphrase \
        -O mountpoint=/ -R "${target}" rpool "${partrpool}"

Das war es auch schon. Jetzt, wie gewohnt den Installer starten und schon wird man nach dem Reboot aufgefordert seinen ZFS Pool mit einem Kennwort zu entschlüsseln. Oh, geht natürlich so mit EFi und lagacy und überhaupt….

Ob alles so funktioniert hat, wie man es sich vorstellt, lässt sich direkt nach dem ersten Boot ins neue System prüfen. Einfach Terminal öffnen und mittels folgender Befehle prüfen, ob die Verschlüsselung aktiviert ist und sich auch sauber durch den Pool bis zu den Benutzerdaten weiter vererbt hat:

test@test-VirtualBox:~$ sudo zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
bpool  1,38G  97,3M  1,28G        -         -     0%     6%  1.00x    ONLINE  -
rpool  26,5G  4,46G  22,0G        -         -     3%    16%  1.00x    ONLINE  -
test@test-VirtualBox:~$ sudo zpool get feature@encryption
NAME   PROPERTY            VALUE               SOURCE
bpool  feature@encryption  disabled            local
rpool  feature@encryption  active              local
test@test-VirtualBox:~$ sudo zfs get encryption rpool/USERDATA/test_9d9i92
NAME                        PROPERTY    VALUE        SOURCE
rpool/USERDATA/test_9d9i92  encryption  aes-256-gcm  -
test@test-VirtualBox:~$

Hier noch Bilder zum Klicken, das mögen ja viele.

Fragen? Dann fragen…

Windows Server 2012 R2: RRAS mit sicheren Cipher Suites härten

RRAS Routing und RAS Icon.

Wenn ich schon bei den ganzen Microsoft Themen bin, warum nicht gleich noch „sicheres“ RRAS Routing- und RAS  mit einem Windows Server 2012 R2?! Im speziellen Beispiel an einem L2TP IPsec VPN für Clientsysteme.

Hat mein alles im Routing und RAS konfiguriert verbinden sich die Clients im Standard mit etwas wie: SHA-1 3DES und der DH Group 2 was ein modp1024 (1024-bit) ist. Pfffffff SHA-1 ja, ok… 3DES muss nicht unbedingt und modp1024 möchte man auch nicht oder?

Screenshot der Windows-Firewall mit erweiterter Sicherheit und angezeichnetem Klickpfad um die Sichereheitsmethoden zu konfigurieren.

Wie konfiguriert man dieses nun also „weg“? Ich muss zugeben dafür einige Zeit gelesen zu haben. Dann ich hätte erwartet dieses irgendwo in der Nähe vom Routing und RAS konfigurieren zu müssen. Dem ist aber nicht so. Die Konfiguration dieser Parameter findet man über die Windows-Firewall mit erweiterter Sicherheit auf dem Windows Server. Irgendeinen Grund wird es schon haben es dort zu ~verstecken~ und sicher ist dieser absolut einleuchtend, wenn man sich näher damit auskennt. Ich hätte dort von alleine sicher niemals gesucht. Man findet es also über: Windows Firewall ==> recht Maus: Windows-Firewall mit erweiterter Sicherheit ==> Eigenschaften ==> IPsec-Einstellungen ==> IPsec-Standardeinstellungen Button „Anpassen“ ==> Schlüsselaustausch.

Hier kann man nun die Einstellungen konfigurieren… *kopfschüttel* Windows Firewall…. Hat jemand eine Idee warum es ausgerechnet da ist? Wie auch immer…

Screenshot der Windows-Firewall mit erweiterter Sicherheit und angezeichnetem Klickpfad um die Datenschutzeinstellungen zu konfigurieren.

Steht der Windows RRAS hinter einem NAT sollte man ebenfalls noch UDP Encapsulation aktivieren. Dafür habe ich folgendes Registry File für euch:

Nach diesen Änderungen sollte man natürlich ganz Windowslike den Server einmal durchstarten. Baut man nun von einem Windows Client eine neue VPN Verbindung auf kann man auf diesem natürlich per PowerShell (bitte mit erweiterten Rechten) prüfen ob seine Änderungen gegriffen haben:

Get-NetIPsecMainModeSA | Select-Object -First 1
Screenshot des Kommandos Get-NetIPsecMainModeSA | Select-Object -First 1 inkl Konsolenausgabe.

Wichtig sind dann:

CipherAlgorithm
HashAlgorithm
GroupId

Im Standard wäre es:

Encryption: 3DES
Authentication/Integrity: SHA-1
Key Size: DH Group 2 (1024 bit)

Nach unseren Änderungen:

Encryption: AES256
Authentication/Integrity: SHA-1
Key Size: DH Group 20 (384-bit elliptic curve group)

Windows Firewall…. Pfff… Ja natürlich lässt sich das Thema mit Gruppenrichtlinien erschlagen, nur ist dieses noch einmal ein Thema für sich, hm? Vor allem habe ich mich sehr schwer damit getan hier die Reihenfolge korrekt vorzugeben.

Fragen? Dann wie immer fragen 🙂

WSUS-Bereinigung: Timeouts beheben und Speicherplatz freigeben

WSUS Windows Server Update Service Icon

Pfffff…. Einen dauerhaft richtig gut laufenden WSUS Server habe ich tatsächlich noch nie gesehen; was auch an mir liegen kann ;-). Irgendwann werden die Dinger laaaaannnnggggssssaaammmm. Dann gibt es Timeouts, dann läuft irgendwann die Serverbereinigung nicht mehr durch und man muss sich mit so einem System beschäftigen, bevor die Platten voll sind.

WSUS ist einfach kein Dienst, den man konfiguriert und dann läuft er, abgesehen von Sicherheitsupdates, durch. Nein… WSUS möchte dauerhaft Aufmerksamkeit. Was mir dabei so aufgefallen ist, möchte ich kurz mit euch teilen!

Screenshot inkl. Klickpfade der WSUS Update Service Oberfläche zum Konfigurieren der Produkte und Klassifizierungen.

Mache NIE (wirklich NIE) Treiberupdates über WSUS. Hier explodiert der Platzverbrauch für die Updates!
Sollte es jemand aktiviert haben. Als erstes über „Windows Server Update Services (WSUS) ==> Optionen ==> Produkte und Klassifizierungen ==> Klassifizierungen ==> Treiber“ rauswerfen. Dann über „Updates ==> Alle Updates“ zu den Dropdown Menus und hier: Genehmigung: Genehmigt und Status: Alle. Warten bis die Liste vollständig ist und nach Klassifizierung sortieren. Alle Treiberupdates auswählen und ablehnen.

Was mir auch geholfen hat ist das folgende PowerShell Script. Dieses läuft zwar ewig, räumt aber viel weg:

[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
Screenshot der Windows PowerShell ISE inkl. script.
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer();
$wsus.GetUpdates() | Where {$_.IsDeclined -eq $true} | ForEach-Object {$wsus.DeleteUpdate($_.Id.UpdateId.ToString()); Write-Host $_.Title removed }

Wo wir beim Aufräumen sind… Es gibt so einen wunderschönen „Assistent für die Serverbereinigung“. Dieser sollte überflüssige Updates weg löschen. Also wenn die Computer für die Updates nicht mehr da sind, die Updates von anderen ersetzt wurden oder heruntergeladene Updates abgelehnt wurden. Es ergibt daher Sinn, diesen Assistenten ggf. täglich laufen zu lassen. Er ist nur leider nicht direkt zu automatisieren. Dafür benötigt man ebenfalls ein PowerShell Script welches man täglich per Aufgabenplanung ausführen lässt. Mir hat folgendes bisher immer gute Dienste geleistet.

# Variablen
$DateFormat = Get-Date -format yyyyMMdd-HH-mm
$Logfile = "H:\Logs\wsus-bereinigung-$DateFormat.log"

# WSUS Bereinigung durchführen
Invoke-WsusServerCleanup -CleanupObsoleteUpdates -CleanupUnneededContentFiles -CompressUpdates -DeclineExpiredUpdates -DeclineSupersededUpdates | Out-File $Logfile

# Mail Variablen
$MailSMTPServer = "smtp.kernel-error.de"
$MailFrom = "administrator@firma88.inf"
$MailTo = "dev@null.de"
$MailSubject = "${env:COMPUTERNAME} Bereinigung $DateFormat"
$MailBody = Get-Content $Logfile | Out-String

# Mail versenden
Send-MailMessage -SmtpServer $MailSMTPServer -From $MailFrom -To $MailTo -subject $MailSubject -body $MailBody -Encoding Unicode

Es räumt auf, erstellt ein Logfile und sendet einem zusätzlich noch eine Status E-Mail darüber zu.

Was aber wenn man in der WSUS Timeout Hölle gefangen ist und die ganzen Versuche fehlschlagen das Ding wieder „sauber“ zu machen? Dabei hat mir folgendes geholfen.

IIS umstellen:
IIS-Manager ==> Anwendungspools ==> WsusPool ==> Erweiterte Einstellungen

Limit für den privaten Speicher (KB): 6000000
Maximale Anzahl von Arbeitsprozessen: 0
Startmodus: AlwaysRunning

Nicht vergessen den IIS neu zu starten oder besser den ganzen Server, es ist ja ein Windows 😉

Screenshot der Database Properties - SUSDB zur WSUS Datenbank.

Windows Internel Database WDI umstellen:
Mit dem Microsoft SQL Server Management Studio zu folgendem Server verbinden: \\.\pipe\MICROSOFT##WID\tsql\query

Dann „Databases ==> SUSDB ==> rechte Maus Properties ==> Options ==> „Compatibility level:“ SQL Server 2012 (110)

Man kann hier auch über „Databases ==> SUSDB ==> rechte Maus Tasks ==> Shrink ==> Database“ den freien Speicherplatz der Datenbank zusammenfassen lassen.

Screenshot der Database Properties - SUSDB zur WSUS Datenbank. Shrink Database.

Wenn man schon hier ist lässt sich die Synchronisierungshistory mit folgender SQL Query aufräumen:

USE SUSDB
GO
DELETE FROM tbEventInstance WHERE EventNamespaceID = '2' AND EVENTID IN ('381', '382', '384', '386', '387', '389')
Screenshot vom Micosoft SQL Server Manager mit SQL Query zur Bereinigung der WSUS Datenbank.

Einfach „New Query“ copy&paste „Execute“.

Wenn die Serverbereinigung weiterhin hängen bleibt hilft es scheinbar oft das erste Update in der „zu löschen“ Liste von Hand zu löschen:

USE SUSDB
GO
exec spGetObsoleteUpdatesToCleanup

Jetzt die erste Update ID notieren und:

USE SUSDB
GO
exec spDeleteUpdate @localUpdateID=HIERUPDATEID

Über den Weg lässt sich auch kontrollieren ob die Versuche das Ding aufzuräumen irgendwie einen Fortschritt bringen. Kommt man auch so nicht weiter hat mir folgende SQL Query geholfen (auch wenn sie unglaublich lange läuft):

USE SUSDB
DECLARE @var1 INT, @curitem INT, @totaltodelete INT
DECLARE @msg nvarchar(200)
CREATE TABLE #results (Col1 INT) INSERT INTO #results(Col1)
EXEC spGetObsoleteUpdatesToCleanup
SET @totaltodelete = (SELECT COUNT(*) FROM #results)
SELECT @curitem=1
DECLARE WC Cursor FOR SELECT Col1 FROM #results
OPEN WC
FETCH NEXT FROM WC INTO @var1 WHILE (@@FETCH_STATUS > -1)
BEGIN SET @msg = cast(@curitem as varchar(5)) + '/' + cast(@totaltodelete as varchar(5)) + ': Deleting ' + CONVERT(varchar(10), @var1) + ' ' + cast(getdate() as varchar(30))
RAISERROR(@msg,0,1) WITH NOWAIT
EXEC spDeleteUpdate @localUpdateID=@var1
SET @curitem = @curitem +1
FETCH NEXT FROM WC INTO @var1
END
CLOSE WC
DEALLOCATE WC
DROP TABLE #results

Ebenfalls gibt es die Möglichkeit seine Datenbank zu re-indexieren. Indexe in einer Datenbank neu aufzubauen hat selten geschadet, hm?

/****************************************************************************** 
This sample T-SQL script performs basic maintenance tasks on SUSDB 
1. Identifies indexes that are fragmented and defragments them. For certain 
   tables, a fill-factor is set in order to improve insert performance. 
   Based on MSDN sample at http://msdn2.microsoft.com/en-us/library/ms188917.aspx 
   and tailored for SUSDB requirements 
2. Updates potentially out-of-date table statistics. 
******************************************************************************/ 
 
USE SUSDB; 
GO 
SET NOCOUNT ON; 
 
-- Rebuild or reorganize indexes based on their fragmentation levels 
DECLARE @work_to_do TABLE ( 
    objectid int 
    , indexid int 
    , pagedensity float 
    , fragmentation float 
    , numrows int 
) 
 
DECLARE @objectid int; 
DECLARE @indexid int; 
DECLARE @schemaname nvarchar(130);  
DECLARE @objectname nvarchar(130);  
DECLARE @indexname nvarchar(130);  
DECLARE @numrows int 
DECLARE @density float; 
DECLARE @fragmentation float; 
DECLARE @command nvarchar(4000);  
DECLARE @fillfactorset bit 
DECLARE @numpages int 
 
-- Select indexes that need to be defragmented based on the following 
-- * Page density is low 
-- * External fragmentation is high in relation to index size 
PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121)  
INSERT @work_to_do 
SELECT 
    f.object_id 
    , index_id 
    , avg_page_space_used_in_percent 
    , avg_fragmentation_in_percent 
    , record_count 
FROM  
    sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f 
WHERE 
    (f.avg_page_space_used_in_percent < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count < page_count - 1) 
    or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0) 
    or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0) 
 
PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20)) 
 
PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121) 
 
SELECT @numpages = sum(ps.used_page_count) 
FROM 
    @work_to_do AS fi 
    INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
    INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
 
-- Declare the cursor for the list of indexes to be processed. 
DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do 
 
-- Open the cursor. 
OPEN curIndexes 
 
-- Loop through the indexes 
WHILE (1=1) 
BEGIN 
    FETCH NEXT FROM curIndexes 
    INTO @objectid, @indexid, @density, @fragmentation, @numrows; 
    IF @@FETCH_STATUS < 0 BREAK; 
 
    SELECT  
        @objectname = QUOTENAME(o.name) 
        , @schemaname = QUOTENAME(s.name) 
    FROM  
        sys.objects AS o 
        INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id 
    WHERE  
        o.object_id = @objectid; 
 
    SELECT  
        @indexname = QUOTENAME(name) 
        , @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END 
    FROM  
        sys.indexes 
    WHERE 
        object_id = @objectid AND index_id = @indexid; 
 
    IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation < 30.0) 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; 
    ELSE IF @numrows >= 5000 AND @fillfactorset = 0 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)'; 
    ELSE 
        SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; 
    PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command; 
    EXEC (@command); 
    PRINT convert(nvarchar, getdate(), 121) + N' Done.'; 
END 
 
-- Close and deallocate the cursor. 
CLOSE curIndexes; 
DEALLOCATE curIndexes; 
 
 
IF EXISTS (SELECT * FROM @work_to_do) 
BEGIN 
    PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20)) 
    SELECT @numpages = @numpages - sum(ps.used_page_count) 
    FROM 
        @work_to_do AS fi 
        INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 
        INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 
 
    PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20)) 
END 
GO 
 
 
--Update all statistics 
PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121)  
EXEC sp_updatestats 
PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121)  
GO

Meist scheint eine Kombination auf verschiedenen Dingen zu helfen. Bisher hat mir zumindest immer irgendetwas davon geholfen. Auch wenn ich dafür einige Zeit in Suchmaschinen verschwenden musste. Dieses ist also eher so etwas wie eine Sammlung gefundener Dinge 😉

Fragen? Dann fragen…

« Ältere Beiträge Neuere Beiträge »

© 2025 -=Kernel-Error=-

Theme von Anders NorénHoch ↑