Comment utiliser PowerShell pour la vérification du hachage des fichiers pour la sécurité informatique ?

Dans le monde informatique actuel, il est primordial de garantir l’intégrité et la sécurité des données. Une méthode efficace pour vérifier l’intégrité des fichiers consiste à utiliser des valeurs de hachage. Une valeur de hachage agit comme une empreinte digitale numérique des fichiers, permettant aux professionnels de l’informatique de vérifier si les fichiers ont été altérés ou corrompus. Cet article se penche sur un script PowerShell conçu pour rechercher des fichiers avec des valeurs de hachage spécifiques, ce qui en fait un outil précieux pour les professionnels de l’informatique et les fournisseurs de services gérés (MSP).

Contexte

Le script PowerShell fourni est conçu pour alerter les utilisateurs si un fichier avec un hachage et une extension spécifiés est trouvé dans un répertoire donné et ses sous-répertoires. Le hachage des fichiers est une pratique courante en informatique pour vérifier l’intégrité des fichiers et détecter les modifications non autorisées. Ce script simplifie et automatise le processus, ce qui permet aux professionnels de l’informatique de garantir plus facilement la sécurité et l’intégrité de leurs systèmes.

Le script

#Requires -Version 5.1

<#
.SYNOPSIS
    Alerts if a file with the extension and specified hash is found in the given search directory or subdirectories. Warning: Hashing large files may impact performance.
.DESCRIPTION
    Alerts if a file with the extension and specified hash is found in the given search directory or subdirectories. Warning: Hashing large files may impact performance.
.EXAMPLE
    -Hash "F55A61A82F4F5943F86565E1FA2CCB4F" -SearchPath "C:" -FileType ".ico" -CustomField "multiline"
    WARNING: Backslash missing from the search path. Changing it to C:\.
    WARNING: File with MD5 hash of F55A61A82F4F5943F86565E1FA2CCB4F found!

    File Name         Path                                                                                    
    ---------         ----                                                                                    
    zoo_ecosystem.ico C:\Users\Administrator\Desktop\Find-FileHash\Test Folder 1\zoo_ecosystem.ico            
    zoo_ecosystem.ico C:\Users\Administrator\Desktop\Find-FileHash\TestFolder1\Test Folder 1\zoo_ecosystem.ico
    zoo_ecosystem.ico C:\Users\Administrator\Desktop\Find-FileHash\TestFolder2\Test Folder 1\zoo_ecosystem.ico

    Attempting to set Custom Field 'multiline'.
    Successfully set Custom Field 'multiline'!

PARAMETER: -Hash "REPLACEMEWITHAVALIDHASH"
    Files with this hash should cause the alert to trigger.

PARAMETER: -Algorithm "MD5"
    Hashing algorithm used for the above hash.

PARAMETER: -SearchPath "C:\ReplaceMeWithAValidSearchPath"
   Specifies one or more starting directories for the search, separated by commas. The search will recursively include all subdirectories from these starting points.

PARAMETER: -Timeout "15"
    Once this timeout is reached, the script will stop searching for files that match your given hash.

PARAMETER: -FileType ".exe"
    Specifies the file extension to filter the search. Only files with this extension will be analyzed for a hash match. Example: '.exe'

PARAMETER: -CustomField "NameOfMultiLineCustomField"
    Specifies the name of an optional multiline custom field where results can be sent. 

.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes: Initial Release
By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use.
    Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. 
    Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. 
    Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. 
    Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. 
    Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. 
    Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. 
    EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA).
#>

[CmdletBinding()]
param (
    [Parameter()]
    [String]$Hash,
    [Parameter()]
    [String]$SearchPath = "C:\Windows",
    [Parameter()]
    [String]$FileType,
    [Parameter()]
    [Int]$Timeout = 15,
    [Parameter()]
    [String]$Algorithm = "MD5",
    [Parameter()]
    [String]$CustomField
)

