Skip to main content Scroll Top

LM, NTLMv1 und NTLMv2: sei‘ vobei! (Hexhex)

Martin Handel
AI-Score-B

Texte weitestgehend KI-frei
Bilder durch KI erzeugt
kann Spuren von Nüssen enthalten

LM, NTLMv1 und NTLMv2: sei‘ vobei! (Hexhex)

GTFOFI

Eene meene, alter Mist, Du seiest nicht mehr sehr vermisst! Hexhex!
Was „unsere“ Bibi Bloxberg, als typischen Paarreim, von sich absondert sei hier in diesem Artikel auf die Protokolle LM, NTLMv1 und NTLMv2 bezogen.

Bevor ich hier aber das Rad neu erfinde möchte ich auf den exzellenten Artikel von Fabian Niesen vom 20.02.2024 bei Infrastrukturhelden verweisen. Dieser Artikel ist sehr zu empfehlen und wird durch einen zweiten Artikel des gleichen Autors erweitert.

Rasch jedoch, die Eckdaten der drei Protokolle:

  • LM / LMv1 (LAN Manager): entwickelt in den späten 80ern. Basiert auf DES kann aber nur Kennwörter mit 14 Zeichen verarbeiten. Typisch für SMB, NetBIOS bei Win9x / ME)
  • NTLMv1 (NT LAN Manager): entwickelt 1993. Beruht auf dem NT hash (MD4(UTF-16LE)). Typisch für SMB, RPC, DCOM, „Negotiate / NTLM“
  • NTLMv2 (NT LAN Manager): entwickelt 1998. Stärkerer Challenge-Response als bei NTLMv1 mit HMAC-MD5. Typisch wie NTLMv1 jedoch Fallback wenn Kerberos scheitert.
LM_NTLMv1_NTLMv2

Die Schwachstellen der alten Protokolle

LM_fail

Extrem „crackfreundlich“

Typische Angriffe:

  • Offline Password Cracking – erbeutete LM-Hashes (SAM-Datenbank / AD-Datenbank) können sehr schnell in Klartextkennwörter überführt werden.
  • Rainbow-Tables – vorberechnete Hash-Tabellen sind wegen der 2 x 7 Zeichen-Geschichte hier sehr effizient
  • Halbes Kennwort – 2 x 7 Kennwörter können bequem getrennt geknackt werden.
High-Risk-Legacy-Path!
NTLMv1_fail

MD4-basierter Hash

Typische Angriffe:

  • NTLM-Relay – die Authentisierung per NTLMv1 wird abgefangen und einfach an ein anderes Ziel weiter geleitet.
  • Pass-the-Hash – Impersonieren auf Basis von Samaccountname und MD4-Hash anstelle Kennwort.
  • Downgrade-Attack – ein Fallback auf NTLMv1 kann provoziert werden, wenn das Protokoll noch verwendet wird.
  • Offline-Cracking – Abgefangene Responses oder Hashes (SAM-Datenbank / AD-Datenbank) können so leicht geknackt werden.
Legacy-Risk-Path!
NTLMv2_fail

Challenge-Response-Protokoll

Typische Angriffe:

  • NTLM Relay – Ohne Signing wie bei NTLMv1
  • Pass-the-Hash – siehe NTLMv1
  • Coercion-Angriffe – das Opfer wird gezwungen sich gegenüber einer kompromittierten Maschine per NTLM zu authentisieren, das führt wieder zu NTLM Relay
  • Offline Guessing – schwache Kennwörter in Verbindung mit vielen abgefangenen Responses lassen die Kennwortrekonstruktion zu
  • Fallback – siehe NTLMv1
Legacy-Fallback-Path!

Audit

Bevor wir den drei werten Damen den Mantel reichen und die Tür weisen müssen wir schauen, wo die drei Trümmerlotten vielleicht noch zu Gange sind.

Ich würde mich hier auf das Event mit der Id 4624 konzentrieren. Mit KB5064479 vom 11.07.2025 für Windows 11 24H2 und Server 2025 wurde das Logging zwar erweitert (Applications and Services Logs > Microsoft > Windows > NTLM > Operational.), bringt bei Server 2022 hier nichts.

Es muss nur beachtet werden, dass dieses Event, 4624, sowohl auf den DCs, wie auch auf den Memberserver und Clients zu untersuchen ist. Der Schwerpunkt der Untersuchung ist aber sicherlich auf den Domain Controllern.

GTFO
OldProtocol
Wann wird NTLM eigentlich verwendet?

