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
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 :
- Ouvrez PowerShell avec les privilèges de l’administrateur.
- Copiez le script dans votre environnement PowerShell.
- Personnalisez les paramètres `-Hours` et `-Attempts` selon vos besoins.
- 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 :
- 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.
- 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).
- 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.
- 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.