begin {
    # Set parameters using dynamic script variables.
    if ($env:hash -and $env:hash -notlike "null") { $Hash = $env:hash }
    if ($env:hashType -and $env:hashType -notlike "null") { $Algorithm = $env:hashType }
    if ($env:searchPath -and $env:searchPath -notlike "null") { $SearchPath = $env:searchPath }
    if ($env:timeoutInMinutes -and $env:timeoutInMinutes -notlike "null") { $Timeout = $env:timeoutInMinutes }
    if ($env:fileExtensionToSearchFor -and $env:fileExtensionToSearchFor -notlike "null") { $FileType = $env:fileExtensionToSearchFor }
    if ($env:customFieldName -and $env:customFieldName -notlike "null") { $CustomField = $env:customFieldName }

    # If given a comma-separated list, split the paths.
    $PathsToSearch = New-Object System.Collections.Generic.List[String]
    if ($SearchPath -match ",") {
        $SearchPath -split "," | ForEach-Object { $PathsToSearch.Add($_.Trim()) }
    }
    else {
        $PathsToSearch.Add($SearchPath)
    }

    $ReplacementPaths = New-Object System.Collections.Generic.List[Object]
    $PathsToRemove = New-Object System.Collections.Generic.List[String]

    # If given a drive without the backslash add it in.
    $PathsToSearch | ForEach-Object {
        if ($_ -notmatch '^[A-Z]:\\$' -and $_ -match '^[A-Z]:$') {
            $NewPath = "$_\"
            $ReplacementPaths.Add(
                [PSCustomObject]@{
                    Index   = $PathsToSearch.IndexOf("$_")
                    NewPath = $NewPath
                }
            )
                
            Write-Warning "Backslash missing from the search path. Changing it to $NewPath."
        }
    }

    # Apply replacements
    $ReplacementPaths | ForEach-Object {
        $PathsToSearch[$_.index] = $_.NewPath 
    }

    # Check if the search path is valid.
    $PathsToSearch | ForEach-Object {
        if (-not (Test-Path $_)) {
            Write-Host -Object "[Error] $_ does not exist!"
            $PathsToRemove.Add($_)
            $ExitCode = 1
        }
    }

    $PathsToRemove | ForEach-Object {
        $PathsToSearch.Remove($_) | Out-Null
    }


    # Error out if no valid paths to search.
    if ($PathsToSearch.Count -eq 0) {
        Write-Host "[Error] No valid paths to search!"
        exit 1
    }

    # A file extension is required.
    if (-not $FileType) {
        Write-Host -Object "[Error] File Type is required!"
        exit 1
    }

    # If we were given the extension without the . we'll add it back in
    if ($FileType -notmatch '^\.') {
        $FileType = ".$FileType"
        Write-Warning -Message "Extension missing changing filetype to $FileType."
    }

    # The timeout has to be between 1 and 120
    if ($Timeout -lt 1 -or $Timeout -gt 120) {
        Write-Host "[Error] Invalid timeout given of $Timeout minutes. Please enter a value between 1 and 120."
        exit 1
    }

    # PowerShell 5.1 supports more algorithms than this, however PowerShell 7 does not.
    $ValidAlgorithms = "SHA1", "SHA256", "SHA384", "SHA512", "MD5"
    if ($ValidAlgorithms -notcontains $Algorithm) {
        Write-Host "[Error] Invalid Algorithm selected. Only SHA1, SHA256, SHA384, SHA512 and MD5 are supported."
        exit 1
    }

    # Helper function to make it easier to set custom fields.
    function Set-NinjaProperty {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory = $True)]
            [String]$Name,
            [Parameter()]
            [String]$Type,
            [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
            $Value,
            [Parameter()]
            [String]$DocumentName
        )
    
        $Characters = $Value | Measure-Object -Character | Select-Object -ExpandProperty Characters
        if ($Characters -ge 10000) {
            throw [System.ArgumentOutOfRangeException]::New("Character limit exceeded, value is greater than 10,000 characters.")
        }
        
        # If we're requested to set the field value for a Ninja document we'll specify it here.
        $DocumentationParams = @{}
        if ($DocumentName) { $DocumentationParams["DocumentName"] = $DocumentName }
        
        # This is a list of valid fields that can be set. If no type is given, it will be assumed that the input doesn't need to be changed.
        $ValidFields = "Attachment", "Checkbox", "Date", "Date or Date Time", "Decimal", "Dropdown", "Email", "Integer", "IP Address", "MultiLine", "MultiSelect", "Phone", "Secure", "Text", "Time", "URL", "WYSIWYG"
        if ($Type -and $ValidFields -notcontains $Type) { Write-Warning "$Type is an invalid type! Please check here for valid types. https://ninjarmm.zendesk.com/hc/en-us/articles/16973443979789-Command-Line-Interface-CLI-Supported-Fields-and-Functionality" }
        
        # The field below requires additional information to be set
        $NeedsOptions = "Dropdown"
        if ($DocumentName) {
            if ($NeedsOptions -contains $Type) {
                # We'll redirect the error output to the success stream to make it easier to error out if nothing was found or something else went wrong.
                $NinjaPropertyOptions = Ninja-Property-Docs-Options -AttributeName $Name @DocumentationParams 2>&1
            }
        }
        else {
            if ($NeedsOptions -contains $Type) {
                $NinjaPropertyOptions = Ninja-Property-Options -Name $Name 2>&1
            }
        }
        
        # If an error is received it will have an exception property, the function will exit with that error information.
        if ($NinjaPropertyOptions.Exception) { throw $NinjaPropertyOptions }
        
        # The below type's require values not typically given in order to be set. The below code will convert whatever we're given into a format ninjarmm-cli supports.
        switch ($Type) {
            "Checkbox" {
                # While it's highly likely we were given a value like "True" or a boolean datatype it's better to be safe than sorry.
                $NinjaValue = [System.Convert]::ToBoolean($Value)
            }
            "Date or Date Time" {
                # Ninjarmm-cli expects the  Date-Time to be in Unix Epoch time so we'll convert it here.
                $Date = (Get-Date $Value).ToUniversalTime()
                $TimeSpan = New-TimeSpan (Get-Date "1970-01-01 00:00:00") $Date
                $NinjaValue = $TimeSpan.TotalSeconds
            }
            "Dropdown" {
                # Ninjarmm-cli is expecting the guid of the option we're trying to select. So we'll match up the value we were given with a guid.
                $Options = $NinjaPropertyOptions -replace '=', ',' | ConvertFrom-Csv -Header "GUID", "Name"
                $Selection = $Options | Where-Object { $_.Name -eq $Value } | Select-Object -ExpandProperty GUID
        
                if (-not $Selection) {
                    throw [System.ArgumentOutOfRangeException]::New("Value is not present in dropdown")
                }
        
                $NinjaValue = $Selection
            }
            default {
                # All the other types shouldn't require additional work on the input.
                $NinjaValue = $Value
            }
        }
        
        # We'll need to set the field differently depending on if its a field in a Ninja Document or not.
        if ($DocumentName) {
            $CustomField = Ninja-Property-Docs-Set -AttributeName $Name -AttributeValue $NinjaValue @DocumentationParams 2>&1
        }
        else {
            $CustomField = Ninja-Property-Set -Name $Name -Value $NinjaValue 2>&1
        }
        
        if ($CustomField.Exception) {
            throw $CustomField
        }
    }

    # Test for local administrator rights
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    $ExitCode = 0
}
process {
    if (-not (Test-IsElevated)) {
        Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges."
        exit 1
    }

    # If we're given a file instead of a folder, we'll check if it matches anyways.
    $PathsToSearch | ForEach-Object {
        if (-not (Get-Item $_).PSIsContainer) {
            Write-Warning "The search path you gave is actually a file not a folder. Checking the hash of the file..."
        }
    }
    
    $HashJobs = New-Object System.Collections.Generic.List[object]
    $MatchingFiles = New-Object System.Collections.Generic.List[object]
    $CustomFieldValue = New-Object System.Collections.Generic.List[string]

    # We'll use a PowerShell job so that we can timeout appropriately.
    $PathsToSearch | ForEach-Object {
        $HashJobs.Add(
            (
                Start-Job -ScriptBlock {
                    param($SearchPath, $FileType, $Algorithm, $Hash)
                    $Files = Get-ChildItem -Path $SearchPath -Filter "*$FileType" -File -Recurse
                    $Files | ForEach-Object {
                        $CurrentHash = Get-FileHash -Path $_.FullName -Algorithm $Algorithm
                        if ($CurrentHash.Hash -match $Hash) { $_.FullName }
                    }
                } -ArgumentList $_, $FileType, $Algorithm, $Hash
            )
        )
    }

    $TimeoutInSeconds = $Timeout * 60
    $StartTime = Get-Date

    # Wait for all jobs to complete or timeout
    foreach ($HashJob in $HashJobs) {
        # Calculate the remaining time
        $TimeElapsed = (Get-Date) - $StartTime
        $RemainingTime = $TimeoutInSeconds - $TimeElapsed.TotalSeconds
    
        # If there is no remaining time, break the loop
        if ($RemainingTime -le 0) {
            break
        }
    
        # Wait for the current job with the remaining time as the timeout
        $HashJob | Wait-Job -Timeout $RemainingTime | Out-Null
    }

    # If we failed to complete the job, we'll output a warning.
    $IncompleteJobs = $HashJobs | Get-Job | Where-Object { $_.State -eq "Running" }
    if ($IncompleteJobs) {
        Write-Host "[Error] The timeout period of $Timeout minutes has been reached, but some files still require a hash check!"
        $CustomFieldValue.Add("[Error] The timeout period of $Timeout minutes has been reached, but some files still require a hash check!")
        $ExitCode = 1
    }

    # Receive the data from the job.
    $HashJobs | Receive-Job -ErrorAction SilentlyContinue -ErrorVariable JobErrors | ForEach-Object {
        $MatchingFiles.Add(
            [PSCustomObject]@{
                "File Name" = $(Split-Path $_ -Leaf)
                Path        = $_
            }
        )
    }

    # If we have any matching files, we'll output them here.
    if ($MatchingFiles) {
        Write-Warning -Message "File with $Algorithm hash of $Hash found!"
        $MatchingFiles | Format-Table -AutoSize | Out-String | Write-Host
        $MatchingFiles | Select-Object -ExpandProperty Path | ForEach-Object { $CustomFieldValue.Add($_) }
    }
    else {
        Write-Host -Object "No files found with $Hash."
    }

    # If we received any failures or errors, we'll output that here.
    $FailedJobs = $HashJobs | Get-Job | Where-Object { $_.State -ne "Completed" -and $_.State -ne "Running" }
    if ($FailedJobs -or $JobErrors) {
        Write-Host ""
        Write-Host "[Error] Failed to get the hash of certain files due to an error."
        $CustomFieldValue.Add(" ")
        $CustomFieldValue.Add("[Error] Failed to get the hash of certain files due to an error.")
        if ($JobErrors) {
            Write-Host ""
            $JobErrors | ForEach-Object { Write-Host "[Error] $($_.Exception.Message)" }
            $CustomFieldValue.Add(" ")
            $JobErrors | ForEach-Object { $CustomFieldValue.Add("[Error] $($_.Exception.Message)") }
        }
        $ExitCode = 1
    }

    $HashJobs | Remove-Job -Force

    # If we're given a custom field, we'll attempt to save the results to it.
    if ($CustomField) {
        try {
            Write-Host "Attempting to set Custom Field '$CustomField'."
            Set-NinjaProperty -Name $CustomField -Value ($CustomFieldValue | Out-String)
            Write-Host "Successfully set Custom Field '$CustomField'!"
        }
        catch {
            if (-not $_.Exception.Message) {
                Write-Host "[Error] $($_.Message)"
            }
            else {
                Write-Host "[Error] $($_.Exception.Message)"
            }
            $ExitCode = 1
        }
        
    }

    exit $ExitCode
}
end {
    
    
    
}

 

