PrintNightmare – CVE-2021-34528

Microsoft a publié une vulnérabilité dans le service spouleur d’impression, dont tous les détails peuvent se retrouver à ce lien. Il est important de prendre cette faille au sérieux car elle permet notamment d’exécuter du code en tant qu’utilisateur SYSTEM ce qui peut être désastreux en fonction de la machine sur laquelle l’exécution est réalisée.

Plusieurs workarounds sont proposés par Microsoft, et ayant mis en place les corrections nécessaires, je vais partager les quelques scripts (Powershell 5.1) que j’ai conçu afin de me faciliter la tâche.

Tout d’abord, à partir d’un fichier CSV d’inventaire, ce script va interroger le système distant pour récupérer l’état complet du service Spooler (état actuel, type de démarrage, etc.). Ainsi, il est possible d’avoir un état des lieux sur l’exécution de ce service.

$inv = Import-Csv inventaire.csv
$output = @()
foreach($srv in $inv){
    Write-Output $srv.hostname
    $output+=Get-Service -Name Spooler -Computer $srv.hostname
}
$output | Export-Csv spooler_status.csv -Delimiter ";" -Encoding UTF8

Ce deuxième script va appliquer le workaround conseillé par Microsoft ; il s’agit de l’extinction du service Spooler et la désactivation du démarrage automatique de celui-ci. Le script interroge ensuite de nouveau le service pour obtenir son état et génère un fichier CSV de retour.

$inv = Import-Csv spooler_off.csv -Delimiter ";"
$output = @()
foreach($srv in $inv){
    Write-Host $srv.MachineName
    Stop-Service -InputObject $(Get-Service -Name Spooler -ComputerName $srv.MachineName)
    Set-Service -Name Spooler -StartupType Disabled -ComputerName $srv.MachineName
    $srvstat = New-Object PSCustomObject
    $srvstat | Add-Member -Name "Hostname" -Value $srv.MachineName -MemberType NoteProperty
    $srvstat | Add-Member -Name "SpoolerStatus" -Value (Get-Service -Name Spooler -ComputerName $srv.MachineName).Status -MemberType NoteProperty
    $output+=$srvstat
}
$output | Export-Csv spooler_processed.csv -Encoding UTF8 -Delimiter ";"

Et enfin, puisque Microsoft a mis à jour aujourd’hui la vulnérabilité en incluant un contrôle de clefs de registre qui n’était pas présent hier, ce troisième script va interroger les serveurs d’une liste au format CSV afin de récupérer l’état des clefs de registre mettant à risque le système.