Ganz einfach:

  1. Wenn der Service nur NTLM unterstützt.
  2. Wenn der Service keinen SPN im Active Directory aufweist (sei es am Computerkonto – Local System / Network Service oder am Benutzerkonto).
  3. Der Aufruf des Services durch eine IP-Adresse* erfolgt
  4. Es eine Fallback von Kerberos V5 auf NTLM gibt (Uhrzeitabweichung, crypto-gap, Key mismatch etc).

*stimmt nicht ganz, siehe unten.

NTLM
PowerShell

Wird so ein Event gefunden liefert das PowerShell-Skript zurück:

  1. QueriedComputer (befragtes System)
  2. WorkstationName (Name des Quellsystems)
  3. IpAddress (die IP Adresse des Quellsystems)
  4. LmPackageName (die Dialektart: LM, NTLMv1, NTLMv2)
PowerShell + XPath

Die PowerShell in Verbindung mit einem XPath- / XML-Filter ist hier das geeignete Werkzeug.
Hierbei wird das Log Security nach dem Event mit der ID 4624 (Ein Konto wurde erfolgreich angemeldet / An account was successfully logged on) durchsucht. Es wird aber gefiltert nach „AuthenticationPackageName“ die nicht Kerberos sind und es unterdrückt Rückgabewerte basierend auf den IPs 0.0.0.0 und 127.0.0.1

# XML-Filter: EventID 4624 + AuthenticationPackageName != "Kerberos"
$xml = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">
      *[System[(EventID=4624)]]
      and
      *[EventData[
            Data[@Name='AuthenticationPackageName']
            and Data[@Name='AuthenticationPackageName']!='Kerberos'
          ]]
    </Select>
  </Query>
</QueryList>
"@

Get-WinEvent -FilterXml $xml -ComputerName CH-VDC01 |
  ForEach-Object {
    $x = [xml]$_.ToXml()
    [pscustomobject]@{
      QueriedComputer = $_.MachineName
      WorkstationName = ($x.Event.EventData.Data | Where-Object Name -eq 'WorkstationName').'#text'
      IpAddress       = ($x.Event.EventData.Data | Where-Object Name -eq 'IpAddress').'#text'
      LmPackageName   = ($x.Event.EventData.Data | Where-Object Name -eq 'LmPackageName').'#text'
    }
  } |
  Where-Object {
    $_.IpAddress -and
    $_.IpAddress -notin @('-', '0.0.0.0', '127.0.0.1')
  } |
  Group-Object -Property IpAddress, LmPackageName, QueriedComputer |
  ForEach-Object { $_.Group | Select-Object -First 1 } |
  Sort-Object -Property IpAddress, LmPackageName
LMNTLM_Off
DomJoinFail01

Abschalten aber richtig!

Bevor man nun LM, NTLMv1 und NTLMv2 abschaltet sind ein paar Punkte zu beachten:

  1. Wenn das Skript oben noch Ergebnisse liefert muss geprüft werden, warum die Quellen noch LM, NTLMv1 und/oder NTLMv2 machen.
  2. Alle Zugriffe auf IP-Adressen müssen über TryIPSPN in der Regel abgefangen werden.
  3. Der Beitritt zur Domäne geht jetzt nur noch über djoin.exe oder die PowerShell*
  4. Autoenrollment / Enrollment von Zertifikaten per GPO und/oder Hand ist nicht mehr ohne weiteres möglich.
  5. Kerberos V5 ist jetzt das zentrale Protokoll. Das heißt man muss auf das DNS, die Uhrzeit, die SPNs, die Computer- und Benutzerkennwörter sowie auf das krbtgt-Konto sehr gut acht geben. Es gibt jetzt keinen Fall-Back mehr!

*Nicht beliebig, sondern genau so:

$Cred = New-object System.Management.Automation.PSCredential "domjoin@ClOUD-HYBRID.DE", (ConvertTo-SecureString -String "KlarTextKennw0rt" -AsPlainText -Force)		
Add-Computer -DomainCredential $Cred -DomainName ClOUD-HYBRID.DE -Restart

Issuing CA / Autoenrollment / Enrollment

Bei einer Issuing CA stößt man auf das Phänomen, dass das Enrollment, egal ob autoenrollment oder manuelles enrollment nicht mehr geht.
Das liegt ursächlich in dem Sachverhalt, dass durch das Abschalten von NTLM die Option eines „pass-through“ weggefallen ist.

