Détecter et prévenir les attaques par force brute avec PowerShell

Si les cybermenaces évoluent sans cesse, il n’en reste pas moins que la plupart des intrusions s’appuient sur des techniques de base qui ont fait leurs preuves. Qui a besoin d’un zero-day si le fait deviner des mots de passe couramment utilisés ou exploiter des mots de passe utilisés sur plusieurs comptes vous permet d’accéder facilement à l’information ?

Les attaques par force brute restent une menace incroyablement courante pour les entreprises. Il est essentiel de détecter et de bloquer ces tentatives le plus rapidement possible, car elles sont souvent le signe avant-coureur d’activités plus dommageables et de tentatives d’accès malveillant à venir. Parce que chaque minute compte dans ces situations, la mise en place de stratégies de verrouillage des comptes et d’alertes en temps réel en cas de tentatives de connexion infructueuses constitue une mesure de dissuasion et d’alerte précoce extrêmement importante.

Mais qu’en est-il de la détection des attaques par force brute sur l’ensemble d’un réseau d’entreprise, à distance et à grande échelle ?

Parce que cela peut être un défi, nous avons fourni le script suivant que les administrateurs peuvent utiliser pour automatiser le processus, en surveillant les tentatives de connexion échouées et en déclenchant des alertes basées sur des seuils personnalisables.

Script de détection et de prévention des attaques par force brute

#Requires -Version 5.1

<#
.SYNOPSIS
    Condition for helping detect brute force login attempts.
.DESCRIPTION
    Condition for helping detect brute force login attempts.
.EXAMPLE
     -Hours 10
    Number of hours back in time to look through in the event log.
    Default is 1 hour.
.EXAMPLE
    -Attempts 100
    Number of login attempts to trigger at or above this number.
    Default is 8 attempts.
.OUTPUTS
    PSCustomObject[]
.NOTES
    Minimum OS Architecture Supported: Windows 10, Windows Server 2016
    Release Notes:
    Initial Release
    (c) 2023 NinjaOne
    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/fr/conditions-dutilisation
    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()]
    [int]
    $Hours = 1,
    [Parameter()]
    [int]
    $Attempts = 8
)

