Tag-Archiv für » iis «

29 | 06 | 2017

CipherSuites in Windows Server vorgeben und überwachen – Teil 1

Geschrieben von um 20:13 Uhr

Wir haben die Aufgabe bekommen, eine ganz bestimmte Auswahl an Cipher Suites für einige Applikationsserver zuzulassen. Gesagt, getan. Nach Prüfung der Kompatibilität zu anderen Applikationen und einem Test in der Testumgebung haben wir das Gewünschte umgesetzt…

…nur um festzustellen, dass anscheinend bei jedem Patchday irgendetwas dabei ist, das die Liste wieder auf den vorgegebenen Zustand zurück stellt. Eine Automatisierungs- und nach Möglichkeit Überwachungslösung musste also her.

Die benötigte Einstellung kann man ganz einfach mit einem PowerShell-Einzeiler

Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002" -Name "Functions" -Value "TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA"

oder visuell durch IISCrypto erledigen. Beide Verfahren tun das gleiche: Den String für den Wert „Functions“ als kommagetrennte Liste der Cipher Suites zusammenbasteln und in die Registry schreiben. Der Haken an der Sache: damit das Ganze auch in Kraft tritt, wird ein Reboot der Maschine benötigt.

Die Erzwingung der korrekten Werte in der Registry erreicht man einfach per Group Policy Preference:

Somit ist erst einmal der Automatisierungsteil erledigt. Nun könnte aber jemand Böses (OK, Böses mit Adminrechten) die Cipher Suites verstellen, die Kiste rebooten, und die veränderte Einstellung würde bis zum nächsten Reboot in Kraft bleiben. Zur Überwachung ist es also nicht ausreichend, den Wert in der Registry abzufragen, man muss schauen, was wirklich aktiv ist.

Der erste Wurf war die Nutzung der Windows-API. Nach einigem Googlen fand ich zwar immer noch kein PowerShell-Modul dafür, aber immerhin den folgenden vielversprechenden Thread auf StackOverflow: https://stackoverflow.com/questions/19695623/how-to-call-schannel-functions-from-net-c

Leicht modifiziert, wird daraus ein binäres Modul:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Management.Automation;

namespace CipherSuites
{
    [Cmdlet(VerbsCommon.Get, "CipherSuites")]
    public class GetCipherSuitesCmdlet: Cmdlet
    {
        [DllImport("Bcrypt.dll", CharSet = CharSet.Unicode)]
        static extern uint BCryptEnumContextFunctions(uint dwTable, string pszContext, uint dwInterface, ref uint pcbBuffer, ref IntPtr ppBuffer);
        [DllImport("Bcrypt.dll")]
        static extern void BCryptFreeBuffer(IntPtr pvBuffer);
        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPT_CONTEXT_FUNCTIONS
        {
            public uint cFunctions;
            public IntPtr rgpszFunctions;
        }
        public const uint CRYPT_LOCAL = 0x00000001;
        public const uint NCRYPT_SCHANNEL_INTERFACE = 0x00010002;
        public const uint CRYPT_PRIORITY_TOP = 0x00000000;
        public const uint CRYPT_PRIORITY_BOTTOM = 0xFFFFFFFF;
        static uint cbBuffer = 0;
        static IntPtr ppBuffer = IntPtr.Zero;
        string so;
        uint Status;

        protected override void BeginProcessing()
        {
             cbBuffer = 0;
             ppBuffer = IntPtr.Zero;
        }
        protected override void ProcessRecord()
        {
        }
        protected override void EndProcessing()
        {
            Status = BCryptEnumContextFunctions(
                         CRYPT_LOCAL,
                         "SSL",
                         NCRYPT_SCHANNEL_INTERFACE,
                         ref cbBuffer,
                         ref ppBuffer);
            if (Status == 0)
            {
                CRYPT_CONTEXT_FUNCTIONS functions = (CRYPT_CONTEXT_FUNCTIONS)Marshal.PtrToStructure(ppBuffer, typeof(CRYPT_CONTEXT_FUNCTIONS));
                IntPtr pStr = functions.rgpszFunctions;
                for (int i = 0; i < functions.cFunctions; i++)
                {
                    so = Marshal.PtrToStringUni(Marshal.ReadIntPtr(pStr));
                    WriteObject(so);
                    pStr = new System.IntPtr((pStr.ToInt64() + (IntPtr.Size)));
                }
                BCryptFreeBuffer(ppBuffer);
            }
            GC.Collect();
            GC.WaitForFullGCComplete();
        }
    }
}

Das Ergebnis könnt ihr ohne jede Gewähr hier herunterladen: CipherSuites.dll. Ich werde das nicht offiziell auf Gallery veröffentlichen – warum, erkläre ich unten. Für das Monitoring wird das noch NAGIOS-konform in PowerShell verpackt:

$root = (get-item $PSScriptRoot).parent.FullName.ToString()
if (!(Get-Command "Get-CipherSuites" -EA SilentlyContinue)) { Import-Module ($root + "\modules\CipherSuites.dll") }
$additional_cs_is_critical = $true
$missing_cs_is_critical = $false
$cs_list = "TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA"
$exitCodes = @{
"UNKNOWN" = 3;
"CRITICAL" = 2;
"WARNING" = 1;
"OK" = 0
}
if (Get-Command "Get-CipherSuites" -EA SilentlyContinue) { $cs = Get-CipherSuites } else { $cs = @() }
$cs_string = $cs -join ","
if ($cs_list-like $cs_string) {
    $state = "OK"
    $status_msg = "CipherSuites are in order"
    $perfdata_msg = "ciphersuites=$($cs.count);0;0"
} elseif (!($cs)) {
    $state = "UNKNOWN"
    $status_msg = "Could not retrieve CipherSuites"
    $perfdata_msg = "ciphersuites=-1;-1;-1"
} else {
    $nadd = 0
    $nmiss = 0
    $csl = $cs_list -split ","
    foreach ($cx in $cs) {
        if ($csl -notcontains $cx) {
            $nadd++
        }
    }
    foreach ($cx in $csl) {
        if ($cs -notcontains $cx) {
            $nmiss++
        }
    }
    if (($nadd -eq 0) -and ($nmiss -eq 0)) {
        $status_msg = "CipherSuites reordered!"
        $perfdata_msg = "ciphersuites=$($cs.count);0;0"
        if ($reordered_cs_is_critical) {
            $state = "CRITICAL"
        } else {
            $state = "WARNING"
        }
    } else {
        $status_msg = "CipherSuites mismatched: $nmiss missing, $nadd additional"
        $perfdata_msg = "ciphersuites=$($cs.count);$nmiss;$nadd"
        if (($additional_cs_is_critical -and ($nadd -gt 0)) -or ($missing_cs_is_critical -and ($nmiss -gt 0))) {
            $state = "CRITICAL"
        } else {
            $state = "WARNING"
        }
    }
}
Write-Host $state": $status_msg|$perfdata_msg"
exit $exitCodes[$state]

Funktioniert soweit, bereits die ersten Tests ließen aber den dringenden Verdacht aufkommen, dass die Windows-API lediglich die konfigurierten, nicht jedoch die ausgeführten Cipher Suites zurückgeben. Ich wollte es genau wissen und habe schnell den Process Monitor angeworfen. Und siehe da…
Hat sich ja wirklich gelohnt, dafür noch eine API zu schreiben. Dann müssen wir also für die Überwachung etwas bemühen, das agiert wie https://testssl.sh. Wie das ausgeht, berichte ich im nächsten Teil.

Happy decrypting!

Tags » , , , , , , «

+

29 | 06 | 2013

Server 2012/IIS8: Bei Root-Zertifikaten aufpassen!

Geschrieben von um 13:15 Uhr

Vor einiger Zeit musste ich innerhalb einer SCCM 2012-Installation einen Web Based Management Point einrichten. Enterprise PKI hatte der Kunde schon am Start, ein Paar Zertifikatsvorlagen udn Autoenrollment-Einstellungen mussten angepasst werden, das war aber nicht weiter wild. Nun mochte aber der Management Point, sobald man ihn auf HTTPS umstellen, sich nicht initialisieren.

Hintergrund: Der MP läuft im IIS. Nach der Grundinstallation versucht er – unter zertifikatsbasierter Authentifizierung – zum sich selbst zu verbinden und eine Liste der Management Points abzufragen. An dieser Stelle bereits loggte der IIS den Fehler 403.16 mit dem erweiterten Fehlercode 2148204809 (in hex 0x800b0109), was soviel bedeutet wie CERT_E_UNTRUSTEDROOT. Sowohl das Server- als auch das Client-Zertifikat sind von der gleichen Issuing CA ausgestellt, somit ist die RCA ebenfalls die gleiche, sie ist per GPO eingetragen, alle Test laufen ohne Beanstandungen durch – was ist also das Problem?

Nun, die Lösung war einfach, auch wenn die Ursache eher als „Bug“ denn als „Feature“ einzustufen ist. Im „Trusted Roots“ des Servers (wie auch jeder anderen Maschine in diesem AD, da die PKI-GPO ganz oben gelinkt ist) befand sich ein Zertifikat von Thawte, welches zwar für eine „Thawte Primary Root CA“ ausgestellt war, jedoch nicht von ihr selber signiert, sondern von einer „Thawte Primary Server CA“. Insofern ist diese Root CA genaugenommen keine Root CA, und das Zertifikat hätte woanders rein gemusst. Man sollte aber meinen, das betrifft uns nicht, da wir ja nur mit Zertifikaten aus der internen PKI hantieren. Bis IIS 7.5 würde das auch stimmen. IIS 8 hingegen verzeiht einem Derartiges nicht und legt das oben beschriebene Verhalten an den Tag. Dazu gibt es auch einen Fast Publish-Artikel: http://support.microsoft.com/kb/2802568.

Entdeckt wurde das Verhalten anscheinend im Zusammenhang mit Lync 2013. Welch Wunder 😉

Tags » , , , , , «

+