Description détaillée

Le script commence par des informations sur les métadonnées, notamment un synopsis, une description et un exemple d’utilisation. Il définit les paramètres que l’utilisateur peut spécifier, tels que la valeur de hachage, le chemin de recherche, le type de fichier, l’algorithme de hachage, le délai d’attente et un champ personnalisé facultatif pour les résultats de sortie.

Configuration des paramètres

  • Hash: La valeur de hachage spécifique à rechercher.
  • SearchPath: Le répertoire de départ de la recherche, par défaut C:NWindows.
  • FileType: L’extension de fichier à filtrer.
  • Timeout: Durée maximale de la recherche, fixée par défaut à 15 minutes.
  • Algorithm: L’algorithme de hachage, par défaut MD5.
  • CustomField: Un champ facultatif pour stocker les résultats.

Le script ajuste dynamiquement ces paramètres en fonction des variables environnementales, si elles sont définies.

Validation du chemin d’accès et du type de fichier

Le script valide les chemins de recherche, en s’assurant qu’ils existent et qu’ils sont correctement formatés. Il ajoute une barre oblique inverse aux lettres de lecteur si elles sont manquantes et vérifie si le chemin d’accès fourni est un fichier au lieu d’un dossier, en émettant des avertissements et en procédant à des ajustements en conséquence.