begin {
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    function Test-StringEmpty {
        param([string]$Text)
        # Returns true if string is empty, null, or whitespace
        process { [string]::IsNullOrEmpty($Text) -or [string]::IsNullOrWhiteSpace($Text) }
    }
    if (-not $(Test-StringEmpty -Text $env:Hours)) {
        $Hours = $env:Hours
    }
    if (-not $(Test-StringEmpty -Text $env:Attempts)) {
        $Attempts = $env:Attempts
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Error -Message "Access Denied. Please run with Administrator privileges."
        exit 1
    }

    if ($(auditpol.exe /get /category:* | Where-Object { $_ -like "*Logon*Success and Failure" })) {
        Write-Information "Audit Policy for Logon is set to: Success and Failure"
    }
    else {
        Write-Error "Audit Policy for Logon is NOT set to: Success and Failure"
        exit 1
        # Write-Host "Setting Logon to: Success and Failure"
        # auditpol.exe /set /subcategory:"Logon" /success:enable /failure:enable
        # Write-Host "Future failed login attempts will be captured."
    }

    $StartTime = (Get-Date).AddHours(0 - $Hours)
    $EventId = 4625

    # Get failed login attempts
    try {
        $Events = Get-WinEvent -FilterHashtable @{LogName = "Security"; ID = $EventId; StartTime = $StartTime } -ErrorAction Stop | ForEach-Object {
            $Message = $_.Message -split [System.Environment]::NewLine
            $Account = $($Message | Where-Object { $_ -Like "*Account Name:*" }) -split 's+' | Select-Object -Last 1
            [int]$LogonType = $($Message | Where-Object { $_ -Like "Logon Type:*" }) -split 's+' | Select-Object -Last 1
            $SourceNetworkAddress = $($Message | Where-Object { $_ -Like "*Source Network Address:*" }) -split 's+' | Select-Object -Last 1
            [PSCustomObject]@{
                Account              = $Account
                LogonType            = $LogonType
                SourceNetworkAddress = $SourceNetworkAddress
            }
        } | Where-Object { $_.LogonType -in @(2, 7, 10) }
    }
    catch {
        if ($_.Exception.Message -like "No events were found that match the specified selection criteria.") {
            Write-Host "No failed logins found in the past $Hours hour(s)."
            exit 0
        }
        else {
            Write-Error $_
            exit 1
        }
    }

    # Build a list of accounts 
    $UsersAccounts = [System.Collections.Generic.List[String]]::new()
    try {
        $ErrorActionPreference = "Stop"
        Get-LocalUser | Select-Object -ExpandProperty Name | ForEach-Object { $UsersAccounts.Add($_) }
        $ErrorActionPreference = "Continue"
    }
    catch {
        $NetUser = net.exe user
        $(
            $NetUser | Select-Object -Skip 4 | Select-Object -SkipLast 2
            # Join each line with a ","
            # Replace and spaces with a ","
            # Split everything by ","
        ) -join ',' -replace 's+', ',' -split ',' |
            # Sort and remove any duplicates
            Sort-Object -Descending -Unique |
            # Filter out empty strings
            Where-Object { -not [string]::IsNullOrEmpty($_) -and -not [string]::IsNullOrWhiteSpace($_) } |
            ForEach-Object {
                $UsersAccounts.Add($_)
            }
    }
    $Events | Select-Object -ExpandProperty Account | ForEach-Object { $UsersAccounts.Add($_) }

    $Results = $UsersAccounts | Select-Object -Unique | ForEach-Object {
        $Account = $_
        $AccountEvents = $Events | Where-Object { $_.Account -like $Account }
        $AttemptCount = $AccountEvents.Count
        $SourceNetworkAddress = $AccountEvents | Select-Object -ExpandProperty SourceNetworkAddress -Unique
        if ($AttemptCount -gt 0) {
            [PSCustomObject]@{
                Account              = $Account
                Attempts             = $AttemptCount
                SourceNetworkAddress = $SourceNetworkAddress
            }
        }
    }

    # Get only the accounts with fail login attempts at or over $Attempts
    $BruteForceAttempts = $Results | Where-Object { $_.Attempts -ge $Attempts }
    if ($BruteForceAttempts) {
        $BruteForceAttempts | Out-String | Write-Host
        exit 1
    }
    $Results | Out-String | Write-Host
    exit 0
}
end {
    $ScriptVariables = @(
        [PSCustomObject]@{
            name           = "Hours"
            calculatedName = "hours" # Must be lowercase and no spaces
            required       = $false
            defaultValue   = [PSCustomObject]@{ # If not default value, then remove
                type  = "TEXT"
                value = "1"
            }
            valueType      = "TEXT"
            valueList      = $null
            description    = "Number of hours back in time to look through in the event log."
        }
        [PSCustomObject]@{
            name           = "Attempts"
            calculatedName = "attempts" # Must be lowercase and no spaces
            required       = $false
            defaultValue   = [PSCustomObject]@{ # If not default value, then remove
                type  = "TEXT"
                value = "8"
            }
            valueType      = "TEXT"
            valueList      = $null
            description    = "Number of login attempts to trigger at or above this number."
        }
    )
}

|

#Requires -Version 5.1

<#
.SYNOPSIS
    Condition for helping detect brute force login attempts.
.DESCRIPTION
    Condition for helping detect brute force login attempts.
.EXAMPLE
     -Hours 10
    Number of hours back in time to look through in the event log.
    Default is 1 hour.
.EXAMPLE
    -Attempts 100
    Number of login attempts to trigger at or above this number.
    Default is 8 attempts.
.OUTPUTS
    PSCustomObject[]
.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()]
    [int]
    $Hours = 1,
    [Parameter()]
    [int]
    $Attempts = 8
)

