ENGINEERING

The five registry values behind CIS Windows L1 credential-theft hardening

Half of “credential theft hardening” on Windows comes down to a handful of registry values. Not a product, not an EDR, not a magic agent - five or six DWORDs that decide whether a tool running as SYSTEM can walk out of your fleet with everyone’s hashes.

I say this as someone who came up through hospital desktop engineering, where the domain admin’s token would never be caught dead on anything but a DC - and oftentimes that’s still not enough. The creds that get a fleet owned aren’t the ones you lock away; they’re the ones you forget are everywhere: the helpdesk account that’s local admin on every workstation, the domain service account whose password is recoverable off every box it runs on, and the tier-2 desktop-support login sitting in LSASS on the same machine a nurse badges into all shift. The scary part of credential theft isn’t exotic. It’s that the default config on a machine nobody hardened will hand LSASS memory to anything with local admin, and the fix is a value you can set in an afternoon and then spend three years making sure nothing flips back.

This post is the value-by-value version: the exact key, the attack it closes, and the gotcha that bites you if you set it blind. Then, honestly, where RFF fits - because “make sure nothing flips it back” is the actual hard part, and it’s the part Conformance exists to do.

1. LSA Protection (RunAsPPL) - the one that stops Mimikatz cold

HKLM\SYSTEM\CurrentControlSet\Control\Lsa
    RunAsPPL = 1        (DWORD)

What it does. With RunAsPPL = 1, LSASS starts as a Protected Process Light. A PPL can only be opened by another equal-or-higher-protection process, which means a tool running as ordinary local admin - even SYSTEM - can no longer call OpenProcess on LSASS and read its memory.

The attack it closes. This is the direct counter to LSASS memory dumping. Mimikatz’s sekurlsa::logonpasswords, a procdump -ma lsass.exe, Task Manager’s “Create dump file”, comsvcs.dll MiniDump - they all need read access to LSASS memory. RunAsPPL revokes that access for anything not itself protected. It’s the single highest-leverage credential-theft control on the list.

The gotchas, and there are real ones.

2. WDigest - stop caching cleartext passwords in memory

HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest
    UseLogonCredential = 0        (DWORD)

What it does. WDigest is a legacy authentication provider that, when enabled, kept the user’s cleartext password sitting in LSASS so it could answer digest challenges. UseLogonCredential = 0 tells it not to. No cleartext in memory.

The attack it closes. The nightmare version of an LSASS dump. Even without cracking a hash, an attacker who dumped LSASS on a box with WDigest caching enabled got plaintext passwords handed to them directly. This value removes that gift.

The gotcha. On modern Windows (post the 2014-era KB2871997 update, so anything you’re running today) the effective default is already 0 - WDigest doesn’t cache cleartext out of the box anymore. So why set it? Because pinning it is the point. A default is not a guarantee; it’s whatever the last thing to touch that key left behind. Some old hardening script, a re-enabled legacy app, a “temporary” troubleshooting change nobody reverted - any of them can flip it back to 1, and now you’re quietly caching plaintext again with no alert. Asserting = 0 explicitly turns “probably fine” into “known and watched.”

3. SMB signing - kill the relay and the man-in-the-middle

HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
    RequireSecuritySignature = 1        (DWORD)

HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters
    RequireSecuritySignature = 1        (DWORD)

What it does. These require SMB packet signing on the server side (LanmanServer) and the client side (LanmanWorkstation). Signing cryptographically ties each SMB message to the session, so a packet can’t be silently altered or replayed by something sitting in the middle.

The attack it closes. SMB relay and SMB man-in-the-middle. The classic move - capture an authentication with Responder, relay it to another host with ntlmrelayx, and act as the victim - depends on the relayed SMB session being unsigned. Require signing on both ends and the relayed session fails to sign, so it fails. This is one of the highest-value controls against lateral movement inside a flat network.

The gotcha. There’s a minor throughput cost to signing every SMB message - historically noticeable on high-volume file-server workloads, largely negligible on modern CPUs with the relevant crypto acceleration, but non-zero. The bigger practical caveat is legacy interop: something very old that can’t sign will fail to talk SMB once you require it. On a current Windows fleet this is rarely the blocker people fear, but you validate against your file servers and any appliance-style SMB endpoints before you flip require (not just enable) fleet-wide.

4. NTLM and the LM hash - stop storing and sending the weak stuff

HKLM\SYSTEM\CurrentControlSet\Control\Lsa
    NoLMHash             = 1        (DWORD)
    LmCompatibilityLevel = 5        (DWORD)

What they do. Two separate jobs.

The attack it closes. Two flavors of the same problem. NoLMHash denies an attacker who dumps the SAM the easy-to-crack LM hash. LmCompatibilityLevel = 5 denies an on-path attacker the ability to negotiate a victim down to LM/NTLMv1 and crack the weaker response - the downgrade attack.

The gotcha. LmCompatibilityLevel = 5 is the one that can break ancient clients. Anything that genuinely can only speak LM or NTLMv1 - a decades-old appliance, some embedded device, a legacy line-of-business box that predates sensible auth - will simply fail to authenticate once you refuse those protocols. On a modern fleet this is almost always the right break (that device was a liability anyway), but it’s a break, so you want to know it’s coming rather than discover it in an outage. This is exactly the kind of control you roll behind report-only first, see who lights up red, and then enforce.

5. Anonymous enumeration - close the null-session recon window

HKLM\SYSTEM\CurrentControlSet\Control\Lsa
    RestrictAnonymous    = 1        (DWORD)
    RestrictAnonymousSAM = 1        (DWORD)

