Archiv für die Kategorie » Server «

19 | 07 | 2017

PowerShell Quirks: Move-ADDirectoryServerOperationMasterRole und der Schema Master

Geschrieben von um 20:54 Uhr

Heute mal ein Quickie, nur als Merker für den Hinterkopf:

Auch wenn es im Internet von Anleitungen nur so wimmelt, die den Befehl

Move-ADDirectoryServerOperationMasterRole <DomainController> -OperationMasterRole SchemaMaster

zeigen, gibt es dazu ein Wort zu sagen: Das funktioniert nur, wenn man

  • entweder den Befehl auf dem DC aufruft, zu dem die Rolle transferiert werden soll
  • oder den Parameter -Force mit angibt.

Alle anderen Rollen lassen sich auch ohne -Force beliebig hin und her transferieren.

Happy FSMOing!

Tags » , , «

+

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 » , , , , , , «

+

21 | 06 | 2017

PowerShell: Ein schneller Versuch, ADMX zu parsen

Geschrieben von um 19:45 Uhr

Hier ein bißchen Code, um alle Einstellungen aus ADMXen inklusive Registry-Pfad und -Typ zu exportieren. Ein paar Typen könnten noch fehlen …

Achtung! In Zeilen 10 und 11 frißt der Syntax Highlighter den Typ (quadratische Klammer auf)xml(quadratische Klammer zu) unmittelbar vor der runden Klammer. Noch weiß ich nicht, wie ich ihn überredet bekomme, das anzuzeigen. Bis dahin hier der Quelltext zum Download: admx_parser

$language = "de-DE"
$root = "C:\Windows\PolicyDefinitions"
$output = @()

$admx_files = Get-ChildItem $root -Filter "*.admx"
foreach ($admx in $admx_files) {
    $admx_file = Split-Path $admx -Leaf
    $adml = "C:\Windows\PolicyDefinitions\$($language)\$($admx_file.TrimEnd('x'))l"
    if (Test-Path $adml) {
        $admx_data = (Get-Content $admx.FullName)
        $adml_data = (Get-Content $adml -Encoding UTF8)
        $adml_strings = $adml_data.policyDefinitionResources.resources.stringTable.GetEnumerator()
        $policies = $admx_data.policyDefinitions.policies
        foreach ($pol in $policies.policy) {
            $dn_string = $pol.displayName.Substring(9, ($pol.displayName.Length - 10))
            $et_string = $pol.explainText.Substring(9, ($pol.explainText.Length - 10))
            $policy_name = ($adml_strings.Where({$_.id -eq $dn_string})).'#text'
            $adml_strings.Reset()
            $policy_desc = ($adml_strings.Where({$_.id -eq $et_string})).'#text'
            $adml_strings.Reset()
            if ($pol.elements.HasChildNodes) {
                $els = $pol.elements.GetEnumerator()
                foreach ($el in $els) {
                    $reg_value = "$($pol.Key)\$($el.valueName)"
                    switch ($el.Name) {
                        'boolean' { $reg_type = 'REG_DWORD (1)' }
                        'decimal' { $reg_type = 'REG_DWORD' }
                        'text' {
                            if ($el.expandable) {
                                $reg_type = 'REG_EXPAND_SZ'
                            } else {
                                $reg_type = 'REG_SZ'
                            }
                        }
                        'enum' {
                            $ex = $el.FirstChild.FirstChild.FirstChild.Name
                            switch ($ex) {
                                'decimal' { $reg_type = 'REG_DWORD' }
                                'text' {
                                    if ($el.expandable) {
                                        $reg_type = 'REG_EXPAND_SZ'
                                    } else {
                                        $reg_type = 'REG_SZ'
                                    }
                                }
                            }
                        }
                        'list' {
                            $reg_type = 'REG_SZ (list)'
                            $reg_value = $el.Key
                        }
                        default { 
                            Write-Host $el.Name -ForegroundColor Cyan
                            $reg_type = $el.Name
                            $reg_value = $el.Key
                        }
                    }
                    $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file}
                    $output += $out_item
                }
            } else {
                $reg_value = "$($pol.Key)\$($pol.valueName)"
                $reg_type = 'REG_DWORD (1)'
                $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file }
                $output += $out_item
            }
        }
    } else {
        Write-Host "ADML in $language not found: $adml" -ForegroundColor Yellow
    }
}
$output | Export-CSV c:\temp\admx.csv -Encoding UTF8

Enjoy!

Tags » , , «

2

17 | 03 | 2017

„Verbindung verweigert“ zwischen SEP sesam und XenServer: Fake News

Geschrieben von um 8:09 Uhr

Wieder was gelernt: Wenn der Versuch, einen XenServer-Sicherungsclient zur SEP sesam-Topologie hinzuzufügen, mit der Fehlermeldung