begin {
    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }
    function Test-StringEmpty {
        param([string]$Text)
        # Returns true if string is empty, null, or whitespace
        process { [string]::IsNullOrEmpty($Text) -or [string]::IsNullOrWhiteSpace($Text) }
    }
    if (-not $(Test-StringEmpty -Text $env:Hours)) {
        $Hours = $env:Hours
    }
    if (-not $(Test-StringEmpty -Text $env:Attempts)) {
        $Attempts = $env:Attempts
    }
}
process {
    if (-not (Test-IsElevated)) {
        Write-Error -Message "Access Denied. Please run with Administrator privileges."
        exit 1
    }

    if ($(auditpol.exe /get /category:* | Where-Object { $_ -like "*Logon*Success and Failure" })) {
        Write-Information "Audit Policy for Logon is set to: Success and Failure"
    }
    else {
        Write-Error "Audit Policy for Logon is NOT set to: Success and Failure"
        exit 1
        # Write-Host "Setting Logon to: Success and Failure"
        # auditpol.exe /set /subcategory:"Logon" /success:enable /failure:enable
        # Write-Host "Future failed login attempts will be captured."
    }

    $StartTime = (Get-Date).AddHours(0 - $Hours)
    $EventId = 4625

    # Get failed login attempts
    try {
        $Events = Get-WinEvent -FilterHashtable @{LogName = "Security"; ID = $EventId; StartTime = $StartTime } -ErrorAction Stop | ForEach-Object {
            $Message = $_.Message -split [System.Environment]::NewLine
            $Account = $($Message | Where-Object { $_ -Like "*Account Name:*" }) -split 's+' | Select-Object -Last 1
            [int]$LogonType = $($Message | Where-Object { $_ -Like "Logon Type:*" }) -split 's+' | Select-Object -Last 1
            $SourceNetworkAddress = $($Message | Where-Object { $_ -Like "*Source Network Address:*" }) -split 's+' | Select-Object -Last 1
            [PSCustomObject]@{
                Account              = $Account
                LogonType            = $LogonType
                SourceNetworkAddress = $SourceNetworkAddress
            }
        } | Where-Object { $_.LogonType -in @(2, 7, 10) }
    }
    catch {
        if ($_.Exception.Message -like "No events were found that match the specified selection criteria.") {
            Write-Host "No failed logins found in the past $Hours hour(s)."
            exit 0
        }
        else {
            Write-Error $_
            exit 1
        }
    }

    # Build a list of accounts 
    $UsersAccounts = [System.Collections.Generic.List[String]]::new()
    try {
        $ErrorActionPreference = "Stop"
        Get-LocalUser | Select-Object -ExpandProperty Name | ForEach-Object { $UsersAccounts.Add($_) }
        $ErrorActionPreference = "Continue"
    }
    catch {
        $NetUser = net.exe user
        $(
            $NetUser | Select-Object -Skip 4 | Select-Object -SkipLast 2
            # Join each line with a ","
            # Replace and spaces with a ","
            # Split everything by ","
        ) -join ',' -replace 's+', ',' -split ',' |
            # Sort and remove any duplicates
            Sort-Object -Descending -Unique |
            # Filter out empty strings
            Where-Object { -not [string]::IsNullOrEmpty($_) -and -not [string]::IsNullOrWhiteSpace($_) } |
            ForEach-Object {
                $UsersAccounts.Add($_)
            }
    }
    $Events | Select-Object -ExpandProperty Account | ForEach-Object { $UsersAccounts.Add($_) }

    $Results = $UsersAccounts | Select-Object -Unique | ForEach-Object {
        $Account = $_
        $AccountEvents = $Events | Where-Object { $_.Account -like $Account }
        $AttemptCount = $AccountEvents.Count
        $SourceNetworkAddress = $AccountEvents | Select-Object -ExpandProperty SourceNetworkAddress -Unique
        if ($AttemptCount -gt 0) {
            [PSCustomObject]@{
                Account              = $Account
                Attempts             = $AttemptCount
                SourceNetworkAddress = $SourceNetworkAddress
            }
        }
    }

    # Get only the accounts with fail login attempts at or over $Attempts
    $BruteForceAttempts = $Results | Where-Object { $_.Attempts -ge $Attempts }
    if ($BruteForceAttempts) {
        $BruteForceAttempts | Out-String | Write-Host
        exit 1
    }
    $Results | Out-String | Write-Host
    exit 0
}
end {
    $ScriptVariables = @(
        [PSCustomObject]@{
            name           = "Hours"
            calculatedName = "hours" # Must be lowercase and no spaces
            required       = $false
            defaultValue   = [PSCustomObject]@{ # If not default value, then remove
                type  = "TEXT"
                value = "1"
            }
            valueType      = "TEXT"
            valueList      = $null
            description    = "Number of hours back in time to look through in the event log."
        }
        [PSCustomObject]@{
            name           = "Attempts"
            calculatedName = "attempts" # Must be lowercase and no spaces
            required       = $false
            defaultValue   = [PSCustomObject]@{ # If not default value, then remove
                type  = "TEXT"
                value = "8"
            }
            valueType      = "TEXT"
            valueList      = $null
            description    = "Number of login attempts to trigger at or above this number."
        }
    )
}

 


Accédez à plus de 700 scripts dans le Dojo NinjaOne

Obtenez l’accès

Comprendre et utiliser le script