What they do. These restrict what an anonymous (null-session) connection can enumerate. RestrictAnonymousSAM = 1 blocks anonymous enumeration of SAM accounts; RestrictAnonymous = 1 further restricts anonymous access to account and share information.

The attack it closes. Pre-attack reconnaissance. Null-session enumeration is how an unauthenticated attacker on the network builds the target list - user names, group memberships, the account-naming convention, who’s in Domain Admins - before spending a single credential. It’s not credential theft on its own; it’s the map an attacker draws first. Restricting it makes them work blind.

The gotcha. Lower blast radius than the others, but non-zero. Some legacy applications, older trust scenarios, and a few monitoring or inventory tools historically leaned on null-session enumeration. On a modern domain almost nothing still depends on it, but if you have something genuinely old that expects to enumerate anonymously, it’ll notice. Same playbook: report first, confirm nothing legitimate depends on it, enforce.

The spine, in one place

If you set nothing else on the credential-theft axis, this is the spine:

HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL                        = 1
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest\UseLogonCredential = 0
HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\RequireSecuritySignature      = 1
HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters\RequireSecuritySignature = 1
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\NoLMHash                        = 1
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\LmCompatibilityLevel            = 5
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RestrictAnonymous               = 1
HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RestrictAnonymousSAM            = 1

Eight values. A subset of what CIS Windows L1 covers, but the subset that specifically attacks credential theft. You could set every one of these by lunch.

Setting them once is easy. Keeping them set is the whole job.

Here’s the honest part, and it’s the reason this post exists.

None of the above is hard to apply. reg add, a GPO, an Intune profile - pick your delivery. The hard part is the next three years. On a real fleet, values like these get quietly reverted constantly: a new GPO from a different team lands with a conflicting setting, an imaging template drifts from the standard, a vendor’s “recommended” install script sets UseLogonCredential = 1 because it still speaks WDigest, someone RDPs into one box for a one-off fix and never puts it back. Every one of those flips a value you thought was locked, and none of them page you. The setting doesn’t announce that it changed. You find out during an incident, or an audit, or never.

That silent-reversion problem is precisely what RFF’s Conformance baselines are built for, and it’s worth being exact about what that means for these specific values.

You don’t hand-type the eight keys. Conformance ships a CIS Windows L1 (starter) template and a lower-risk Credential Theft Essentials one, and they’re self-documenting - each control explains why it matters, so when an auditor asks why LmCompatibilityLevel is 5 you have the justification inline, not in someone’s memory. To make these actually evaluate green-or-red instead of grey “unknown,” the agent collects the credential-theft registry spine - RunAsPPL, WDigest, SMB signing, NTLM - so RunAsPPL either is 1 on a box or it isn’t, and you can see which.

The baseline is an editable, versioned document, not a frozen capture. This is the part I care about most and the reason we built native versioning instead of reaching for Git. Seed from the CIS template, then edit expected values by hand as your standard evolves - full version history, diff any two versions, one-click revert. When you decide LmCompatibilityLevel should move, you change the declared value, a new version lands, and the fleet re-diffs against it.

Drift shows up in plain English - down to which GPO set the value. When a box has UseLogonCredential = 1 and your baseline says 0, you don’t get a hex blob. You get the readable setting, the expected-versus-found values, and - read from RSoP - which GPO imposed the wrong value. That last part is the difference between “something is off” and “this policy, owned by that team, is the thing flipping it back.” You fix it at the source instead of playing registry whack-a-mole every refresh cycle.

Enforcement is report-only by default, and approval-gated when you turn it on. Out of the box Conformance reports and shuts up - which, for a control like LmCompatibilityLevel = 5 that can break an ancient client, is exactly what you want first. When you’re ready to fix drift, RFF auto-generates the remediation job and stages the exact script for a human to read and approve. Nothing writes to a box until you click approve, and the inverse script is captured up front, so rollback is one click and never “hope you wrote down what it was.” If an active GPO owns a value, RFF won’t fight it - it reports the drift and points you at the GPO’s scope, because otherwise you’d have a box flapping every two hours forever.

To be precise about the boundary, because this brand doesn’t overclaim: RFF detects and enforces your desired state. It is not a CIS auditor certification. It won’t hand you a stamped compliance attestation. What it does is capture these exact per-value settings as a readable standard, tell you the instant one drifts and why, and fix it with your approval and a rollback. The read-only effective-policy view is free - see the resolved policy on a box and which GPO set what, at no cost. The capture engine, the CIS templates, versioned baselines, cross-fleet drift detection, and approval-gated enforce-plus-rollback are the Pro part, because that’s the machinery that does the three-years-of-work-after-lunch for you.

If you want the other end of this posture - the local-model and shadow-AI blind spot that credential hardening doesn’t touch - that’s here. Different axis, same philosophy: see it honestly, act on it with a hand on the brake.

The lesson

Credential-theft hardening on Windows is not mysterious. It’s eight registry values, each closing a named attack, each with a gotcha you can plan around. Any competent Windows admin can set them.

The part that actually fails, over and over, is keeping them set on a fleet where a hundred things quietly conspire to un-set them. Applying a value is a Tuesday. Knowing - in plain English, forever - that the value is still what you declared, and getting told the moment it isn’t and which GPO did it, is the job. That’s the job we built Conformance to do.

Set the eight values. Then make them stay.


Conformance is part of RFF - free for your first 100 endpoints. Seed a CIS Windows L1 baseline, watch which boxes drift, fix it with a hand on the brake - getrff.com.