E002-HOSTS   Kein Zugang auf Rechner <XENPOOLMASTER>: 2017-03-16 16:41:49: sxs-1500: Error:    Could not login to XEN Server: <class ’socket.error‘>: [[Errno 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte]

quittiert wird, muss es noch lange nicht heißen, dass man etwas falsch gemacht hat oder dass ein Netzwerkproblem vorliegt. Einfach ignorieren und versuchen, einen Sicherungsauftrag anzulegen – die Chancen stehen gut, dass es klappen wird. Sehr gut sogar.

Es geht nämlich bei der Verbindungsprüfung nicht ein einziges Paket Richtung XenServer – ich habe es sogar mit WireShark überwacht, so sehr hat diese Meldung uns aus der Spur gebracht.

Aber so ist es halt mit allen Fake News…

Tags » , , , «

+

04 | 03 | 2017

PowerShell als Malware-Engine war gestern, jetzt ist Group Policy dran…

Geschrieben von um 20:22 Uhr

Interessante und extrem beunruhigende Beobachtung (leider schon im Feld) von Darren Mar-Elia: https://sdmsoftware.com/group-policy-blog/security-related/group-policy-malware-delivery-system/

Aber letztendlich gilt auch hier, was wir schon immer gepredigt haben: Nicht darauf verlassen, dass der Hersteller es besser weiß, sondern den Zugriff nach Möglichkeit explizit steuern.

Tags » , , «

+

14 | 01 | 2017

Der netdom-Befehl und die Sprachverwirrung

Geschrieben von um 11:19 Uhr

In einem aktuellen Projekt schon wieder erlebt und, da man es bis zum nächsten Mal garantiert wieder vergisst, hier zur Erinnerung:

Auf einem deutschen Domain Controller heißt es nicht etwa so wie in der Dokumentation

netdom trust DOMAIN1 /domain:DOMAIN2 /EnableSidHistory:YES

, sondern vielmehr

netdom trust DOMAIN1 /domain:DOMAIN2 /EnableSidHistory:JA

Also, Eselsbrücke: Bei AD-Migrationen gilt das Törtchen-Motto: „Sag‘ JA zu YES“. Happy migrating!

Tags » «

+

24 | 10 | 2016

Exchange 2013 CU14 und 2016 CU3: Probleme mit der Suche

Geschrieben von um 23:42 Uhr

Ein Wort der Warnung: Die vor kurzem erschienenen CUs von Exchange 2013 und 2016 haben anscheinend gravierende Probleme bei der Indizierung.

Ein Beispiel hier: https://social.technet.microsoft.com/Forums/en-US/a05ea439-8d7a-4d83-a8c4-fcc7e78904bf/exchnage-2013-cu-14-database-indexing-failed?forum=exchangesvradmin

In Kürze: Der Indexdienst verweigert für einige (in manchen Fällen auch für alle) Datenbanken die Indizierung. Die einzige Lösung ist die Installation eines zusätzlichen Servers mit einem älteren Patch-Stand und Verschiebung der Postfächer dorthin.

Wer also gerade sein Update plant, sollte lieber etwas warten und v2 oder halt ein nächstes CU installieren.

Tags » , , , , , «

+

09 | 10 | 2016

PowerShell Hack: Mit RDExSessionInfo RDP-Daten geliefert bekommen

Geschrieben von um 0:01 Uhr

Eigentlich ist es eher ein C#-Hack, aber wir wollen ja nicht zu pingelig sein 😉

Die Schwierigkeit, in PowerShell sinnvolle Daten über RDP-Sitzungen zu bekommen, war mir schon sehr lange ein Dorn im Auge. Ich habe mich aber die ganze Zeit davor gedrückt, die Windows-API anzuzapfen und die Infos von dort zu ziehen, aber nachdem Gernot Meyer in diesem TechNet-Thread) schon mal einen Proof Of Concept vorgenommen hat, konnte ich nun nicht mehr an mich halten und habe ein PowerShell-Modul namens RDExSessionInfo erstellt, das einige wichtige Basisdaten liefert:

  • Username, Domäne, Sitzungs-ID und -Status
  • Zeiten für Logon, Connect, Desconnect und Last Input (als DateTime-Objekt)
  • Upstream und Downstream Bytes
  • Client Build, Name und IP

Das ganze habe ich auf der PSGallery veröffentlicht, viel Spaß damit! Eine Status-Seite und kleine Einführung findet man hier im Blog.

Tags » , , , , «

+

07 | 10 | 2016

MAP in einer Workgroup-Umgebung

Geschrieben von um 22:30 Uhr

Heute mal etwas leichtere Kost, eher als Reminder gedacht.