Notre script repose sur deux paramètres principaux : Heure (-Hours) et Tentatives (-Attempts). Le paramètre `-Hours` détermine la période à examiner dans le journal des événements (1 heure par défaut). Le paramètre `-Attempts` définit le seuil de tentatives de connexion avant de déclencher une alerte (la valeur par défaut est de 8 tentatives).

Pour installer et exécuter le script, procédez comme suit :

  1. Ouvrez PowerShell avec les privilèges de l’administrateur.
  2. Copiez le script dans votre environnement PowerShell.
  3. Personnalisez les paramètres `-Hours` et `-Attempts` selon vos besoins.
  4. Exécutez le script.

Le script évalue ensuite les journaux d’événements en fonction des paramètres fournis. Si le nombre de tentatives de connexion infructueuses dépasse le seuil fixé dans le délai spécifié, le système vous avertit d’une éventuelle attaque par force brute. 

Prenons l’exemple d’une personne qui souhaite surveiller les tentatives de connexion au cours des trois dernières heures et qui souhaite recevoir une alerte si plus de 15 tentatives échouent. Avec NinjaOne, vous avez la possibilité de personnaliser votre approche de la sécurité en exécutant le script avec des paramètres personnalisés tels que -Hours 3 -Attempts 15. Cette fonction vous permet de vous adapter aux besoins de sécurité uniques et aux profils de risque de votre entreprise.

Mesures de sécurité supplémentaires

La détection des attaques par force brute n’est qu’un élément d’une stratégie globale de cybersécurité. D’autres mesures cruciales sont nécessaires : 

  1. Des mots de passe forts : Encouragez les utilisateurs à créer des mots de passe robustes et uniques; idéalement un mélange de lettres, de chiffres et de symboles. Les gestionnaires de mots de passe peuvent faciliter la la gestion de mots de passe complexes.
  1. Authentification multifactorielle (AMF) : L’AMF fournit une couche supplémentaire de sécurité, exigeant des utilisateurs qu’ils vérifient leur identité à l’aide de deux mécanismes ou plus (par exemple, quelque chose qu’ils connaissent, quelque chose qu’ils ont, ou quelque chose qu’ils sont).
  1. Mises à jour logiciel : La mise à jour régulière des logiciels est essentielle. Les mises à jour comprennent souvent des correctifs pour les failles de sécurité qui, si elles ne sont pas corrigées, peuvent ouvrir la voie à des cyber-attaques.
  1. Formation des employés: La mise en place d’un programme de sensibilisation à la sécurité permet d’informer les employés sur les cybermenaces et sur le rôle qu’ils jouent dans le maintien de la sécurité. Le facteur humain est souvent le maillon faible de la cybersécurité, et des employés bien informés peuvent renforcer considérablement vos défenses.

Conclusions

Une détection efficace de la force brute est cruciale dans le paysage numérique actuel, et notre script PowerShell offre une solution puissante et personnalisable. Cependant, il est essentiel de se rappeler qu’il fait partie d’une stratégie de cybersécurité plus large. En associant la détection en temps réel à des mots de passe forts, à l’AMF, à des mises à jour logicielles et à la formation des employés, vous pouvez créer un protocole de sécurité complet pour protéger vos actifs numériques. 

NinjaOne est un outil complet qui renforce considérablement votre capacité à détecter et à contrer les attaques par force brute. Avec un un contrôle total sur la sécurité des terminaux vous pouvez gérer les applications, modifier les registres à distance et déployer des scripts pour renforcer la sécurité. Les contrôles d’accès basés sur les rôles garantissent que vos techniciens n’ont que les niveaux d’accès nécessaires, réduisant ainsi les points d’intrusion potentiels. La plateforme offre également des outils de gestion du cryptage des lecteurs et la possibilité d’installer et de gérer automatiquement la protection des terminaux, ce qui permet un contrôle précis des opérations antivirus. 

De plus, la fonction d’échange d’informations d’identification de NinjaOne protège les informations d’identification, et constitue une ligne de défense cruciale contre les attaques par force brute. Il permet également d’identifier et de supprimer les terminaux malveillants, ce qui ajoute une couche de protection supplémentaire. N’attendez pas qu’une intrusion se produise. Commencez dès aujourd’hui votre périple vers une sécurité renforcée dès maintenant avec NinjaOne. 

Pour aller plus loin

Pour créer une équipe informatique efficace et performante, il est essentiel d'avoir une solution centralisée qui joue le rôle de nœud principal pour 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 profitez d'un 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)).