MISP.Tools.psm1
# Trust all certificates Function Enable-TrustAllCertsPolicy { <# .SYNOPSIS Trust all SSL certificates even if self-signed, and set protocol to Tls 1.2. #> [CmdletBinding()] Param( [Parameter(Mandatory=$False)] [switch]$NoValidateSsl ) $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($me): Setting TLS Preferences with Certificate Validation: $(!$NoValidateSsl)" # Establish Certification Policy Exception $PSDesktopException = @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ # Set PowerShell to TLS1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 if ($NoValidateSsl) { if ($PSEdition -ne 'Core'){ if (-Not ("TrustAllCertsPolicy" -as [type])) { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy is not enabled. Enabling." Add-Type $PSDesktopException try { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } catch { throw [Exception] ` "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update System.Net.ServicePointManager::CertificatePolicy to new TrustAllCertsPolicy" } } } else { # Set session default for Invoke-RestMethod and Invoke-WebRequest to SkipCertificateCheck if (-Not $PSDefaultParameterValues.ContainsKey('Invoke-RestMethod:SkipCertificateCheck')) { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: PowerShell Core - Invoke-RestMethod SkipCertificateCheck set to true" Try { $PSDefaultParameterValues.Add("Invoke-RestMethod:SkipCertificateCheck",$true) } Catch { throw [Exception] ` "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update PSDefaultParameterValues Invoke-RestMethod:SkipCertificateCheck to value true" } } } } else { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy set as Not Required." } } # Private Function Example - Replace With Your Function function Invoke-MispRestMethod { <# .SYNOPSIS Invoke a call to the MISP REST API .DESCRIPTION Invoke a call to the MISP REST API This function is intended to abstract the call to Invoke-RestMethod and supply the required details for a MISP call .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Method Base API URL for the API Calls to MISP .PARAMETER NoValidateSsl Don't validate the SSL Certificate .INPUTS [PSCredential] -> Credential [System.Uri.Builder] -> BaseUri [Switch] -> NoValidateSsl [string] -> Method (Post, Get, Put, Delete) .OUTPUTS [PSCustomObject] -> MISP Context .EXAMPLE PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com' .LINK https://url.to.repo/repo/path/ #> [CmdletBinding()] param( [Parameter(Mandatory=$true, HelpMessage = 'MISP Context to use. A MISP Context includes both the BaseUri as well as the API Key')] [PSCustomObject] $Context, [Parameter(Mandatory=$true, HelpMessage = 'Full URI to requested REST Resource as String or UriBuilder object')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder]$Uri, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, HelpMessage = 'PsCustomObject containing data that will be sent as the Json Body')] [PsCustomObject] $Body ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Invoking MISP REST API with URI $($Uri)" $Headers = @{ "Content-Type" = "application/json" "Accept" = "application/json" "Authorization" = $Context.Credential.GetNetworkCredential().Password } } Process { # Set SSL Preferences/Certificate Trust Policy Enable-TrustAllCertsPolicy -NoValidateSsl:$Context.NoValidateSsl # Build list of parameters to pass to Invoke-RestMethod $Request = @{ Method = $Method Uri = $Uri.ToString() Headers = $Headers } # Add body if supplied if ($MyInvocation.BoundParameters.ContainsKey("Body")) { $Request.Add('Body', ($Body | ConvertTo-Json -Depth 10)) } # There's some altered handling of "Content-Type" Header in some PowerShell 7.2 releases where ContentType Parameter has to be passed instead or in addition if (($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -ge 2) -or ($PSVersionTable.PSVersion.Major -ge 8)) { $Request.Add("ContentType", "application/json") } $Response = Invoke-RestMethod @Request Write-Output $Response } End { } } # Set MISP API Key/URI Context function Get-MispAttribute { <# .SYNOPSIS Get MISP Attribute(s) .DESCRIPTION Get MISP Attribute(s) If no Attribute ID is supplied, all attributes are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Attributes .EXAMPLE PS> $Attributes = Get-MispAttribute -Context $MispContext Return all Attributes .EXAMPLE PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234 Return details for attribute 1234 .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$false)] [Int]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Attribute(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.Attribute } else { # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function New-MispAttribute { <# .SYNOPSIS Construct a new MISP Attribute(s) .DESCRIPTION Construct a new MISP Attribute(s) The Attribute will be able to be added to an new Event, or added to an existing Event .PARAMETER Category The category that the Attribute belogs to. One of: - Internal feference - Targeting data - Antivirus detection - Payload delivery - Artifacts dropped - Payload installation - Persistence mechanism - Network activity - Payload type - Attribution - External analysis - Financial fraud - Support Tool - Social network - Person - Other .PARAMETER Type The type of Attribute. There are many types to choose from. Common types include: - md5 - sha1 - sha256 - ip-src - ip-dst - hostname - domain - url - user-agent - port - comment - other .INPUTS [PsCustomObject] -> Context [string] -> Category [string] -> Type .OUTPUTS [Array] -> Array of Attributes .EXAMPLE PS> $Attributes = Get-MispAttribute -Context $MispContext Return all Attributes .EXAMPLE PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234 Return details for attribute 1234 .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$false)] [ValidateSet("Internal reference", "Targeting data", "Antivirus detection", "Payload delivery", "Artifacts dropped", "Payload installation", "Persistence mechanism", "Network activity", ` "Payload type", "Attribution", "External analysis", "Financial fraud", "Support Tool", "Social network", "Person", "Other")] [string]$Category = "Other", [Parameter(Mandatory=$true)] [ValidateSet("md5", "sha1", "sha256", "filename", "pdb", "filename|md5", "filename|sha1", "filename|sha256", "ip-src", "ip-dst", "hostname", "domain", "domain|ip", "email", "email-src", ` "eppn", "email-dst", "email-subject", "email-attachment", "email-body", "float", "git-commit-id", "url", "http-method", "user-agent", "ja3-fingerprint-md5", "jarm-fingerprint", ` "favicon-mmh3", "hassh-md5", "hasshserver-md5", "regkey", "regkey|value", "AS", "snort", "bro", "zeek", "community-id", "pattern-in-file", "pattern-in-traffic", "pattern-in-memory", ` "pattern-filename", "pgp-public-key", "pgp-private-key", "yara", "stix2-pattern", "sigma", "gene", "kusto-query", "mime-type", "identity-card-number", "cookie", "vulnerability", "cpe", "weakness", "attachment", "malware-sample", "link", "comment", "text", "hex", "other", "named pipe", "mutex", "process-state", "target-user", "target-email", "target-machine", "target-org", "target-location", "target-external", "btc", "dash", "xmr", "iban", "bic", "bank-account-nr", "aba-rtn", "bin", "cc-number", "prtn", "phone-number", "threat-actor", "campaign-name", "campaign-id", ` "malware-type", "uri", "authentihash", "vhash", "ssdeep", "imphash", "telfhash", "pehash", "impfuzzy", "sha224", "sha384", "sha512", "sha512/224", "sha512/256", "sha3-224", "sha3-256", "sha3-384", ` "sha3-512", "tlsh", "cdhash", "filename|authentihash", "filename|vhash", "filename|ssdeep", "filename|imphash", "filename|impfuzzy", "filename|pehash", "filename|sha224", "filename|sha384", ` "filename|sha512", "filename|sha512/224", "filename|sha512/256", "filename|sha3-224", "filename|sha3-256", "filename|sha3-384", "filename|sha3-512", "filename|tlsh", "windows-scheduled-task", ` "windows-service-name", "windows-service-displayname", "whois-registrant-email", "whois-registrant-phone", "whois-registrant-name", "whois-registrant-org", "whois-registrar", "whois-creation-date", ` "x509-fingerprint-sha1", "x509-fingerprint-md5", "x509-fingerprint-sha256", "dns-soa-email", "size-in-bytes", "counter", "datetime", "port", "ip-dst|port", "ip-src|port", "hostname|port", "mac-address", ` "mac-eui-64", "email-dst-display-name", "email-src-display-name", "email-header", "email-reply-to", "email-x-mailer", "email-mime-boundary", "email-thread-index", "email-message-id", "github-username", ` "github-repository", "github-organisation", "jabber-id", "twitter-id", "dkim", "dkim-signature", "first-name", "middle-name", "last-name", "full-name", "date-of-birth", "place-of-birth", "gender", ` "passport-number", "passport-country", "passport-expiration", "redress-number", "nationality", "visa-number", "issue-date-of-the-visa", "primary-residence", "country-of-residence", "special-service-request", ` "frequent-flyer-number", "travel-details", "payment-details", "place-port-of-original-embarkation", "place-port-of-clearance", "place-port-of-onward-foreign-destination", "passenger-name-record-locator-number", ` "mobile-application-id", "chrome-extension-id", "cortex", "boolean", "anonymised")] [string] $Type, [Parameter(Mandatory=$false)] [ValidateSet("Organisation","Community","Connected","All","Group","Inherit")] [string]$Distribution = "Organisaton", [Parameter(Mandatory=$true)] [string] $Comment, [Parameter(Mandatory=$true)] [string] $Value ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Build New MISP Attribute(s)" $DistributionMap = @{ Organisation = 0 # Your organisation only Community = 1 # This community only Connected = 2 # Connected Communities All = 3 # All communities Group = 4 # Sharing Group Inherit = 5 # Inherit Event } } Process { # Build the Attribute Body $Attribute = @{ type = $Type category = $Category distribution = $DistributionMap.($Distribution) to_ids = $true comment = $Comment value = $Value } # Return all fo the events Write-Output $Attribute } End { } } # Set MISP API Key/URI Context function Search-MispAttribute { <# .SYNOPSIS Search for MISP Event(s) .DESCRIPTION Search for MISP Event(s) If no Event ID is supplied, the first 100 events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Search JSON containing search criteria As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this $Search = @{ returnFormat = "json" type = @{ OR = @("ip-src", "ip-dst") } tags = @{ NOT = @("tlp:red") OR = @("tlp:%") } } Refer to MISP Automation -> Search for further details on available search criteria .INPUTS [PsCustomObject] -> Context [hastable] -> Search .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Search = @{ returnFormat = "json" value = 8.8.8.8 } PS> $Attributes = Search-MispAttribute -Context $MispContext -Search $Search Return matching attributes .LINK https://www.circl.lu/doc/misp/automation/#search https://url.to.repo/repo/path/ #> [CmdletBinding( SupportsShouldProcess, ConfirmImpact="Low" )] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [hashtable]$Search ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Search for MISP Attributes(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes/restSearch") } Process { if ($PSCmdlet.ShouldProcess("Search MISP Attributes")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body ($Search | ConvertTo-Json -Depth 20) -Method 'POST' # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function Get-MispEvent { <# .SYNOPSIS Get MISP Event(s) .DESCRIPTION Get MISP Event(s) If no Event ID is supplied, all events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = Get-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = Get-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$false)] [Int]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, $Id) } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.Event } else { # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function New-MispEvent { <# .SYNOPSIS Create a new MISP Event .DESCRIPTION Create a new MISP Event Take the provided details and use these to create a new Event in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Event Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [string]$Info, [Parameter(Mandatory=$true)] [ValidateSet("High","Medium","Low","Undefined")] [string]$ThreatLevel, [Parameter(Mandatory=$false)] [ValidateSet("Initial","Ongoing","Complete")] [string]$Analysis = "Initial", [Parameter(Mandatory=$false)] [ValidateSet("Organisation","Community","Connected","All","Group","Inherit")] [string]$Distribution = "Organisaton", [Parameter(Mandatory=$false)] [System.Boolean]$Published = $false, [Parameter(Mandatory=$false)] [array] $Attribute ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Create new MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events") $ThreatLevelMap = @{ High = 1 Medium = 2 Low = 3 Undefined = 4 } $AnalysisMap = @{ Initial = 0 Ongoing = 1 Complete = 2 } $DistributionMap = @{ Organisation = 0 # Your organisation only Community = 1 # This community only Connected = 2 # Connected Communities All = 3 # All communities Group = 4 # Sharing Group Inherit = 5 # Inherit Event } } Process { # Build up the Event Body $EventBody = [pscustomobject]@{ date = (Get-Date).ToString("yyyy-MM-dd") threat_level_id = $ThreatLevelMap.($ThreatLevel) info = $Info published = $Published analysis = $AnalysisMap.($Analysis) distribution = $DistributionMap.($Distribution) } # If attributes were supplied, add these too if($MyInvocation.BoundParameters.ContainsKey("Attribute")) { $EventBody | Add-Member -MemberType NoteProperty -Name 'Attribute' -Value $Attribute } Write-Debug "Event Body:`n$($EventBody | ConvertTo-Json -Depth 10)" # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" -Body $EventBody Write-Output $Response.Event } End { } } # Set MISP API Key/URI Context function Search-MispEvent { <# .SYNOPSIS Search for MISP Event(s) .DESCRIPTION Search for MISP Event(s) If no Event ID is supplied, the first 100 events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Search JSON containing search criteria As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this $Search = @{ returnFormat = "json" type = @{ OR = @("ip-src", "ip-dst") } tags = @{ NOT = @("tlp:red") OR = @("tlp:%") } } Refer to MISP Automation -> Search for further details on available search criteria .INPUTS [PsCustomObject] -> Context [hastable] -> Search .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = Search-MispEvent -Context $MispContext -Search $Search Return matching events .LINK https://www.circl.lu/doc/misp/automation/#search https://url.to.repo/repo/path/ #> [CmdletBinding( SupportsShouldProcess, ConfirmImpact="Low" )] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [hashtable]$Search ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Search for MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events/restSearch") } Process { if ($PSCmdlet.ShouldProcess("Search MISP Events")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body ($Search | ConvertTo-Json -Depth 20) -Method POST # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function New-MispContext { <# .SYNOPSIS Create a PSObject containing Credentials and Base URI for interaction with MISP .DESCRIPTION Create a PSObject containing Credentials and Base URI for interaction with MISP Credentials supplied in the form of a PSCredential Object and Base URI as either a string or Uri::Builder object .PARAMETER Credential PSCredential Object with ApiKey as Password .PARAMETER BaseUri Base API URL for the API Calls to MISP .PARAMETER NoValidateSsl Don't validate the SSL Certificate .INPUTS [PSCredential] -> Credential [System.Uri.Builder] -> BaseUri [Switch] -> NoValidateSsl .OUTPUTS [PSCustomObject] -> MISP Context .EXAMPLE PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com' .LINK https://url.to.repo/repo/path/ #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [pscredential]$Credential, [Parameter(Mandatory=$true)] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder]$BaseUri, [Parameter(Mandatory=$false)] [switch]$NoValidateSsl ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Setting credentials for MISP with Base URI: $($BaseUri). Cerificiate Validation: $(!$NoValidateSsl)" $MispContext = New-Object pscustomobject -Property @{ Credential = $Credential BaseUri = $BaseUri NoValidateSsl = [boolean]$NoValidateSsl } } Process { # Set SSL Preferences/Certificate Trust Policy Enable-TrustAllCertsPolicy Write-Output $MispContext } End { } } # Read MISP API Key/URI from configuration file function Read-MispContext { <# .SYNOPSIS Read the MISP Context from a saved preferences file .DESCRIPTION Read the MISP Context from a saved preferences file Check to see that the contents of the file matches our expectations after loading .PARAMETER Path Path containing the preferences file, defaults to users AppDataLocal Path .PARAMETER FileName Name of file to use for MISP Context, defaults to MISPContext.xml .INPUTS [String] -> Path [String] -> Filename .OUTPUTS [PSCustomObject] -> MISP Context into $Script:MispContext .EXAMPLE PS > $MispContext = Read-MispContext .EXAMPLE PS > $MispContext = Read-MispContext -Filename 'MyMISP.xml' .LINK https://url.to.repo/repo/path/ #> [CmdletBinding()] Param ( [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 0 )] [string] $Path = $Script:DefaultMispPreferencePath, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 1 )] [string] $Filename = $Script:DefaultMispPreferenceFilename ) Begin { $Me = $MyInvocation.MyCommand.Name $ContextFilename = Join-Path -Path $Path -ChildPath $Filename Write-Verbose "$($Me): Loading MISP Context from $($ContextFilename)" } Process { # Check directory exists if (!(Test-Path -Path $Path -PathType Container)) { throw "Path not found: $($Path)" } # Check file exists if (!(Test-Path -Path $ContextFilename -PathType Leaf)) { throw "Context File not found: $($ContextFilename)" } # Load file contents try { $LoadContext = Import-Clixml -Path $ContextFilename } catch { throw "Error loading context from file $($ContextFilename)" } $ContextProperties = $LoadContext | Get-Member -MemberType NoteProperty # Check if a Credential Property Exists If (!($ContextProperties.Name -contains 'Credential')){ throw "Context File did not contain a Credential Property" } # Ensure Credential Property is of the correct type If (!($LoadContext.Credential.GetType().Name -eq 'PSCredential')) { throw "Context File Credential Property is not of type [PSCredential]" } # Check if a Credential Property Exists If (!($ContextProperties.Name -contains 'BaseUri')){ throw "Context File did not contain a BaseUri Property" } # Ensure BaseUri Property is of the correct type If (!($LoadContext.BaseUri.GetType().Name -eq 'String')) { throw "Context File BaseUri Property is not of type [String]" } # Check if a NoValidateSsl Property Exists If (!($ContextProperties.Name -contains 'NoValidateSsl')){ throw "Context File did not contain a NoValidateSsl Property" } # Ensure NoValidateSsl Property is of the correct type If (!($LoadContext.NoValidateSsl.GetType().Name -eq 'boolean')) { throw "Context File NoValidateSsl Property is not of type [boolean]" } $OutputContext = [PSCustomObject]@{ Credential = [PSCredential]$LoadContext.Credential BaseUri = [System.UriBuilder]$LoadContext.BaseUri NoValidateSsl = $LoadContext.NoValidateSsl } Write-Output $OutputContext } End { } } # Save MISP API Key/URI to Configuration File function Save-MispContext { <# .SYNOPSIS Write MISP Context containing Api Key/URL out to a file for future reference .DESCRIPTION Write MISP Context containing Api Key/Url out to a file for future reference Default location is user's profile path, but can be overridden with -DestinationPath Default Filename is MISPContext.xml, but can be overridden with -Filename .PARAMETER DestinationPath Path to save MispContext to, defaults to users AppDataLocal Path on your operating system .PARAMETER FileName Name of file to use for MISP Context, defaults to MISPContext.xml .INPUTS [PSCustomObject] -> MISP Context [String] -> DestinationPath [String] -> Filename .OUTPUTS No output is expected if this succeeds .EXAMPLE Save to default location and filename PS > $MispContext | Save-MispContext .EXAMPLE Save to default location with alternate filename PS > $MispContext | Save-MispContext -Filename 'MyMISP.xml' .LINK https://url.to.repo/repo/path/ #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, Position = 0 )] [pscustomobject] $InputObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 1 )] [string] $DestinationPath = $Script:DefaultMispPreferencePath, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 2 )] [string] $Filename = $Script:DefaultMispPreferenceFilename ) Begin { $Me = $MyInvocation.MyCommand.Name $ContextFilename = Join-Path -Path $DestinationPath -ChildPath $Filename Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)" } Process { # Create the folder if it does not exist if (!(Test-Path $DestinationPath)) { if ($PSCmdlet.ShouldProcess($DestinationPath, "Create Configuration Folder")) { Write-Verbose "$($me): Configration Folder $($DestinationPath) does not exist, Creating" $Output = New-Item -ItemType Directory -Path $DestinationPath Write-Debug $Output } } # Save the file if ($PSCmdlet.ShouldProcess($ContextFilename, "Save MISP Context")) { Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)" # Ensure output is appropriately formatted $OutputObject = [PSCustomObject]@{ Credential = $InputObject.Credential BaseUri = $InputObject.BaseUri.ToString() NoValidateSsl = [bool]$InputObject.NoValidateSsl } $OutputObject | Export-Clixml $ContextFilename } } End { } } # Set MISP API Key/URI Context function Add-MispAttributeTag { <# .SYNOPSIS Add a tag to a MISP Attribute .DESCRIPTION Add a tag to a MISP Attribute An attribute may have one or more tags associated. This can assist with the filtering and grouping of attributes .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER TagId Id of tag to add to Attribute .PARAMETER EventId Id of Attribute to which tag should be added .PARAMETER Local Whether the tag should be attached locally or not to the event .INPUTS [PsCustomObject] -> Context [Int] -> TagId [Int] -> AttributeId [switch] -> Local .OUTPUTS None if successful .EXAMPLE PS> $Tags = Add-MispAttributeTag -Context $MispContext -AttributeId 1234 -TagId 69 -Local Add Tag with Id 69 to attribute Id 1234, locally .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$true )] [Int]$AttributeId, [Parameter( Mandatory=$true )] [Int]$TagId, [Parameter( Mandatory=$false )] [switch] $Local ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Tag to MISP Attribute" if($Local) { $TagLocal = 1 } else { $TagLocal = 0 } # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, ("attributes/addTag/{0}/{1}/local:{2}" -f $AttributeId, $TagId, $TagLocal)) } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' Write-Debug $Response | ConvertTo-Json -Depth 10 if (!$Response.saved) { Write-Warning "Unable to add tag to Attribute" } } End { } } # Set MISP API Key/URI Context function Add-MispEventTag { <# .SYNOPSIS Add a tag to a MISP Event .DESCRIPTION Add a tag to a MISP Event An event may have one or more tags associated. This can assist with the filtering and grouping of events .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER TagId Id of tag to add to Event .PARAMETER EventId Id of event to which tag should be added .PARAMETER Local Whether the tag should be attached locally or not to the event .INPUTS [PsCustomObject] -> Context [Int] -> TagId [Int] -> EventId [switch] -> Local .OUTPUTS None if successful .EXAMPLE PS> $Tags = Add-MispEventTag -Context $MispContext -EventId 1234 -TagId 69 -Local Add Tag with Id 69 to event Id 1234, locally .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$true )] [Int]$EventId, [Parameter( Mandatory=$true )] [Int]$TagId, [Parameter( Mandatory=$false )] [switch] $Local ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Tag to MISP Event" if($Local) { $TagLocal = 1 } else { $TagLocal = 0 } # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, ("events/addTag/{0}/{1}/local:{2}" -f $EventId, $TagId, $TagLocal)) } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' Write-Debug $Response | ConvertTo-Json -Depth 10 if (!$Response.saved) { Write-Warning "Unable to add tag to Event" } } End { } } # Set MISP API Key/URI Context function Get-MispTag { <# .SYNOPSIS Get MISP Tag(s) .DESCRIPTION Get MISP Tag(s) If no Tag ID is supplied, all tags are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific tag .PARAMETER Criteria Search criteria in SQL Match format. eg. "%banana%" .INPUTS [PsCustomObject] -> Context [Int] -> Id [string] -> Criteria .OUTPUTS [Array] -> Array of tags .EXAMPLE PS> $Tags = Get-MispTag -Context $MispContext Return all Tags .EXAMPLE PS> $Event = Get-MispTag -Context $MispContext -Id 1234 Return details for tag 1234 .EXAMPLE PS> $Event = Get-MispTag -Context $MispContext -Criteria '%banana%' Return all tags containing the text 'banana' .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding(DefaultParameterSetName='ListAll')] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$false, ParameterSetName='ById' )] [Int]$Id, [Parameter( Mandatory=$false, ParameterSetName='ByCriteria' )] [string]$Criteria ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Attribute(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "tags") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Uri.Path = [io.path]::Combine($Uri.Path, "search/$($Criteria)") } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { Write-Output $Response.Tag } else { # Return all fo the events Write-Output $Response.Tag } } End { } } # Set MISP API Key/URI Context function Get-MispWarningList { <# .SYNOPSIS Get MISP Warning List(s) .DESCRIPTION Get MISP Warning List(s) If no Warning List ID is supplied, all warning lists are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific warning list .PARAMETER Criteria Criteria to limit the set of returned Warning Lists Available Criteria are: - enabled Any criteria other than 'enabled' are ignored by the API .INPUTS [PsCustomObject] -> Context [Int] -> Id [hashtable] -> Criteria .OUTPUTS [Array] -> Array of Warning Lists .EXAMPLE PS> $WarningLists = Get-MispWarningList -Context $MispContext Return first all Warning Lists .EXAMPLE PS> $WarningLists = Get-MispWarningList -Context $MispContext -Id 1234 Return details for warning list 1234 .EXAMPLE PS> $Criteria = @{ enabled = $true } PS> $WarningLists = Get-MispWarningList -Context $MispContext -Criteria $Criteria Return Enabled Warning Lists .LINK https://url.to.repo/repo/path/ https://www.circl.lu/doc/misp/automation/#warninglists-api #> [CmdletBinding(DefaultParameterSetName='ListAll')] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$false, ParameterSetName='ById' )] [Int]$Id, [Parameter( Mandatory=$false, ParameterSetName='ByCriteria' )] [hashtable]$Criteria ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Warning List(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "warninglists") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } } Process { Write-Verbose "$($Me): Invoking with ParameterSetName: $($PSCmdlet.ParameterSetName)" # Call the API if ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' -Body ($Criteria | ConvertTo-Json) } else { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri } if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.warningList } else { # Return all fo the events Write-Output $Response.warninglists.warninglist } } End { } } Export-ModuleMember -Function Get-MispAttribute, New-MispAttribute, Search-MispAttribute, Get-MispEvent, New-MispEvent, Search-MispEvent, New-MispContext, Read-MispContext, Save-MispContext, Add-MispAttributeTag, Add-MispEventTag, Get-MispTag, Get-MispWarningList |