Die Issuing CA darf nun aber die Credentials über Kerberos V5 gar nicht an die DCs weiter reichen, weder per „Constrained Delegation“, noch per „resource based Kerberos contrained delegation (RBKCD)“.
Dieses muss schlicht korrigiert werden: die Issuing CA muss das Recht bekommen Credentials an die DCs weiter zu reichen.
Glücklicherweise läuft der Dienst als „Local System“, verfügt somit über das Privileg „Impersonate a client after authentication“.

Constrained Delegation vs. RBKCD

Bitte nicht mehr mit der „Constrained Delegation“ arbeiten. Konten mit „Account is sensitive and can not be delegated“ können/dürfen eine „Constrained Delegation“ nicht verwenden.
Bitte in solchen Szenarien immer mit RBKCD arbeiten.

$CA = Get-ADComputer -Identity CH-VSUBCA01
Set-ADComputer -Identity CH-VDC01 -PrincipalsAllowedToDelegateToAccount $CA
Set-ADComputer -Identity CH-VDC02 -PrincipalsAllowedToDelegateToAccount $CA
Set-ADComputer -Identity CH-VDC03 -PrincipalsAllowedToDelegateToAccount $CA
Set-ADComputer -Identity CH-VDC04 -PrincipalsAllowedToDelegateToAccount $CA

Natürlich: kommt ein DC hinzu oder wird ein alter DC durch einen neuen DC mit gleichem Namen ersetzt muss die Delegation via RBKCD nachgezogen werden.

AutoEnrollmentFail03
AutoEnrollmentFail02
CADelegation01
CADelegation02
CADelegation03

Ok, wie starte ich am besten?

Mit dem Spiel „Wir tuen so, als ob!“.

Die beiden Audit Policies protokollieren wann später eingehendes NTLM geblock würde.

Audit1
Audit2
Audit3
NTLMBlock01

Protected Users

Ohne NTLM zu blockieren kann man schon mal durch die Mitgliedschaft in den „Protected Usersfür User ausprobieren, wie sich ein rein Kerberos-basiertes Umfeld anfühlt.

Der Vorteil: wenn es nicht so läuft wie geplant, verlässt man die Gruppe einfach wieder.

ProtectedUsers01
ProtectedUsers02
ESAE

Vorbereitung

Neben dem Audit durch die Event Logs (4624 und NTLM Auditing) und vorsichtigen Tests mit der Gruppe „Protected Users“ würde ich alles auf Kerberos SSO umstellen, soweit als möglich.

Dort wäre zu benennen:

  1. RemoteGuard bei mstsc.exe erzwingen und
  2. IPSPN / TypIPSPN bei Computer- und Dienstkonten
Remote Credential Guard (RCG) – gemopst auch meinen alten Blog-Artikel

Der Remote Credential Guard hat erst mal gar nicht so viel mit dem Credential Guard zu tun, zumindest verlangt dieser keinen LsaIso.exe-Prozess. Der RCG ist seit Windows 10 1607 und Server 2016 verfügbar und verwendet eine Call-back-Funktion im Falle einer RDP-Sitzung um dem User zu authentifizieren. Call-back bedeutet die Anmeldedaten des Uses bleiben auf dem Client, gelangen nicht in den LSA-Cache des RDP-Servers.

