> For the complete documentation index, see [llms.txt](https://jeanpeyremesmots.gitbook.io/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jeanpeyremesmots.gitbook.io/home/jpmm-en/system-administration/active-directory/decouverte-et-mise-en-place/3-hardening-du-serveur.md).

# 3 - Hardening the server

Since I'm into cybersecurity, I wanted to assess how secure my freshly deployed AD was, using PingCastle. A first scan of the domain gives a poor score:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-74d6130d4eae30b44c26ca67d885884a28001694%2Fimage%20(117).png?alt=media" alt=""><figcaption></figcaption></figure>

That's typical of a default installation. The report raises many points:

| Risk model                       | Score | Reason shown in the report                                                                                                                                        |
| -------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Anomalies**                    | 70    | The spooler is reachable remotely from 1 DC, LAPS is not installed, and some other ASR mitigations are not enabled.                                               |
| **Privileged accounts**          | 40    | Admin accounts are not protected by the "sensitive and cannot be delegated" option.                                                                               |
| **Stale Objects**                | 31    | Only one DC, last AD backup 20 days ago, and other rules on "stale" objects.                                                                                      |
| **Pass-the-credential**          | 25    | The spooler is reachable remotely from 1 DC, which increases the risk of credential theft/reuse.                                                                  |
| **Account take over**            | 20    | Admin accounts are not marked as sensitive to delegation.                                                                                                         |
| **Backup**                       | 20    | Not enough DCs for redundancy, and the last AD backup is too old.                                                                                                 |
| **Irreversible change**          | 20    | OUs without protection against accidental deletion, Schema Admins not empty, Recycle Bin not enabled.                                                             |
| **Old authentication protocols** | 15    | The LAN Manager setting allows NTLMv1 or LM.                                                                                                                      |
| **Audit**                        | 10    | PowerShell auditing is not fully enabled, and DC auditing does not collect some key events.                                                                       |
| **Local group vulnerability**    | 10    | Non-admin users can add up to 10 computers to the domain.                                                                                                         |
| **Provisioning**                 | 10    | The password of some accounts never expires, and the password security level is insufficient.                                                                     |
| **Weak password**                | 10    | No password policy for service accounts, and at least one policy with a length under 8 characters.                                                                |
| **Network topography**           | 5     | Risk of Internet scripts running from DCs, and a DC subnet is missing from the declaration.                                                                       |
| **Network sniffing**             | 5     | Hardened Paths have been lowered, and LLMNR is not disabled everywhere.                                                                                           |
| **Object configuration**         | 1     | Defender ASR is not fully in Block/Warn mode, some file options are risky, Kerberos Armoring needs checking, and the Terminal Services GPO is not compliant.      |
| **Reconnaissance**               | 0     | DsHeuristics does not mitigate CVE-2021-42291, NetCease is not found, PreWin2000 contains "Authenticated Users", and Anonymous Binding to the rootDSE is enabled. |

So let's fix all of that 😎

### Disabling NTLM:

Disabling NTLM is a well-known recommendation: it is obsolete and easily abused in NTLM Relay attacks. We can disable NTLM authentication with the following GPO:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-ac9c94385a02aeabc29c4d8072d8cd7b9ed62918%2Fimage%20(118).png?alt=media" alt=""><figcaption></figcaption></figure>

We also set the LAN Manager authentication level so that only NTLMv2 is accepted:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-224bf6a459297b9e3a721dc26d7d0a0f8eb415a9%2Fimage%20(119).png?alt=media" alt=""><figcaption></figcaption></figure>

We check that the GPO is applied:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-974fede72604e0694718d76db27589b0e8e6d740%2Fimage%20(120).png?alt=media" alt=""><figcaption></figcaption></figure>

The Stale Objects score then goes from 31/100 to:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-4e92dbfe8fa50849f91fcb7ca8bb8fd368caeaae%2Fimage%20(121).png?alt=media" alt=""><figcaption></figcaption></figure>

Better already! Let's keep going.

### Enabling the anti-delegation flag for the admin account:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-b07e66242b5bb34d136b4e470b1da313cbcc175c%2Fimage%20(122).png?alt=media" alt=""><figcaption></figcaption></figure>

This prevents a compromised service from stealing the Admin's Kerberos TGT through delegation. The related score drops as follows:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-8c1d5865ae2607991abf6314470fad74a5e27b52%2Fimage%20(123).png?alt=media" alt=""><figcaption></figcaption></figure>

### Protection against accidental deletion of OUs:

The following OUs do not have the anti-delete flag enabled:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-e79765d1d709e0f45245c9cf310308a30b68ac37%2Fimage%20(124).png?alt=media" alt=""><figcaption></figcaption></figure>

So we enable it for all OUs:

```powershell
Get-ADOrganizationalUnit -Filter * | Set-ADOrganizationalUnit -ProtectedFromAccidentalDeletion $true
```

### Schema Admins not empty (1 account)

The **Schema Admins** group contains 1 account instead of being empty: the "**Administrateur**" account.

```powershell
Remove-ADGroupMember "Administrateurs du schéma" -Members "Administrateur"
```

This group can modify the AD schema, and schema changes are irreversible. It should be empty in production; in this lab, I chose not to run the command above, to avoid any risk of locking myself out of the Admin account.

### Recycle Bin not enabled:

```powershell
# Active la corbeille AD
Enable-ADOptionalFeature -Identity 'Recycle Bin Feature' -Scope ForestOrConfigurationSet -Target 'mesmots.local'
```

This makes it possible to **recover any deleted object** for 180 days instead of losing it permanently.

Privileged Accounts in the report now drops to 0:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-8dedf86d9ee23a3c4986eb8ec026e6367a4427cb%2Fimage%20(125).png?alt=media" alt=""><figcaption></figcaption></figure>

### No backup:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-7bff8b66ecfc617311ef12ecd77cf8ce4c1d2952%2Fimage%20(126).png?alt=media" alt=""><figcaption></figcaption></figure>

Critical in production, but out of scope here since this is a local lab.

### Spooler reachable remotely

The **Print Spooler** service on the DC is reachable via RPC (MS-RPRN), which exposes it to **PrintNightmare** and other RCE attacks on DCs. A DC has no reason to print, so we disable the service with PowerShell:

```powershell
# Sur ton DC, désactive complètement le spooler distant
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Spooler" -Name "DependOnService" -Value @("RPCSS")
Stop-Service Spooler -Force
Set-Service Spooler -StartupType Disabled
```

### Configuring LAPS

LAPS manages the local admin passwords of domain-joined machines: Windows LAPS **generates a strong, unique password for the local administrator account of each machine it manages**, while **automatically rotating these passwords**. The passwords are then encrypted and stored in Active Directory or Azure Active Directory, depending on the configuration.

On the DC, the following PowerShell command allows computers in the OU to write their own LAPS password attributes:

```powershell
Set-LapsADComputerSelfPermission -Identity "OU=Ordinateurs,OU=MesMots,DC=mesmots,DC=local"
```

Then with a GPO > Computer Configuration > Policies > Administrative Templates > System > LAPS:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-c2a0746457e8e34bb28a3711ca8c26b8f5cdc879%2Fimage%20(127).png?alt=media" alt=""><figcaption></figcaption></figure>

We keep at least one password in history in case of a lockout:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-43cfa3dbff91acc102fd745b0869c88d766a3257%2Fimage%20(128).png?alt=media" alt=""><figcaption></figcaption></figure>

We end up with this:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-8691fe4ca43ca87f94de5e5792f8f926553fff90%2Fimage%20(129).png?alt=media" alt=""><figcaption></figcaption></figure>

We then move on to the configuration on the client PC, running in order:

```powershell
gpupdate /force

# Reboot pour appliquer la conf
Restart-Computer
```

After logging back in on the client PC, we check its event log:

**Applications and Services Logs > Microsoft > Windows > LAPS > Operational**

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-58c90194ae6e80bcafbda2a835ecbd9fb8febcd3%2Fimage%20(130).png?alt=media" alt=""><figcaption></figcaption></figure>

LAPS was applied successfully. We then retrieve the password assigned to that PC with the following PowerShell command:

```powershell
PS C:\Users\Administrateur> Get-LapsADPassword "WIN11-HOME-LAB" -AsPlainText

ComputerName        : WIN11-HOME-LAB
DistinguishedName   : CN=WIN11-HOME-LAB,OU=Ordinateurs,OU=MesMots,DC=mesmots,DC=local
Account             : Administrateur
Password            : XXXXXXXXXXXXXXX
PasswordUpdateTime  : 09/05/2026 23:17:40
ExpirationTimestamp : 08/06/2026 23:17:40
Source              : EncryptedPassword
DecryptionStatus    : Success
AuthorizedDecryptor : MESMOTS0\Admins du domaine
```

We can now run a program as admin on the client PC, entering in the UAC prompt:

* "**.\Administrateur**" as the username (I forgot the **.\\** that indicates a local account at first, which got me stuck for almost an hour 😄)
* And the password generated by LAPS:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-124ffc8555fbf3ea11510766655f8a332a0fe1f6%2Fimage%20(131).png?alt=media" alt=""><figcaption></figcaption></figure>

We get an elevated shell in "**System32**", so the LAPS-managed admin account works!

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-2dfd1cbfcc29a901798462f1df632165a1ef0b65%2Fimage%20(132).png?alt=media" alt=""><figcaption></figcaption></figure>

And the PingCastle score drops again:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-a0245a87f358b46e95d398e2ad89ce06ca58cd0a%2Fimage%20(133).png?alt=media" alt=""><figcaption></figcaption></figure>

### Blocking Internet access for malicious script engines

The following script engines (some of them LOLBins):

**wscript.exe, cscript.exe, mshta.exe, conhost.exe, runScriptHelper.exe**

can connect directly to the Internet to exfiltrate data or download payloads. By default, the Windows firewall does not block them outbound.

**Solution via GPO**:

* Edit a GPO linked to the DCs and workstations (`Computer > Configuration > Windows Settings > Windows Firewall with Advanced Security`), which I named "**Sécurité - Bloquer moteur de scripts /= internet**":

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-773efdf1b67d9ec4e4f2dc804518244987ca6989%2Fimage%20(134).png?alt=media" alt=""><figcaption></figcaption></figure>

```
Programme : %SystemRoot%\System32\wscript.exe (et les autres)
```

Action:

* Block the connection

We end up with the following programs blocked:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-9adbc8b47b1bc48ab2c8f56bd3f321b3d207818c%2Fimage%20(135).png?alt=media" alt=""><figcaption></figcaption></figure>

### DC subnet

Even though the DC has a static IP, I declared its subnet in Sites and Services so that clients are mapped to the right site:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-6004e1fee156169a51286a8010d6afc4d45041ff%2Fimage%20(136).png?alt=media" alt=""><figcaption></figcaption></figure>

The displayed site is then correct:

```powershell
PS C:\Users\Administrateur> nltest /dsgetsite
Default-First-Site-Name
La commande a été correctement exécutée
```

With these two rules, Stale Objects went from 31 to **11/100** (S-OldNtlm fixed via the "**Sécurité - Désactiver NTLM**" GPO):

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-74e74cb74f1f7b063c062db75287b40d248d70fb%2Fimage%20(137).png?alt=media" alt=""><figcaption></figcaption></figure>

PingCastle still flags this:

<figure><img src="https://1542616339-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fq0kLRqEluZvLRXK1LDcp%2Fuploads%2Fgit-blob-e2e01cd847d6a5ab1d02604022dee9f14d82c2db%2Fimage%20(138).png?alt=media" alt=""><figcaption></figcaption></figure>

But that is expected, because **Runscripthelper.exe** only existed on **Windows 10 build 16299**. The AD runs on **Windows Server 2019**, where this binary simply no longer exists: Microsoft removed it in later versions.

There is still work to do to reach a score of 0 (rarely achieved in production anyway). The main thing keeping the score high is the missing backup, which is expected for an AD running locally on my PC.

All the other points are **"informative rules" (0 points)**, typical of a solo lab.
