Code : MO-AD-008 | Version : 1.1 | Date : 16 avril 2026 | Auteur : C. Legrand
v1.1 -- 16 avril 2026 : note de synchronisation post-rotation. Le mot de passe historique
Colombo66(OPNsense 1) est confirme obsolete depuis le 16/04. Les items residuels (OPNsense 2, Ferme HyperV, SuluCisco, Zabbix) restent traites separement via MO-SEC-003.
Procédure de rotation semestrielle des mots de passe critiques de l'infrastructure BTS SIO :
Le compte krbtgt fait l'objet du mode opératoire dédié MO-AD-002 (rotation en deux passes à 12h d'intervalle).
Contexte initial : avant la première exécution de cette procédure (14/04/2026), la majorité de ces mots de passe étaient identiques (même MDP réutilisé) et inchangés depuis novembre 2022 (1 252 jours pour le compte Administrateur AD, 1 404 jours pour krbtgt). Un test de password spraying découvrait le compte Administrateur en moins d'une heure.
| Public concerné | Administrateurs de l'infrastructure BTS SIO |
| Systèmes ciblés | DC1+DC2 (AD), OPNsense 1 (10.0.112.1), OPNsense 2 (10.0.112.101 si actif), NAS QNAP Scotty (10.0.112.5), Zabbix (10.0.112.190 web + SSH) |
| Outils | PowerShell + WinRM, Python requests, Playwright (headless Chromium), Bitwarden CLI |
| Authentification | Compte Domain Admin pour AD, vault Vaultwarden (admin@bts.sio) pour le reste |
| Durée totale | 90 minutes |
| Présentiel requis | Recommandé (accès LAN évite la rupture VPN/WinRM en cas d'incident OPNsense ou DC1) |
bw unlock --raw)pip install playwright && playwright install chromium)
Cache NTLM : après la rotation du MDP AD, l'ancien mot de passe reste accepté pendant environ 1 heure via le cache NTLM des contrôleurs de domaine. Comportement attendu de Windows, ne pas confondre avec un échec de propagation.
| Règle | Justification |
|---|---|
| 24 caractères minimum | Au-delà de la cible ANSSI (12) et FGPP-Admins (16) |
Charset [A-Za-z0-9!@#%_+-=.] |
Évite les caractères problématiques (apostrophe, dollar, backslash) qui cassent l'échappement shell/PowerShell/SQL |
| Génération cryptographique | secrets.choice (Python) ou openssl rand, jamais Math.random ou $RANDOM |
| Un MDP par système | Aucun partage entre AD, OPNsense, NAS, Zabbix |
| Stockage exclusif Vaultwarden | Aucun fichier en clair, aucune note papier |
python3 -c "
import secrets, string
chars = string.ascii_letters + string.digits + '!@#%_+-=.'
print(''.join(secrets.choice(chars) for _ in range(24)))"
Ordre d'exécution : suivre l'ordre périphériques avant AD. Si la rotation AD échoue, on conserve l'accès aux firewalls et au NAS pour intervenir. L'inverse n'est pas vrai.
Sur DC1 via WinRM :
repadmin /replsummary # 0 echec / 0 erreur
Get-Service NTDS, kdc, DNS, Netlogon, W32Time
netdom query fsmo
wbadmin get versions # backup SystemState < 7 jours
Toute anomalie doit être corrigée avant de continuer.
HTTP plutôt qu'HTTPS : depuis le VLAN pédagogique (10.0.232.0/16), HTTPS 443 vers 10.0.112.1 est souvent en timeout (règle de filtrage). HTTP 80 répond et permet le scripting. Pas de risque : la session reste interne au LAN.
import re, requests
s = requests.Session()
# 1. Anti-CSRF dynamique : nom + valeur aléatoires
r = s.get('http://10.0.112.1/')
m = re.search(r'<input type="hidden" name="([A-Za-z0-9_-]+)" value="([A-Za-z0-9_-]+)"', r.text)
csrf_name, csrf_value = m.group(1), m.group(2)
# 2. Login
s.post('http://10.0.112.1/', data={
csrf_name: csrf_value,
'usernamefld': 'admin',
'passwordfld': OLD_PW,
'login': '1',
})
# 3. Récupérer le formulaire de changement de MDP (nouveau CSRF)
r = s.get('http://10.0.112.1/system_usermanager_passwordmg.php')
m = re.search(r'<input type="hidden" name="([A-Za-z0-9_-]+)" value="([A-Za-z0-9_-]+)"', r.text)
csrf_name, csrf_value = m.group(1), m.group(2)
# 4. POST nouveau MDP
s.post('http://10.0.112.1/system_usermanager_passwordmg.php', data={
csrf_name: csrf_value,
'passwordfld0': OLD_PW,
'passwordfld1': NEW_PW,
'passwordfld2': NEW_PW,
'save': 'Sauvegarder',
})
# -> 302 ?savemsg=Saved+settings+for+user
Validation : login avec nouveau MDP → redirection /ui/core/dashboard. Login avec ancien MDP → reste sur page login (HTTP 200 sans redirection).
QTS 4.2.6 EOL : les endpoints REST de modification d'utilisateur (
privRequest.cgi,userConfig.cgi) renvoient HTTP 200 vide sans appliquer le changement. Le SSH (port 22) est fermé. Seule l'automation de l'UI ExtJS via Playwright fonctionne.
import asyncio
from playwright.async_api import async_playwright
async def run():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True,
args=['--ignore-certificate-errors'])
ctx = await browser.new_context(ignore_https_errors=True)
page = await ctx.new_page()
# Login
await page.goto('https://10.0.112.5')
await page.fill('input#username', 'admin')
await page.fill('input[type="password"]', OLD_PW)
await page.locator('button:has-text("Login")').first.click()
await asyncio.sleep(5) # laisser charger le desktop ExtJS
# Panneau de configuration -> Utilisateurs
await page.locator('text="Panneau de configuration"').first.dblclick()
await asyncio.sleep(3)
await page.locator('text="Utilisateurs"').first.click()
# Cliquer la première img de la ligne admin (icône change pwd, sans tooltip)
await page.locator('tr:has-text("admin") img').first.click()
await asyncio.sleep(2)
# Remplir les 3 champs (ancien / nouveau / confirmer) et appliquer
pwf = page.locator('input[type="password"]')
await pwf.nth(0).fill(OLD_PW)
await pwf.nth(1).fill(NEW_PW)
await pwf.nth(2).fill(NEW_PW)
await page.locator('button:has-text("Appliquer")').first.click()
await asyncio.sleep(3)
await browser.close()
asyncio.run(run())
Validation :
import base64, requests
b64 = base64.b64encode(NEW_PW.encode()).decode()
r = requests.get(f'https://10.0.112.5/cgi-bin/authLogin.cgi?user=admin&pwd={b64}',
verify=False)
# Vérifier <authPassed><![CDATA[1]]></authPassed>
$Old = ConvertTo-SecureString $env:OLD_PW -AsPlainText -Force
$New = ConvertTo-SecureString $env:NEW_PW -AsPlainText -Force
Set-ADAccountPassword -Identity Administrateur `
-OldPassword $Old -NewPassword $New `
-Server (Get-ADDomainController -Discover).HostName[0]
# Forcer la réplication immédiate
Start-Sleep -Seconds 10
repadmin /syncall /AdeP
# Vérifier la propagation sur tous les DCs
foreach ($dc in (Get-ADDomainController -Filter *)) {
Get-ADUser Administrateur -Server $dc.HostName -Properties PasswordLastSet |
Format-Table SamAccountName, PasswordLastSet
}
Validation WinRM avec nouveau MDP :
import winrm
s = winrm.Session('10.0.112.2', auth=('BTS\Administrateur', NEW_PW), transport='ntlm')
print(s.run_cmd('whoami').std_out.decode())
# -> bts\administrateur
Répéter sur DC2 (10.0.112.3).
Pour chaque système dont le MDP a changé :
export NODE_TLS_REJECT_UNAUTHORIZED=0 # certificat self-signed
export BW_SESSION=$(bw unlock --raw)
ITEM_ID=$(bw list items --search "OPNsense 1" | jq -r '.[0].id')
bw get item $ITEM_ID | jq '.login.password = "<NOUVEAU_MDP>"' \
| bw encode | bw edit item $ITEM_ID
bw sync
Items à mettre à jour : DC1, DC2, OPNsense 1, OPNsense 2, Scotty — NAS QNAP TS-439.
Si le MDP du NAS a changé, mettre à jour le fichier d'authentification utilisé par le script de backup. Voir MO-AD-006 §4.6.
$nouveau = "admin|<NOUVEAU_MDP_NAS>"
Set-Content -Path 'C:\Backups-AD
as.cred' -Value $nouveau -NoNewline
# L'ACL existante (SYSTEM + Administrators) est préservée par Set-Content.
Tester immédiatement :
$cred = (Get-Content 'C:\Backups-AD
as.cred' -Raw).Trim()
$user, $pwd = $cred -split '\|', 2
net use Y: "\10.0.112.5\Partage NAS" /user:$user $pwd
Get-ChildItem Y:\ | Select-Object -First 5
net use Y: /delete
OPNsense
NAS QNAP
authLogin.cgi avec nouveau MDP → authPassed=1authLogin.cgi avec ancien MDP → authPassed=0C:\Backups-AD as.cred mis à jour (ACL préservée)net use test réussi avec nouveau MDPActive Directory
PasswordLastSet sur les 2 DCs = aujourd'hui| Cible | Procédure de retour arrière |
|---|---|
| OPNsense | Console physique (option 3) Reset the root password dans le menu CLI). Nécessite accès clavier/écran sur le boîtier. |
| NAS QNAP | Bouton de reset physique sur le boîtier (10 secondes appuyé). Réinitialise le MDP admin à la valeur d'usine sans toucher aux données. |
| Active Directory | Utiliser un autre compte Domain Admin pour réinitialiser : Set-ADAccountPassword -Identity Administrateur -Reset -NewPassword $X. En dernier recours, mode DSRM via la console serveur. |
Rollback Vaultwarden : Vaultwarden conserve l'historique des modifications par item (Attachments & History dans l'UI web). On peut récupérer la valeur précédente.
| Problème | Solution |
|---|---|
| OPNsense HTTPS 443 timeout depuis le VLAN pédagogique | Utiliser HTTP 80. Si HTTP échoue aussi, basculer sur l'IP de management depuis le VLAN admin. |
| QNAP : le dialog Changer mot de passe ne se trouve pas par texte | L'icône est une <img> sans tooltip dans QTS 4.2. Utiliser tr:has-text("admin") img.first |
QNAP : authPassed=0 avec le nouveau MDP |
Caractère & ou = non accepté. Re-générer en restreignant à [A-Za-z0-9!@#%_+-=.] |
AD : Set-ADAccountPassword échoue (Access denied) |
Compte WinRM doit être Domain Admin. Vérifier l'ACL AdminSDHolder. |
| AD : ancien MDP encore accepté après rotation | Cache NTLM : phénomène attendu (~1h). Pour purger : Restart-Service Netlogon sur les DCs. |
| Vaultwarden CLI : self signed certificate | Exporter NODE_TLS_REJECT_UNAUTHORIZED=0 avant tout appel bw. |
| Backup T43 échoue après rotation NAS | nas.cred non mis à jour. Vérifier contenu et ACL (cf. MO-AD-006 §4.6). |