Entre 00:00 et 00:20, vos sessions PowerShell Remoting passent, puis tout casse avec « username and password incorrect ». Ce guide pragmatique détaille le diagnostic Kerberos/NTLM, WinRM et capacité, et fournit des scripts pour stabiliser vos connexions nocturnes à grande échelle.
Contexte et symptômes observables
Une application .NET lance des connexions PowerShell Remoting en chaîne vers plus de 100 machines. Les tentatives démarrées juste après minuit réussissent pendant une vingtaine de minutes, puis toutes les nouvelles connexions échouent avec le message d’erreur « username and password incorrect ». Le même scénario, exécuté en journée, ne présente aucune anomalie.
Ce modèle « fonctionne, puis échoue brutalement » trahit généralement un évènement périodique sur l’infrastructure (rotation de mot de passe, maintenance réseau, redémarrage d’un service critique, quotas qui arrivent à saturation) plutôt qu’un problème purement applicatif.
Ce que signifie vraiment « username and password incorrect »
Ce message, renvoyé côté client par WinRM/WS‑Man, n’implique pas forcément que le mot de passe est erroné : il peut également résulter d’un verrouillage de compte, d’un échec Kerberos dû à une dérive d’horloge, d’une restriction d’horaires de connexion Active Directory, d’un échec NTLM ou d’un dépassement de capacité qui se traduit par une erreur d’authentification générique.
Hypothèses principales à privilégier
- Rotation automatique du mot de passe du compte de service ou d’un compte local cible (LAPS/PAM) autour de 00:20, non prise en compte par l’application.
- Fenêtre de maintenance sur l’infrastructure (KDC/contrôleurs de domaine, DNS, pare‑feu, IDS/IPS, proxy TLS) démarrant entre 00:15 et 00:30.
- Quotas WinRM ou ressources LSA atteints après un nombre élevé de sessions séquentielles, entraînant un rejet ultérieur des authentifications.
- Dérive temporelle entre la source, les hôtes cibles et les DC ; Kerberos devient invalide dès dépassement de la tolérance de temps.
Plan d’action express pour reproduire, tracer et fixer
- Reproduire sur un sous‑ensemble (10–15 machines) en lançant la campagne à 00:05 et en la prolongeant au‑delà de 00:30 pour isoler l’effet « franchissement de fenêtre ».
- Tracer simultanément :
- Journaux WinRM sur la source et les cibles (Microsoft‑Windows‑WinRM/Operational).
- Événements Sécurité AD : 4768 (TGT), 4771 (Kerberos pre‑auth fail), 4625 (logon échec), 4740 (verrouillage compte) côté DC.
- Événements Système (erreurs DNS/KDC/W32Time) et performances (CPU/mémoire/handles) sur la source.
- Vérifier l’infrastructure : redémarrages planifiés de services KDC/DNS/pare‑feu, rotations de certificats WinRM, sauvegardes et bascules réseau à minuit.
- Ajuster les quotas WinRM si l’on observe « quota exceeded », « shell count limit exceeded » ou des délais anormaux côté WS‑Man.
- Mettre en place un runspace pool (C#) ou un throttling (PowerShell) avec gigue aléatoire pour lisser la charge.
- Confirmer par une campagne de test dépassant 30 minutes que l’erreur disparaît durablement.
Tableau de diagnostic guidé
Axe de diagnostic | Points à contrôler | Correctifs possibles |
---|---|---|
Authentification (Kerberos/NTLM) | Durée de vie et expiration des tickets Kerberos ; codes d’échec 4771 ; verrouillage 4740 ; restrictions d’horaires AD ; dérive NTP | Synchroniser NTP sur source/DC/cibles ; relever 4768/4771/4625 ; lever une restriction d’horaires si présente ; envisager gMSA ou rotation coordonnée |
WinRM/PowerShell quotas | MaxShellsPerUser , MaxProcessesPerShell , IdleTimeout , file WS‑Man | Ajuster via winrm set ou stratégie de groupe ; fermer les shells inactifs ; pooling |
Réseau et sécurité | Backups, patching, bascules de pare‑feu/IDS à minuit ; ports 5985/5986 ; TLS offload | Coordination avec réseau/sécurité ; sondes TCP pendant la fenêtre ; logs du reverse proxy/SSL |
Ressources locales | Fuites de handles/threads, pics CPU/mémoire, sockets éphémères | PerfMon, redémarrage contrôlé du processus .NET, pool de connexions persistantes |
Parallélisation | 100 connexions en rafale (même séquentielles) épuisent les ressources sur les cibles | Batchs de 20 avec délai aléatoire entre lots ; runspace pool |
Vérifications Kerberos et NTLM
Contrôle de l’heure et de la dérive
# Sur la source, mesurer la dérive par rapport aux DC
w32tm /monitor
# Forcer une resynchronisation si nécessaire
w32tm /resync /force
Une dérive supérieure à quelques minutes peut invalider Kerberos. Corrigez NTP sur tous les segments (source, DC, cibles).
Collecte des événements côté DC
# Exemple de recherche ciblée sur un créneau de 00:00 à 01:00
$start = [datetime]::Today.AddHours(0)
$end = [datetime]::Today.AddHours(1)
$xml = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[(EventID=4768 or EventID=4771 or EventID=4625 or EventID=4740)
and TimeCreated[timediff(@SystemTime) <= 3600000]]]
and *[EventData[Data[@Name='TargetUserName'] and (Data='MonCompteService')]]</Select>
</Query>
</QueryList>
"@
Get-WinEvent -FilterXml $xml |
Select TimeCreated, Id, ProviderName, @{n='Message';e={$_.FormatDescription()}} |
Sort TimeCreated
Indicateurs clés : 4771 avec failure code 0x18 (mot de passe erroné), 0x25 (dérive horaire), 4740 (compte verrouillé). Si ces événements se mettent à pleuvoir à partir de 00:20, suspectez une rotation de mot de passe ou une tâche qui rejoue des authentifications invalides.
Restrictions d’horaires de connexion AD
# Vérifier si le compte de service est restreint par logonHours
Import-Module ActiveDirectory
Get-ADUser MonCompteService -Properties logonHours |
Select-Object SamAccountName, logonHours
Si le compte est interdit de connexion après 00:20, WinRM renverra un échec d’authentification. Ajustez la stratégie ou utilisez un compte exempté de restrictions.
Inspection et réglage des quotas WinRM
Lecture des paramètres actuels
# Sur chaque cible
winrm get winrm/config
winrm get winrm/config/service
winrm get winrm/config/winrs
# En PowerShell (plus lisible)
Get-ChildItem WSMan:\localhost\Service
Get-ChildItem WSMan:\localhost\Shell
Paramètres à considérer pour 100+ cibles
Clé WinRM | Rôle | Exemple de valeur de test | Impact / Remarques |
---|---|---|---|
Service\MaxConnections | Connexions WS‑Man simultanées | 300–500 | Éviter l’embouteillage si plusieurs sources parlent à la même cible |
Shell\MaxShellsPerUser | Nombre de shells par utilisateur | 100–150 | Augmenter si de nombreux shells restent ouverts côté cible |
Shell\MaxProcessesPerShell | Processus autorisés par shell | 15–25 | Relever si vos scripts lancent des utilitaires externes |
Shell\IdleTimeout | Fermeture automatique des shells inactifs | 10–30 min | Réduit l’accumulation de sessions orphelines |
Shell\MaxMemoryPerShellMB | Limite mémoire par shell | 1024–2048 | Éviter la pression mémoire de pwsh.exe/wsmprovhost.exe |
Modification contrôlée
# Exemple : relever temporairement quelques quotas (à adapter, tester, documenter)
winrm set winrm/config/winrs @{MaxShellsPerUser="120";MaxProcessesPerShell="25";IdleTimeout="1800000"}
winrm set winrm/config/service @{MaxConnections="400"}
# Via la PSDrive WSMan (souvent plus fiable en script)
Set-Item WSMan:\localhost\Shell\MaxShellsPerUser 120
Set-Item WSMan:\localhost\Shell\IdleTimeout 1800000
Appliquez ces réglages d’abord sur un petit périmètre, surveillez la consommation mémoire/CPU, et revenez à des valeurs raisonnables après la résolution pour éviter d’autres effets de bord.
Répartition de charge côté application
Runspace pool en C# (.NET)
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public class RemoteBatch
{
public static void Main()
{
var creds = new PSCredential("DOMAINE\MonCompteService",
new System.Security.SecureString()); // renseigner de façon sécurisée
```
var connection = new WSManConnectionInfo
{
ComputerName = "placeholder", // sera changé à la volée
AuthenticationMechanism = AuthenticationMechanism.Negotiate,
OpenTimeout = 180000,
OperationTimeout = 180000
};
connection.Credential = creds;
// Pool minimisant l’explosion de sessions simultanées
using var pool = RunspaceFactory.CreateRunspacePool(1, 20);
pool.Open();
var targets = new List<string> { /* 100+ machines */ };
var jobs = new List<IAsyncResult>();
foreach (var t in targets)
{
var ci = connection.Copy(); // extension perso qui clone et remplace ComputerName
ci.ComputerName = t;
var ps = PowerShell.Create();
ps.RunspacePool = pool;
ps.AddCommand("Test-WSMan")
.AddParameter("ComputerName", t)
.AddParameter("Authentication", "Default")
.AddParameter("ErrorAction", "Stop");
// Jigue aléatoire (50–500 ms) pour éviter les rafales alignées
System.Threading.Thread.Sleep(new Random().Next(50, 500));
jobs.Add(ps.BeginInvoke());
}
foreach (var j in jobs) PowerShell.EndInvoke(j);
Console.WriteLine("Campagne terminée.");
}
```
}
Un runspace pool limite l’éventail de shells actifs côté cible et lisse la pression sur les services d’authentification.
Throttling en PowerShell
$computers = Get-Content .\machines.txt
$cred = Get-Credential
$throttle = 20
$script = {
param($c,$cred)
Start-Sleep -Milliseconds (Get-Random -Min 100 -Max 1500)
try {
Test-WSMan -ComputerName $c -Credential $cred -Authentication Default -ErrorAction Stop
[pscustomobject]@{ Computer=$c; Result='OK'; When=Get-Date }
} catch {
[pscustomobject]@{ Computer=$c; Result='FAIL'; Error=$_.Exception.Message; When=Get-Date }
}
}
$jobs = foreach($c in $computers){
Start-Job -ScriptBlock $script -ArgumentList $c,$cred
while ((Get-Job -State Running).Count -ge $throttle) { Start-Sleep -Milliseconds 200 }
}
$results = Receive-Job -Wait -AutoRemoveJob
$results | Sort-Object Result,Computer | Format-Table -AutoSize
Signaux d’observation et interprétations
Observation | Interprétation probable | Action ciblée |
---|---|---|
Échecs 4771 code 0x18 à partir de 00:20 | Mot de passe invalide ou rotation non propagée | Vérifier PAM/LAPS/gMSA ; redémarrer l’appli ou recharger les secrets |
Échecs Kerberos code 0x25 autour de minuit | Dérive d’horloge | Corriger NTP et surveiller w32time |
WinRM log : « shell count limit exceeded » | Quota MaxShellsPerUser atteint | Relever temporairement le quota et fermer les shells inactifs |
Latence réseau et resets TCP à 00:20 | Maintenance pare‑feu/IDS, bascule de routeur, sauvegarde | Coordination réseau, fenêtre dédiée aux campagnes, tests de latence |
Événement 4740 (compte verrouillé) | Rafale d’échecs sur mot de passe (boucle de retry) | Réduire les tentatives, ajouter backoff et jitter |
Cas typique : rotation de mot de passe à 00:20
De nombreuses organisations déclenchent la rotation quotidienne des comptes privilégiés (PAM, coffre de secrets, scripts maison) juste après minuit. L’application .NET lit le secret au démarrage ou le met en cache ; à 00:20, le coffre met à jour le mot de passe, mais l’application continue à utiliser l’ancien pour créer de nouvelles sessions—ce qui explique que les sessions ouvertes avant 00:20 continuent d’opérer, alors que toute nouvelle connexion échoue.
Correctifs :
- Remplacer les mots de passe statiques par une gMSA (group Managed Service Account) pour l’identité de l’application si possible.
- Si l’on conserve un secret classique, récupérer le secret à chaque création de session (ou sur signal de rotation) au lieu de le conserver indéfiniment en mémoire.
- Mettre en place un health‑check à H+25 minutes après la rotation pour valider que les connexions fonctionnent toujours et alerter en cas d’échec.
Scripts de collecte et d’assainissement
Collecte ciblée des erreurs WinRM
# Sur une cible sensible, pendant la fenêtre 00:00–01:00
$filter = @{
LogName = 'Microsoft-Windows-WinRM/Operational'
StartTime = (Get-Date).Date
EndTime = (Get-Date).Date.AddHours(1)
}
Get-WinEvent -FilterHashtable $filter |
Where-Object { $_.LevelDisplayName -in 'Error','Warning' } |
Select TimeCreated, Id, @{n='Message';e={$_.FormatDescription()}} |
Sort TimeCreated
Fermeture des shells orphelins
# Lister les sessions distantes côté cible
Get-Process wsmprovhost | Select-Object Id,StartTime,CPU,WS
# Depuis la source, recyclage propre après batch
Get-PSSession | Remove-PSSession
Test de ports et de gigue réseau
$targets = Get-Content .\machines.txt
$results = foreach($t in $targets){
$s = New-Object Net.Sockets.TcpClient
$sw = [System.Diagnostics.Stopwatch]::StartNew()
try {
$s.Connect($t, 5985) # 5986 pour HTTPS
$sw.Stop()
[pscustomobject]@{Computer=$t; Port=5985; LatencyMs=$sw.ElapsedMilliseconds; Status='Open'}
} catch {
[pscustomobject]@{Computer=$t; Port=5985; LatencyMs=$null; Status='Blocked'}
} finally { $s.Dispose() }
}
$results | Sort-Object Status,LatencyMs,Computer | Format-Table -AutoSize
Bonnes pratiques côté PowerShell Remoting
- Préférer Kerberos (intra‑domaine) à NTLM ; spécifier explicitement
-Authentication Kerberos
pour éviter des bascules imprévues. - Utiliser HTTPS (
5986
) si vos flux traversent des équipements d’inspection TLS – vérifiez les certificats WinRM et leurs dates de rotation. - Limiter la simultanéité côté source : 20–30 flux actifs suffisent souvent pour saturer le pipeline WS‑Man sans générer de pics d’échec.
- Fermer systématiquement les
PSSession
et réutiliser un runspace pool pour réduire le churn de processuswsmprovhost.exe
sur les cibles. - Journaliser les codes d’erreur côté application (y compris exceptions internes WinRM) et corréler avec les horodatages de rotation/maintenance.
Procédure complète d’investigation
- Reproduction contrôlée : 10–15 machines, démarrage à 00:05, durée > 40 min, throttling=20.
- Traçage : activer les journaux WinRM Operational sur source et cibles ; relever Security 4768/4771/4625/4740 sur DC.
- Temps/NTP : vérifier
w32tm /monitor
, corriger si drift > 1–2 min. - Réseau : tester 5985/5986 ; demander les fenêtres de sauvegarde/IDS/pare‑feu autour de 00:15–00:30.
- Quotas : lire
winrm get winrm/config
; releverMaxShellsPerUser
,MaxConnections
etIdleTimeout
. - Application : rechargement des secrets après rotation ; mise en place d’un runspace pool et d’un jitter.
- Validation : après chaque changement, rejouer une campagne et vérifier l’absence d’erreurs > 30 min après 00:20.
FAQ orientée dépannage
Pourquoi l’erreur n’apparaît‑elle pas le jour ?
Parce que la cause est temporelle : rotation de secret, maintenance, redémarrage d’un service KDC/DNS, ou quotas remis à zéro après la nuit. Lancer des campagnes de jour ne déclenche pas ces conditions.
Augmenter les quotas WinRM suffit‑il ?
Non. C’est utile pour valider une hypothèse de saturation, mais la solution durable consiste à lisser la charge (pooling/throttling), fermer les shells inactifs, et corriger la source (rotation de mot de passe, NTP, maintenance).
Comment savoir si c’est une rotation de mot de passe ?
Cherchez une coïncidence stricte entre heure de rotation et début des échecs 4771/4625, et vérifiez les journaux de votre coffre de secrets/PAM. Testez immédiatement un Get-Credential
manuel ; si la connexion passe avec le nouveau secret mais échoue via l’appli, c’est un cache de secret côté application.
Modèle de rapport d’incident exploitable
- Période impactée : 00:20–01:00 (début exact mesuré à la seconde).
- Volume : 100 cibles, ~X sessions échouées.
- Codes d’échec : 4771 0x18, 4625 type 3 (selon cas).
- Réseau : ports 5985/5986 atteignables/non atteignables à t0+N.
- Quotas WinRM : valeurs avant/après, messages Operational.
- Correctifs : rotation coordonnée, NTP, pooling, ajustements quotas.
- Résultat : aucune erreur sur plage 00:20–01:10 après correctif.
Checklist de stabilisation durable
- Adopter une gMSA pour l’application .NET ou un mécanisme de refresh de secret post‑rotation.
- Forcer Kerberos lorsque c’est possible, ou sécuriser NTLM si nécessaire.
- Déployer un runspace pool et un throttling à 20–30 avec gigue.
- Surveiller en continu les journaux WinRM et les événements 4768/4771/4625/4740.
- Mettre en place des tests synthétiques entre 00:15 et 00:40.
- Documenter et réduire les fenêtres de maintenance chevauchant minuit.
- Encadrer les quotas WinRM par GPO, avec des valeurs validées en charge.
Résumé exécutif
Le motif « réussite 00:00–00:20 puis avalanche de « username and password incorrect » » pointe vers un événement planifié : rotation de secret, maintenance réseau, ou saturation des quotas. En combinant collecte de journaux (WinRM + DC), vérification NTP, lissage de la charge (runspace pool/throttling) et ajustement mesuré des limites WinRM, on élimine rapidement les causes et on stabilise définitivement les campagnes nocturnes de PowerShell Remoting à grande échelle.
Rappel des étapes recommandées
- Reproduire sur 10–15 machines pour isoler le facteur temps.
- Tracer WinRM et Sécurité (4768/4771/4625/4740) durant 00:00–01:00.
- Vérifier l’infrastructure (KDC/DNS/pare‑feu/IDS) et NTP.
- Ajuster les quotas WinRM si des messages de dépassement apparaissent.
- Implémenter un runspace pool ou un throttling avec gigue.
- Confirmer la disparition de l’erreur au‑delà de 30 minutes après le point de bascule.
En appliquant cette démarche structurée—collecte de logs, vérification des quotas et limitation de la charge—on identifie généralement une combinaison de quotas WinRM et de maintenance réseau planifiée comme cause première.