Das Microsoft Assesment & Planning Toolkit ist ja ein bewährtes Mittel zur systemübergreifenden Performance-Analyse. Zum Beispiel, um eine Konsolidierung von Servern von Physik und „wilder Virtualisierung“ auf geordnete virtuelle Plattformen oder in die Cloud zu modellieren. So auch heute. Die Herausforderung jedoch war, dass nur einer der zu betrachtenden Server tatsächlich Domänen-Mitglied ist – und zwar in einer Domäne, in die der MAP-Server natürlich nicht aufgenommen werden durfte! Alle anderen Server gehören keiner Domäne an, und die meisten von ihnen haben nur den Default-„Administrator“ an Konten eingerichtet. Das Kennwort von diesem Administrator ist zwar nicht überall unterschiedlich, aber auch nicht überall gleich – ca. die Hälfte der Server hatte „Kennwort1“, und der Rest teilte sich in etwa gleichmäßig in „Kennwort2“ und „Kennwort3“.

Landet man in einer solchen Situation, so muss man folgendes berücksichtigen:

  • „.\Administrator“ kann MAP nicht erfassen, nur „Administrator“ oder „<COMPUTER>\Administrator“
  • Jedes Account kann in der exakten Schreibweise nur einmal vorkommen, das Erfassen von „Administrator“ mit unteschiedlichen Passwörtern ist also nicht ohne weiteres möglich
  • Im Inventory-Teil kann man jedem Eintrag, wenn man ihn manuell eingibt, oder jeder Liste, wenn man sie aus Dateien einliest, ein eigenes Konto zuweisen.
  • Im Performance-Sammlungs-Teil gibt es diese Funktion aber nicht – da kann man NUR eine Liste von Accounts zum Durchprobieren erfassen!
  • Die Schreibweise „<COMPUTER>\Administrator“ wird im Performance-Sammlungs-Teil nur für den Computer akzeptiert, der auch tatsächlich da steht. Somit mussten wir die Durchprobier-Liste in Form „Administrator – Kennwort1″,“DomainUser – DomainKennwort“, „SERVER2A\Administrator – Kennwort2“, „SERVER2B\Administrator – Kennwort2″,…“SERVER2Z\Administrator – Kennwort2″,“SERVER3A\Administrator – Kennwort3“,… erfassen. Alles andere hat nicht funktioniert.
  • Im Inventory-Teil (WMI) wird hingegen auch ein „fremder“ Computername angenommen, wenn nur der Benutzername und das Kennwort passen.

Last but not least: Die eingegebenen Accounts werden bei Abmeldung „vergessen“ und stehen beim nächsten Aufruf von MAP nicht zur Auswahl.

In einer Domänen-Umgebung wäre das alles überhaupt kein Problem…

 

Tags » , , «

+

17 | 08 | 2016

PowerShell Hack: Schnelle Übersicht über Updates gewinnen

Geschrieben von um 12:01 Uhr

Wenn wir „einfach nur“ wissen wollen, wann auf einem Haufen Maschinen zuletzt Updates installiert wurden und wie viele noch jeweils ausstehen, kann man sich wie folgt behelfen:

$servers = @('SERVER01'
,'SERVER02'
,'SERVER03'
# usw.
)
$script_block = $ExecutionContext.InvokeCommand.NewScriptBlock(@'
$wu_session = New-Object -COM "Microsoft.Update.Session";
$wu_searcher = $wu_session.CreateUpdateSearcher();
$last_instd = ($wu_searcher.QueryHistory(0,1)).Item(0).Date;
$wures = $wu_searcher.Search("IsInstalled=0 and Type='Software'");
$updates_to_install = $wures.Updates.Count;
"$($env:COMPUTERNAME) | $last_instd | $updates_to_install"
'@)
Invoke-Command -ComputerName $servers -ScriptBlock $script_block

Die Reihenfolge der zurückgelieferten Ergebnisse läßt sich hier aber nicht vorhersagen. Möchte man die Ergebnisse genau in der Reihenfolge sehen, wie die Server eingegeben wurden, müsste man den Aufruf einzeln auslösen. Die letzte Zeile ist dann durch

foreach ($server in $servers) {
    Invoke-Command -ComputerName $server -ScriptBlock $script_block
}

zu ersetzen.

Die Auswahl der zu untersuchenden Computer läßt sich freilich auch automatisieren. Um z.B. Überblick über alle Computer in einer OU zu gewinnen, kann man Anfang die Definition des Arrays durch

$servers = (Get-ADComputer -Filter * -SearchBase "OU=Computer,OU=Firma,DC=meine,DC=domain,DC=de" -SearchScope Subtree).Name

ersetzt werden.

Tags » , , , «

+