$output = @()
$inv = Import-Csv inventaire_hostname.csv -Delimiter ";"
foreach($srv in $inv){
    Write-Host $srv.Hostname
    $pnp = New-Object PSCustomObject
    if(Invoke-Command -ComputerName $srv.Hostname { Test-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\PointAndPrint\" }){
        $pnp | Add-Member -Name "Hostname" -Value $srv.Hostname -MemberType NoteProperty
        $pnp | Add-Member -Name "PointAndPrintKeyExists" -Value "Yes" -MemberType NoteProperty  
        if((Invoke-Command -ComputerName $srv.Hostname { Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\PointAndPrint\" -Name NoWarningNoElevationOnInstall }) -eq 1) {
            $pnp | Add-Member -Name "NoWarningNoElevationOnInstall" -Value "1" -MemberType NoteProperty
        }
        else { $pnp | Add-Member -Name "NoWarningNoElevationOnInstall" -Value "0" -MemberType NoteProperty }
        if((Invoke-Command -ComputerName $srv.Hostname {Get-ItemPropertyValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Printers\PointAndPrint\" -Name NoWarningNoElevationOnUpdate }) -eq 1) {
            $pnp | Add-Member -Name "NoWarningNoElevationOnUpdate" -Value "1" -MemberType NoteProperty
        }
        else { $pnp | Add-Member -Name "NoWarningNoElevationOnUpdate" -Value "0" -MemberType NoteProperty }
    }
    else {$pnp | Add-Member -Name "Hostname" -Value $srv.Hostname -MemberType NoteProperty ; $pnp | Add-Member -Name "PointAndPrintKeyExists" -Value "No or Server Unreacheable" -MemberType NoteProperty }
    $output+=$pnp
}
$output | Export-Csv pointnprint.csv -Delimiter ";" -Encoding UTF8 

En fonction du nombre de serveurs à interroger, l’exécution du script peut être relativement longue. Par souci de rapidité de développement, je n’ai pas inclus de contrôle de réponse du serveur avant d’utiliser Invoke-Command. Il est donc possible que Powershell retourne une erreur si le serveur ne répond pas à la requête ou si il la rejette, cela n’impacte pas le bon déroulement du script.

Naturellement, une étude d’impact et de faisabilité est à faire avant toute intervention et extinction d’un service, même pour le spouleur qui n’est pas d’une importance capitale sur une grande majorité de machines. Il ne reste donc plus qu’à attendre l’application des correctifs de sécurité publiés par Microsoft.

Mes premiers pas sur Python — acte III

Afin de pratiquer Python, j’ai trouvé quelques pages d’exercices sur w3resource. Je n’ai pas réalisé tous les exercices présentés mais plutôt une sélection d’entre eux. Dans cet article, je vais présenter mon code pour chacun d’entre eux, la solution officielle n’étant pas forcément la seule valable. Nombre de ces scripts utilisent des arguments pour être fonctionnels ; vous pouvez appeler l’aide intégrée au script en passant -h en argument. J’ai codé ces snippets avec Python 3.9.1 sur macOS.

Continue reading

Mes premiers pas sur Python – acte II

Il y a quelques semaines de cela, je réalisais mes premiers scripts en Python, langage que j’ai toujours voulu apprendre mais pour lequel je manquais de use case concrets, ayant plus l’habitude de Powershell, travaillant sur des environnements Microsoft. Cependant, par curiosité et envie d’apprendre, je planche volontiers à l’occasion sur Python et c’est aujourd’hui pour un besoin purement personnel que j’ai écrit ces deux scripts.

Pour replacer le contexte, je participe à des compétitions de speedrunning de jeux rétro. Un score est calculé en fonction du temps réalisé pour compléter le jeu en question. Récemment, je me suis demandé si il n’était pas intéressant de valoriser les temps d’exécution très rapides en doublant chaque point attribué sous les 2 heures plutôt que d’avoir un traitement linéaire comme actuellement : on part d’une base de 36000 points (1 par seconde, jusqu’à 10 heures) et l’on soustrait un point pour chaque seconde.

Par exemple, pour une partie complétée en 1 heure, 50 minutes et 28 secondes, on a donc un score de 36000 – 6628 (1 x 3600 + 50 x 60 + 28) soit 29372. Avec ce calcul, on a donc 29372 ; mais avec la nouvelle formule, 36000 – 6628 + 572 soit 29944. Les 572 points proviennent des 572 secondes qui séparent le chronomètre des 2 heures.

J’ai donc développé deux scripts Python permettant de faire le calcul. Le premier script demande à l’utilisateur de saisir à la suite les valeurs. Les boucles try et if permettent de faire les contrôles de robustesse nécessaires :

hrCount = input("Hours: ")
mnCount = input("Minutes: ")
seCount = input("Seconds: ")
try:
	hrCount = int(hrCount)
	mnCount = int(mnCount)
	seCount = int(seCount)
except:
	print("Please input integer values as hours, minutes and seconds.")
	quit()
if seCount >= 60:
	print("Seconds value can't be higher than 60.")
	quit()
if mnCount >= 60:
	print("Minutes value can't be higher than 60.")
	quit()
totalSec = hrCount*3600+mnCount*60+seCount
if totalSec > 36000:
	print("Timer can't be higher than 10 hours.")
	quit()
base = 36000 - totalSec
if totalSec < 7200:
	bonus = 7200 - totalSec
else:
	bonus = 0
sc = base + bonus
print(base)
print(bonus)
print(sc)

Le script affiche à la fin le score de base calculé linéairement, le bonus puis le total.

Le deuxième script effectue le même traitement mais avec une approche différente. J’ai voulu alléger un peu le code et les contrôles de robustesse en utilisant le module argparse. Ce module permet d’ajouter des paramètres en ligne de commande au script : ainsi c’est Python lui-même qui va vérifier le type d’entrée de mes arguments (ici, des integer) et permettre d’afficher une aide.

import argparse
parser = argparse.ArgumentParser(description='Counts all seconds under a 2-hour timer twice.')
parser.add_argument('hrCount', metavar='h', type=int, help='Number of hours')
parser.add_argument('mnCount', metavar='m', type=int, help='Number of minutes')
parser.add_argument('seCount', metavar='s', type=int, help='Number of seconds')
args = parser.parse_args()

if args.seCount >= 60:
	print("Seconds value can't be higher than 60.")
	quit()
if args.mnCount >= 60:
	print("Minutes value can't be higher than 60.")
	quit()
totalSec = args.hrCount*3600+args.mnCount*60+args.seCount
if totalSec > 36000:
	print("Timer can't be higher than 10 hours.")
	quit()
base = 36000 - totalSec
if totalSec < 7200:
	bonus = 7200 - totalSec
else:
	bonus = 0
sc = base + bonus
print(base)
print(bonus)
print(sc)

Voici ce que cela donne en exécution. Le paramètre -h est directement intégré et n’a pas à être codé dans le script.

Les deux scripts sont disponibles dans des versions commentées dans l’archive en lien ci-dessous. J’ai utilisé la version 3.9 de Python sous macOS pour les écrire et les utiliser.

Header error lors d’un tar x sur une archive splitée

Hier, j’ai transféré plusieurs Go de données sur un serveur distant sous Debian 10, sous forme d’une archive tar splitée en fichiers de 4 Mo. J’avais donc dans mon répertoire de destination un peu plus de 1000 fichiers au format MonArchive.tar.001.

Je tente un merge avec cat de ces fichiers afin d’avoir une seule grosse archive tar que je peux ensuite extraire.

cat MonArchive.tar.* > MonArchive.tar

Puis je tente une extraction :

tar xvf MonArchive.tar

Et je me heurte à une erreur d’header lors de l’extraction, s’arrêtant net après le troisième fichier sur une vingtaine présente dans l’archive. Je retente un cat et un tar mais sans succès. Ayant encore les fichiers d’origine sur ma station Windows, je décide donc de faire un comparatif des checksums respectifs pour m’assurer qu’il n’y ait pas de fichier qui ait été mal transféré.

Sur mon système Debian :

sha256sum MonArchive.tar.* > MonArchiveChecksum

Sous Windows, grâce à Powershell :

Get-FileHash * -Algorithm SHA256

Je récupère donc mes extractions, ouvre un Notepad++ et compare les fichiers : aucune différence de checksum. Je suis donc un peu surpris car cela veut dire que les fichiers sont a priori identiques entre la destination et la source. En conclusion, le problème semble se situer au niveau du merge de mes multiples archives.

Au détour d’un banal ls sur mon système Debian, j’ai compris d’où venait le problème. A l’heure où j’écris cet article (il était 1 heure du matin quand je me suis penché sur le sujet 😅), je n’ai plus les archives sur mon système Debian mais je les ai régénérées sur mon système Windows, et la cause est identique. Voici le résultat d’une commande ls sur Powershell :

Les fichiers étaient nommés MonArchive.tar.001, MonArchive.tar.002 jusque MonArchive.tar.1032. On passe donc une numérotation sur 3 chiffres à 4 en arrivant au millier ; ce qui va fatalement poser problème pour cat puisqu’il fusionne les fichiers par ordre alphabétique. Or, par ordre alphabétique, il y a donc MonArchive.tar.100, MonArchive.tar.1000, MonArchive.tar.1001, MonArchive.tar.1002… avant de finalement continuer sur MonArchive.tar.101, MonArchive.tar.1010. Par conséquent, l’archive est mal constituée puisque pas concaténée dans l’ordre : cela ne serait pas arrivé si toute la numérotation avait été faite sur 4 chiffres dès le départ (7-zip pour Windows est l’outil de compression d’origine).

J’ai donc procédé en plusieurs étapes : isolation des fichiers tar à 4 chiffres, concaténation d’une première archive avec les 999 premiers fichiers tar, puis concaténation d’une deuxième archive avec les derniers fichiers, et enfin fusion des deux archives pour extraction. Ensuite, j’ai pu vérifier l’intégrité apparente de l’archive finale :

tar tvf MonArchiveFinale.tar

Tous les fichiers lisibles de l’archive sont apparus, j’ai donc pu les extraire 😊