Gestion des tâches pour le calcul du hachage

Pour gérer les calculs de hachage potentiellement longs, le script utilise des tâches PowerShell. Il lance une tâche pour chaque chemin de recherche, en recherchant de manière récursive les fichiers portant l’extension spécifiée et en calculant leur valeur de hachage. Les résultats sont collectés et comparés à la valeur de hachage fournie.

Délai d’attente et gestion des erreurs

Le script comprend un mécanisme de temporisation qui permet d’arrêter la recherche au bout d’une période donnée, l’empêchant ainsi de fonctionner indéfiniment. Il collecte toutes les erreurs rencontrées pendant l’exécution du travail et fournit des messages d’erreur détaillés à l’utilisateur.

Intégration de champs personnalisés

Si un champ personnalisé est spécifié, le script tente d’y enregistrer les résultats à l’aide de la fonction Set-NinjaProperty, qui gère différents types de champs personnalisés et veille à ce que les données soient correctement formatées et respectent les limites de caractères.

Résultat final

Le script affiche les fichiers correspondants, les éventuelles erreurs rencontrées et tente de définir le champ personnalisé en fonction des résultats. Il se termine par un code d’état approprié en fonction de la réussite ou de l’échec des opérations.

Cas d’utilisation potentiels

Imaginez un professionnel de l’informatique chargé de maintenir l’intégrité des fichiers de distribution de logiciels sur un réseau. Ce script lui permet d’analyser régulièrement les répertoires dans lesquels sont stockés les logiciels, afin de s’assurer qu’aucune modification non autorisée n’y a été apportée. Par exemple, si le hachage d’un fichier .exe critique change, cela peut indiquer une altération ou une corruption, ce qui nécessite une investigation immédiate.