Dadurch ergeben sich folgende Vorteile:

  • Credentials aren’t sent to the remote host
  • During the remote session, you can connect to other systems using SSO
  • An attacker can act on behalf of the user only when the session is ongoing
    mstsc-RemoteGuard-01
    RemoteCredentialGuard
    IPSPN: SPNs mit IP-Adressen – ebenso gemopst aus meinem alten Blog-ARtikel

    Das Neue nun: SPNs mit IP-Adressen.

    Das hatte ich vorher schon bei verschiedenen Kunden gesehen und mich darüber gewundert, denn SPNs gingen bisher nur mit Namen.

    Die Betonung liegt auf „gingen“: denn jetzt gehen auch SPNs mit IP-Adressen!

    TryIPSPN
    TryIPSPN-02
    TryIPSPN-03

    IPSPN / TryIPSPN

    IPSPN beziehungsweise die Clientseite, TypIPSPN, muss manuell an den Computer- und Dienstkonten gesetzt werden. Bei Dienstkonten, insbesondere bei solchen mit keytabs, würde ich die Konfiguration tendentiell eher manuell vornehmen. Dienstkonten mit SPNs gibt es vermutlich weniger als Computerkonten mit SPNs.

    Bei den Computerkonten haben wir dafür eine größere Menge als Dienstkonten mit SPNs im Active Directory und Computerkonten weisen eine höhere Dynamik auf, bedingt durch DHCP, Multi-homing und mehrerer SPNs pro Objekt, zumindest im Vergleich zu Dienstkonten.

    Die Computerkonten mit SPNs würde ich daher lieber programmatisch pflegen, Benutzer- / Dienstkonten eher manuell.

    Was ist zu beachten?

    Bei den bisherigen SPNs wird die jeweilige, eindeutige Klasse um IPSPN erweitert, hier fett abgebildet.

    Beispiel: setspn -l ch-vwac01
    Registered ServicePrincipalNames for CN=CH-VWAC01,CN=Computers,DC=CLOUD-HYBRID,DC=DE:
    TERMSRV/CH-VWAC01
    TERMSRV/CH-VWAC01.CLOUD-HYBRID.DE
    TERMSRV/192.168.0.6
    RestrictedKrbHost/CH-VWAC01
    RestrictedKrbHost/192.168.0.6
    HOST/CH-VWAC01
    HOST/192.168.0.6
    RestrictedKrbHost/CH-VWAC01.CLOUD-HYBRID.DE
    HOST/CH-VWAC01.CLOUD-HYBRID.DE

    Die Logik sagt somit:

    1. Computername zur/zu IP-Adresse(n) auflösen
    2. IP-Adressen zu den SPNs hinzufügen
    3. verwaiste IPSPNs entfernen, wenn wir doch gerade eh schon das Attribut pflegen.

    Das ganze macht das Skript rechts.

    Das Skript muss ge-dot-sourced werden und weist mehrere Parameter auf:

    1. -Computername
      sollte selbsterklärend sein
    2. -AllIPv4
      bei Multi-homed-Systemen wird IPSPN für alle IPv4-Adressen angewendet, sonst nur die erste per DNS ermittelte
    3. -RemoveStale
      Steht bei dem Computerobjekt ein IPSPN im Attribut, welcher im DNS nicht mehr auffindbar ist, wird dieser IPSPN entfernt.
    IPSPN01
    Import-Module ActiveDirectory
    
    function Debug-LdapFilterValue {
      param([Parameter(Mandatory)][string]$Value)
      # LDAP filter escaping: \  *  (  )  NUL
      $Value = $Value -replace '\\','\5c'
      $Value = $Value -replace '\*','\2a'
      $Value = $Value -replace '\(','\28'
      $Value = $Value -replace '\)','\29'
      $Value = $Value -replace "`0",'\00'
      return $Value
    }
    
    function Sync-IpSpnsForComputer {
      [CmdletBinding(SupportsShouldProcess)]
      param(
        [Parameter(Mandatory)]
        [string]$ComputerName,
    
        [switch]$AllIPv4,
        [switch]$RemoveStale
      )
    
      $ipv4Regex = '^\d{1,3}(\.\d{1,3}){3}$'
    
      $adComp = Get-ADComputer -Identity $ComputerName -Properties servicePrincipalName, dNSHostName
      $currentSpns = @($adComp.servicePrincipalName)
      if (-not $currentSpns) { throw "Computer '$ComputerName' hat keine SPNs." }
    
      $dnsName = if ($adComp.dNSHostName) { $adComp.dNSHostName } else { $ComputerName }
      $dnsIPv4s = @(Resolve-DnsName -Name $dnsName -Type A -ErrorAction Stop |
          Where-Object { $_.IPAddress -match $ipv4Regex } |
          Select-Object -ExpandProperty IPAddress -Unique)
    
      if (-not $dnsIPv4s) { throw "Keine IPv4 für '$dnsName' gefunden." }
      if (-not $AllIPv4) { $dnsIPv4s = @($dnsIPv4s[0]) }
    
      # Serviceklassen aus hostbasierten SPNs ableiten
      $serviceClasses = @(
        foreach ($spn in $currentSpns) {
          if ($spn -match '^(?[^/]+)/(?.+)$') {
            $rhs = $matches['rhs']
            $hostPart = ($rhs -split ':', 2)[0]
            if ($hostPart -notmatch $ipv4Regex) { $matches['svc'] }
          }
        }
      ) | Select-Object -Unique
    
      if (-not $serviceClasses) { throw "Keine hostbasierten SPNs gefunden." }
    
      $wantedIpSpns = foreach ($ip in $dnsIPv4s) {
        foreach ($svc in $serviceClasses) { "$svc/$ip" }
      }
    
      $toAdd = $wantedIpSpns | Where-Object { $currentSpns -notcontains $_ } | Select-Object -Unique
    
      $toRemove = @()
      if ($RemoveStale) {
        $svcRegex = ($serviceClasses | ForEach-Object { [regex]::Escape($_) }) -join '|'
        $ipSpnRegex = "^(?:$svcRegex)\/(?\d{1,3}(?:\.\d{1,3}){3})$"
    
        $toRemove = $currentSpns | Where-Object {
          ($_ -match $ipSpnRegex) -and ($matches['ip'] -notin $dnsIPv4s)
        } | Select-Object -Unique
      }
    
      # Duplikat-Check in AD (alle Objekttypen)
      foreach ($spn in $toAdd) {
        $escaped = Debug-LdapFilterValue $spn
        $dup = Get-ADObject -LDAPFilter "(servicePrincipalName=$escaped)" -Properties servicePrincipalName -ErrorAction Stop
    
        if ($dup -and ($dup.DistinguishedName -ne $adComp.DistinguishedName)) {
          throw "SPN '$spn' existiert bereits auf '$($dup.DistinguishedName)'. Abbruch (Duplikat/SPN-Konflikt)."
        }
      }
    
        $toAdd = @($toAdd | Where-Object { $_ } | ForEach-Object { [string]$_ } | Select-Object -Unique)
    
        if ($toAdd.Count -gt 0) {
              foreach ($spn in $toAdd) {
                & setspn.exe -S $spn $adComp.Name
                if ($LASTEXITCODE -ne 0) { Write-Warning "Konnte SPN nicht hinzufügen: $spn" }
              }
            }
    
            if ($toRemove.Count -gt 0) {
              foreach ($spn in $toRemove) {
                & setspn.exe -D $spn $adComp.Name
                if ($LASTEXITCODE -ne 0) { Write-Warning "Konnte SPN nicht entfernen: $spn" }
              }
            }
    
      [pscustomobject]@{
        ComputerName          = $ComputerName
        DnsName               = $dnsName
        DnsIPv4               = ($dnsIPv4s -join ', ')
        DerivedServiceClasses = ($serviceClasses -join ', ')
        Added                 = ($toAdd -join ', ')
        Removed               = ($toRemove -join ', ')
      }
    }

    NTLM per GPO abschalten

    Per Gruppenrichtlinie muss jetzt, vorsichtig bei den Clients beginnend, das NTLM abgeschaltet werden.

    Zu finden ist das unter „Computer Configuration“ ⇒ Policies ⇒ „Windows Settings“ ⇒ „Security Settings“ ⇒ „Local Policies“ ⇒ „Security Options“ ⇒ „Network security: Restrict NTLM: Outgoing NTLM traffic to remote servers ⇒ Deny all“.

    Ebenso auf ausgewählten Domain Controllern das NTLM abschalten:

    Zu finden ist das unter „Computer Configuration“ ⇒ Policies ⇒ „Windows Settings“ ⇒ „Security Settings“ ⇒ „Local Policies“ ⇒ „Security Options“ ⇒ „Network security: Restrict NTLM: NTLM authentication in this domain ⇒ Deny all account“.

    Client_Server_noNTLM01
    DC_noNTLM01
    NTLMLess_Sites

    Die Zauberformel

    1. Mit wenigen Clients anfangen. NTLM abschalten und auf Rückmeldungen der Anwender achten.
    2. Den Kreis der NTLM -less-Clients erweitern bis alle Clients ohne NTLM auskommen.
    3. Mit wenigen Server weiter machen. NTLM abschalten, auf Rückmeldungen der Systembetreuer und Anwender achten.
    4. Den Kreis der NTLM -less-Server erweitern bis alle Server ohne NTLM bei auskommen.
    5. Mit wenigen Domain Controllern weiter machen, am besten, sofern vorhanden, in einer AD-Site. NTLM abschalten und die Logs auf den DCs hierzu beobachen.
    6. Den Kreis der NTLM -less-Domain Controller erweitern bis alle Domain Controller ohne NTLM auskommen.

    Das ist mir zu heiß!

    Das ist aktuell (25.02.2026) noch kein super heißes Thema, sollte aber im Auge behalten werden.
    Ich halte es für empfehlenswert auf jeden Fall schon mal zu auditieren, zu validieren und mit IPSPN / TryIPSPN erste Erfahrungen zu sammeln.
    Meine Vermutung ist, es wird sich wie mit Weihnachten verhalten: zack, auf einmal ist der Termin da und man zieht ein langes Gesicht.

    Wenn das nun zu unbequem und / oder zu risikoreich anmutet: wir bieten in diesem Bereich seit 2019 Unterstützung, Beratung und Schulungen an.

    Eine kurze E-Mail an info@iqunit.com oder ein Anruf unter +49 731 1848699-0 sind keine große Hürde.

    Help_IQunit_NTLM

    Leave a comment

    fünf + 11 =