Public/Export-SigmaRule.ps1
<#
.SYNOPSIS sigmadb - Export sigma rule from db .DESCRIPTION Exports single (or all) sigma rules either to a YAML file or directly to Elastic ndjson (sigma repo required) .EXAMPLE PS C:\> Export-SigmaRule -Id 1 -Destination .\export\ -Database .\test.db -SigmaRepo .\sigma Exports Rule 1 (with exceptions) to .\export\[filename] from .\test.db database as sigma rule file (.yml) Sigma Repo is needed for converting rules with multiple yaml documents .INPUTS Id: Optional sigma rule id Destination: Destination folder for export Database: Path to database file SigmaRepo: Path to sigma repo Elastic: Switch for converting directly to elastic rule (needs sigmac) BackendConfig: Path to sigmac (elasticsearch) backend config file .OUTPUTS None .NOTES Author: ncrqnt Date: 08.09.2021 PowerShell: 7.1.4 Changelog: 1.3.0 22.09.2021 ncrqnt Added usage of config file in private function Changed call of SigmaDB class 1.2.1 16.09.2021 ncrqnt Restructure of config file 1.2.0 15.09.2021 ncrqnt Added support for exporting disabled rules 1.1.0 13.09.2021 ncrqnt Changed Database parameter to Config 1.0.0 08.09.2021 ncrqnt Initial creation #> function Export-SigmaRule { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'medium')] param ( [Parameter(Mandatory = $false, ParameterSetName='sigma')] [Parameter(Mandatory = $false, ParameterSetName='elastic')] [string]$Id, [Parameter(Mandatory = $false, ParameterSetName='sigma')] [Parameter(Mandatory = $false, ParameterSetName='elastic')] [string]$Destination, [Parameter(Mandatory = $false, ParameterSetName='sigma')] [Parameter(Mandatory = $false, ParameterSetName='elastic')] [string]$Config = '.\sigmadb\config.yml', [Parameter(Mandatory = $false, ParameterSetName='sigma')] [Parameter(Mandatory = $false, ParameterSetName='elastic')] [switch]$NoProgressBar, [Parameter(Mandatory = $false, ParameterSetName='sigma')] [Parameter(Mandatory = $false, ParameterSetName='elastic')] [switch]$ExcludeDisabled, [Parameter(Mandatory = $true, ParameterSetName='elastic')] [switch]$Elastic, [Parameter(Mandatory = $true, ParameterSetName='elastic')] [ValidateScript( { if (Test-Path $_ -PathType Container) { $true } else { throw "$_ is not a directory." } })] [string]$SigmaRepo, [Parameter(Mandatory = $false, ParameterSetName='elastic')] [ValidateScript( { if (Test-Path $_ -PathType Leaf) { $true } else { throw "$_ not found or not a file." } })] [string]$BackendConfig, [Parameter(Mandatory = $false, ParameterSetName='elastic')] [pscredential]$Credential ) begin { $cfg = Get-PrivSigmaConfig -Config $Config $db = New-Object -TypeName SigmaDB -ArgumentList $cfg.Files.Database if (-not $Destination) { $Destination = $cfg.Folders.Exports } if (-not $Credential) { if ($cfg.ExportToElastic.Enabled -and $Elastic) { Write-Warning "Export to Elastic was enabled in the config.yml" $Credential = Get-Credential -Message "Please enter credential for $($cfg.ExportToElastic.URL)" -Title "Elastic Credential Request" } } if (-not (Test-Path $Destination -PathType Container)) { Write-Error "Destination '$Destination' does not exist or is not a folder" -ErrorAction Stop return } else { Get-ChildItem $Destination -Recurse | ForEach-Object { Remove-Item $_ | Out-Null } } } process { if ($Elastic) { # Update MITRE tactics and techniques in sigma repo $here = (Get-Location).Path Set-Location "$SigmaRepo\tools\config\mitre" python.exe update_mitre.py | Out-Null Set-Location $here } if ($Id) { # single rule $rule = $db.Query("SELECT * FROM rule WHERE id = @id", @{ id = $Id })[0] if ($rule.is_enabled -eq 0 -and $ExcludeDisabled) { Write-Verbose "Rule '$Id' is disabled and -ExcludeDisabled was passed. Skipping rule." } elseif ($rule.Count -gt 0) { Export-PrivSigmaRule -Rule $rule -Destination $Destination -Database $db -Config $cfg -SigmaRepo:$SigmaRepo -Elastic:$Elastic -BackendConfig:$BackendConfig Write-Output "Rule exported: '$($rule.title)'" } else { Write-Warning "No rule with id '$Id' found." } } else { $rules = $db.Query("SELECT * FROM rule") if ($rules.Count -gt 0) { $i = 1 foreach ($rule in $rules) { if ($rule.is_enabled -eq 0 -and $ExcludeDisabled) { Write-Verbose "Rule '$Id' is disabled and -ExcludeDisabled was passed. Skipping rule." } else { $max = $rules.Count $num = "{0:d$(([string]$max).Length)}" -f $i $percent = 100 / $max * $i $name = $rule.title if (-not $NoProgressBar) { Write-Progress -Activity "Exporting" -Status "$num / $max completed" -PercentComplete $percent -CurrentOperation "Rule: $name" } else { Write-Output "[$num/$max] $name" } Export-PrivSigmaRule -Rule $rule -Destination $Destination -Database $db -SigmaRepo $SigmaRepo -Config $cfg -Elastic:$Elastic -BackendConfig:$BackendConfig $i++ } } } else { Write-Warning "No rules in database '$($cfg.Files.Database)' found." } } if ($cfg.ExportToElastic.Enabled -and $Elastic) { $importFile = "$Destination\rule_import.ndjson" $parameters = @{ Method = 'Post' Uri = "$($cfg.ExportToElastic.URL)/api/detection_engine/rules/_import?overwrite=true" Headers = @{'kbn-xsrf' = 'randombullshitgo' } ContentType = 'multipart/form-data' Form = @{file = Get-Item $importFile } Credential = $Credential Authentication = 'Basic' } if ($PSCmdlet.ShouldProcess("'$importFile' ($((Get-Content $importFile -Encoding utf8).Length) rules)", "Upload to Elastic ($($cfg.ExportToElastic.URL))")) { Invoke-RestMethod @parameters } } } end { $db.Close() } } |