Comparaisons

Les méthodes traditionnelles pour vérifier l’intégrité des fichiers impliquent souvent des contrôles manuels de hachage à l’aide d’outils tels que certutil sur Windows. Ce script automatise et optimise le processus, offrant ainsi une solution plus efficace et plus complète. Il s’intègre également en toute simplicité à d’autres scripts et outils utilisés par les professionnels de l’informatique, ce qui renforce son utilité dans divers scénarios.

FAQ

Comment puis-je spécifier plusieurs répertoires pour la recherche ?
Utilisez une liste séparée par des virgules dans le paramètre -SearchPath.

Quels sont les algorithmes de hachage pris en charge ?
Le script prend en charge MD5, SHA1, SHA256, SHA384 et SHA512.

Puis-je augmenter le délai d’attente au-delà de 120 minutes ?
Non, le délai maximum autorisé est de 120 minutes pour des raisons de performance et de praticité.

Implications

L’utilisation de ce script aide les professionnels de l’informatique à garantir l’intégrité des fichiers cruciaux, réduisant ainsi le risque de failles de sécurité causées par des fichiers altérés ou corrompus. En automatisant le processus de vérification du hachage, il permet de gagner du temps et fournit une méthode fiable pour maintenir l’intégrité des données sur de grands réseaux.

Recommandations

  • Mettez à jour et entretenez régulièrement le script pour l’adapter aux nouveaux algorithmes de hachage et aux nouveaux types de fichiers.
  • Planifiez l’exécution du script à intervalles réguliers afin de contrôler en permanence l’intégrité des fichiers.
  • Utilisez la fonction de champ personnalisé pour enregistrer les résultats dans un endroit central afin d’en faciliter l’accès et la révision.

Conclusion

L’intégration de scripts PowerShell comme celui-ci dans votre boîte à outils informatique améliore votre capacité à maintenir l’intégrité et la sécurité des données. Des outils tels que NinjaOne peuvent améliorer davantage ces processus, en offrant des fonctionnalités avancées et des intégrations qui rendent la gestion des systèmes informatiques plus efficace. En tirant parti de ces technologies, les professionnels de l’informatique peuvent mieux protéger leurs environnements et garantir la fiabilité de leurs données.

Pour aller plus loin

Créer une équipe informatique efficace et performante nécessite une solution centralisée qui soit l’outil principal pour fournir vos services. NinjaOne permet aux équipes informatiques de surveiller, gérer, sécuriser et prendre en charge tous les appareils, où qu’ils soient, sans avoir besoin d’une infrastructure complexe sur site.

Pour en savoir plus sur NinjaOne Endpoint Management, participez à une visite guidée ou commencez votre essai gratuit de la plateforme NinjaOne.

Catégories :

Vous pourriez aussi aimer

×

Voir NinjaOne en action !

En soumettant ce formulaire, j'accepte la politique de confidentialité de NinjaOne.

Termes et conditions NinjaOne

En cliquant sur le bouton « J’accepte » ci-dessous, vous indiquez que vous acceptez les termes juridiques suivants ainsi que nos conditions d’utilisation:

  • Droits de propriété: NinjaOne possède et continuera de posséder tous les droits, titres et intérêts relatifs au script (y compris les droits d’auteur). NinjaOne vous accorde une licence limitée pour l’utilisation du script conformément à ces conditions légales.
  • Limitation de l’utilisation: Les scripts ne peuvent être utilisés qu’à des fins personnelles ou professionnelles internes légitimes et ne peuvent être partagés avec d’autres entités.
  • Interdiction de publication: Vous n’êtes en aucun cas autorisé à publier le script dans une bibliothèque de scripts appartenant à, ou sous le contrôle d’un autre fournisseur de logiciels.
  • Clause de non-responsabilité: Le texte est fourni « tel quel » et « tel que disponible », sans garantie d’aucune sorte. NinjaOne ne promet ni ne garantit que le script sera exempt de défauts ou qu’il répondra à vos besoins ou attentes particulières.
  • Acceptation des risques: L’utilisation du script est sous votre propre responsabilité. Vous reconnaissez qu’il existe certains risques inhérents à l’utilisation du script, et vous comprenez et assumez chacun de ces risques.
  • Renonciation et exonération de responsabilité: Vous ne tiendrez pas NinjaOne pour responsable des conséquences négatives ou involontaires résultant de votre utilisation du script, et vous renoncez à tout droit ou recours légal ou équitable que vous pourriez avoir contre NinjaOne en rapport avec votre utilisation du script.
  • EULA: Si vous êtes un client de NinjaOne, votre utilisation du script est soumise au contrat de licence d’utilisateur final qui vous est applicable (End User License Agreement (EULA)).