├── .gitattributes ├── LICENSE ├── PSCortex ├── PSCortex.psd1 └── PSCortex.psm1 ├── README.md └── images ├── PSCortex.png └── PSCortex.svg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 lahell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PSCortex/PSCortex.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PSCortex' 3 | # 4 | # Generated by: lahell 5 | # 6 | # Generated on: 22.05.2021 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'PSCortex' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '0.0.4' 16 | 17 | # Supported PSEditions 18 | CompatiblePSEditions = @('Desktop', 'Core') 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '18d2dc25-1dc2-4f40-9b34-169477de59f9' 22 | 23 | # Author of this module 24 | Author = 'lahell' 25 | 26 | # Company or vendor of this module 27 | # CompanyName = '' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2023 lahell. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Get endpoints, incidents and alerts from the Cortex XDR API' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | PowerShellVersion = '5.1' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | DotNetFrameworkVersion = '4.6.1' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = @( 73 | 'Initialize-CortexConfig', 74 | 'Get-CortexEndpointList', 75 | 'Get-CortexEndpoint', 76 | 'Remove-CortexEndpoint', 77 | 'Get-CortexIncident', 78 | 'Get-CortexIncidentExtraData', 79 | 'Get-CortexAlert', 80 | 'Get-CortexAuditAgentReport', 81 | 'Get-CortexAuditManagementLog', 82 | 'Get-CortexViolation' 83 | ) 84 | 85 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 86 | # CmdletsToExport = @() 87 | 88 | # Variables to export from this module 89 | # VariablesToExport = '*' 90 | 91 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 92 | # AliasesToExport = @() 93 | 94 | # DSC resources to export from this module 95 | # DscResourcesToExport = @() 96 | 97 | # List of all modules packaged with this module 98 | # ModuleList = @() 99 | 100 | # List of all files packaged with this module 101 | # FileList = @() 102 | 103 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 104 | PrivateData = @{ 105 | 106 | PSData = @{ 107 | 108 | # Tags applied to this module. These help with module discovery in online galleries. 109 | Tags = @('Cortex', 'XDR') 110 | 111 | # A URL to the license for this module. 112 | LicenseUri = 'https://raw.githubusercontent.com/lahell/PSCortex/master/LICENSE' 113 | 114 | # A URL to the main website for this project. 115 | ProjectUri = 'https://github.com/lahell/PSCortex' 116 | 117 | # A URL to an icon representing this module. 118 | IconUri = 'https://raw.githubusercontent.com/lahell/PSCortex/master/images/PSCortex.png' 119 | 120 | # ReleaseNotes of this module 121 | # ReleaseNotes = '' 122 | 123 | } # End of PSData hashtable 124 | 125 | } # End of PrivateData hashtable 126 | 127 | # HelpInfo URI of this module 128 | # HelpInfoURI = '' 129 | 130 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 131 | # DefaultCommandPrefix = '' 132 | 133 | } 134 | 135 | -------------------------------------------------------------------------------- /PSCortex/PSCortex.psm1: -------------------------------------------------------------------------------- 1 | #region enums 2 | enum CortexRegion { 3 | AU 4 | CA 5 | DE 6 | EU 7 | GV 8 | IN 9 | JP 10 | SG 11 | UK 12 | US 13 | } 14 | 15 | enum CortexSecurityLevel { 16 | Advanced 17 | Standard 18 | } 19 | 20 | enum CortexEndpointStatus { 21 | Connected 22 | Disconnected 23 | Lost 24 | Uninstalled 25 | } 26 | 27 | enum CortexIncidentStatus { 28 | ResolvedThreatHandled 29 | UnderInvestigation 30 | New 31 | ResolvedFalsePositive 32 | ResolvedKnownIssue 33 | ResolvedAuto 34 | ResolvedDuplicate 35 | ResolvedOther 36 | } 37 | 38 | enum CortexAlertSeverity { 39 | Low 40 | Medium 41 | High 42 | Unknown 43 | } 44 | 45 | enum CortexAuditAgentReportCategory { 46 | Status 47 | Audit 48 | Monitoring 49 | } 50 | 51 | enum CortexViolationType { 52 | CdRom 53 | DiskDrive 54 | FloppyDisk 55 | PortableDevice 56 | } 57 | #endregion 58 | 59 | #region classes 60 | class CortexConfig { 61 | [PSCredential]$Credential 62 | [CortexSecurityLevel]$SecurityLevel 63 | [String]$TenantName 64 | [CortexRegion]$Region 65 | [Uri]$BaseUri 66 | 67 | CortexConfig( 68 | [PSCredential]$Credential, 69 | [CortexSecurityLevel]$SecurityLevel, 70 | [String]$TenantName, 71 | [CortexRegion]$Region 72 | ) { 73 | $this.Credential = $Credential 74 | $this.SecurityLevel = $SecurityLevel 75 | $this.TenantName = $TenantName 76 | $this.Region = $Region 77 | $this.BaseUri = 'https://api-{0}.xdr.{1}.paloaltonetworks.com/public_api/v1' -f $TenantName.ToLower(), $Region.ToString().ToLower() 78 | } 79 | } 80 | 81 | class CortexEndpointSummary { 82 | [String]$AgentId 83 | [String]$AgentStatus 84 | [String]$HostName 85 | [String]$AgentType 86 | [IPAddress[]]$IPAddress 87 | [DateTime]$LastSeen 88 | [String[]]$Users 89 | 90 | CortexEndpointSummary( 91 | [PSCustomObject]$EndpointSummary 92 | ) { 93 | $this.AgentId = $EndpointSummary.agent_id 94 | $this.AgentStatus = (Get-Culture).TextInfo.ToTitleCase($EndpointSummary.agent_status.ToLower()) 95 | $this.HostName = $EndpointSummary.host_name 96 | $this.AgentType = $EndpointSummary.agent_type 97 | $this.IPAddress = $EndpointSummary.ip -as [IPAddress[]] 98 | $this.LastSeen = ConvertFrom-UnixTimestamp $EndpointSummary.last_seen 99 | $this.Users = $EndpointSummary.users 100 | } 101 | } 102 | 103 | class CortexEndpoint { 104 | [String]$EndpointId 105 | [String]$EndpointName 106 | [String]$EndpointType 107 | [String]$EndpointStatus 108 | [String]$OperatingSystemType 109 | [String]$OperatingSystem 110 | [Version]$OperatingSystemVersion 111 | [IPAddress[]]$IPAddress 112 | [IPAddress[]]$IPv6Address 113 | [IPAddress]$PublicIP 114 | [String[]]$Users 115 | [String]$Domain 116 | [String]$Alias 117 | [DateTime]$FirstSeen 118 | [DateTime]$LastSeen 119 | [String]$ContentVersion 120 | [String]$InstallationPackage 121 | [String]$ActiveDirectory 122 | [DateTime]$InstallDate 123 | [Version]$EndpointVersion 124 | [String]$IsIsolated 125 | [Nullable[DateTime]]$IsolatedDate 126 | [String[]]$GroupName 127 | [String]$OperationalStatus 128 | [Object[]]$OperationalStatusDescription 129 | [String]$ScanStatus 130 | [DateTime]$ContentReleaseTimestamp 131 | [DateTime]$LastContentUpdateTime 132 | [String[]]$MACAddress 133 | [String]$AssignedPreventionPolicy 134 | [String]$AssignedExtensionsPolicy 135 | [String]$ContentStatus 136 | 137 | CortexEndpoint( 138 | [PSCustomObject]$Endpoint 139 | ) { 140 | $this.EndpointId = $Endpoint.endpoint_id 141 | $this.EndpointName = $Endpoint.endpoint_name 142 | $this.EndpointType = $Endpoint.endpoint_type 143 | $this.EndpointStatus = ConvertTo-PascalCase $Endpoint.endpoint_status.ToLower() 144 | $this.OperatingSystemType = $Endpoint.os_type 145 | $this.OperatingSystem = $Endpoint.operating_system 146 | $this.OperatingSystemVersion = $Endpoint.os_version 147 | $this.IPAddress = $Endpoint.ip -as [IPAddress[]] 148 | $this.IPv6Address = $Endpoint.ipv6 -as [IPAddress[]] 149 | $this.PublicIP = $Endpoint.public_ip -as [IPAddress] 150 | $this.Users = $Endpoint.users 151 | $this.Domain = $Endpoint.domain 152 | $this.Alias = $Endpoint.Alias 153 | $this.FirstSeen = ConvertFrom-UnixTimestamp $Endpoint.first_seen 154 | $this.LastSeen = ConvertFrom-UnixTimestamp $Endpoint.last_seen 155 | $this.ContentVersion = $Endpoint.content_version 156 | $this.InstallationPackage = $Endpoint.installation_package 157 | $this.ActiveDirectory = $Endpoint.active_directory 158 | $this.InstallDate = ConvertFrom-UnixTimestamp $Endpoint.install_date 159 | $this.EndpointVersion = $Endpoint.endpoint_version 160 | $this.IsIsolated = $Endpoint.is_isolated 161 | $this.IsolatedDate = ConvertFrom-UnixTimestamp $Endpoint.isolated_date 162 | $this.GroupName = $Endpoint.group_name 163 | $this.OperationalStatus = $Endpoint.operational_status 164 | $this.OperationalStatusDescription = $Endpoint.operational_status_description 165 | $this.ScanStatus = $Endpoint.scan_status 166 | $this.ContentReleaseTimestamp = ConvertFrom-UnixTimestamp $Endpoint.content_release_timestamp 167 | $this.LastContentUpdateTime = ConvertFrom-UnixTimestamp $Endpoint.last_content_update_time 168 | $this.MACAddress = $Endpoint.mac_address 169 | $this.AssignedPreventionPolicy = $Endpoint.assigned_prevention_policy 170 | $this.AssignedExtensionsPolicy = $Endpoint.assigned_extensions_policy 171 | $this.ContentStatus = $Endpoint.content_status 172 | } 173 | } 174 | 175 | class CortexIncident { 176 | [Int]$IncidentId 177 | [String]$IncidentName 178 | [DateTime]$CreationTime 179 | [DateTime]$ModificationTime 180 | [Nullable[DateTime]]$DetectionTime 181 | [String]$Status 182 | [String]$Severity 183 | [String]$Description 184 | [String]$AssignedUserMail 185 | [String]$AssignedUserPrettyName 186 | [Int]$AlertCount 187 | [Int]$LowSeverityAlertCount 188 | [Int]$MediumSeverityAlertCount 189 | [Int]$HighSeverityAlertCount 190 | [Int]$UserCount 191 | [Int]$HostCount 192 | [String]$Notes 193 | [String]$ResolveComment 194 | [String]$ManualSeverity 195 | [String]$ManualDescription 196 | [String]$XdrUrl 197 | [Boolean]$Starred 198 | [String[]]$Hosts 199 | [String[]]$Users 200 | [String[]]$IncidentSources 201 | [String]$RuleBasedScore 202 | [String]$ManualScore 203 | 204 | CortexIncident( 205 | [PSCustomObject]$Incident 206 | ) { 207 | $this.IncidentId = $Incident.incident_id 208 | $this.IncidentName = $Incident.incident_name 209 | $this.CreationTime = ConvertFrom-UnixTimestamp $Incident.creation_time 210 | $this.ModificationTime = ConvertFrom-UnixTimestamp $Incident.modification_time 211 | $this.DetectionTime = ConvertFrom-UnixTimestamp $Incident.detection_time 212 | $this.Status = ConvertTo-PascalCase $Incident.status 213 | $this.Severity = ConvertTo-PascalCase $Incident.severity 214 | $this.Description = $Incident.description 215 | $this.AssignedUserMail = $Incident.assigned_user_mail 216 | $this.AssignedUserPrettyName = $Incident.assigned_user_pretty_name 217 | $this.AlertCount = $Incident.alert_count 218 | $this.LowSeverityAlertCount = $Incident.low_severity_alert_count 219 | $this.MediumSeverityAlertCount = $Incident.med_severity_alert_count 220 | $this.HighSeverityAlertCount = $Incident.high_severity_alert_count 221 | $this.UserCount = $Incident.user_count 222 | $this.HostCount = $Incident.host_count 223 | $this.Notes = $Incident.notes 224 | $this.ResolveComment = $Incident.resolve_comment 225 | $this.ManualSeverity = $Incident.manual_severity 226 | $this.ManualDescription = $Incident.manual_description 227 | $this.XdrUrl = $Incident.xdr_url 228 | $this.Starred = $Incident.starred 229 | $this.Hosts = $Incident.hosts 230 | $this.Users = $Incident.users 231 | $this.IncidentSources = $Incident.incident_sources 232 | $this.RuleBasedScore = $Incident.rule_based_score 233 | $this.ManualScore = $Incident.manual_score 234 | } 235 | } 236 | 237 | class CortexEvent { 238 | [String]$AgentInstallType 239 | [Nullable[DateTime]]$AgentHostBootTime 240 | [String]$EventSubType 241 | [String]$ModuleId 242 | [String]$AssociationStrength 243 | [String]$DstAssociationStrength 244 | [String]$StoryId 245 | [String]$EventId 246 | [String]$EventType 247 | [DateTime]$EventTimestamp 248 | [String]$ActorProcessInstanceId 249 | [String]$ActorProcessImagePath 250 | [String]$ActorProcessImageName 251 | [String]$ActorProcessCommandLine 252 | [String]$ActorProcessSignatureStatus 253 | [String]$ActorProcessSignatureVendor 254 | [String]$ActorProcessImageSha256 255 | [String]$ActorProcessImageMd5 256 | [String]$ActorProcessCausalityId 257 | [String]$ActorCausalityId 258 | [String]$ActorProcessOsPid 259 | [String]$ActorThreadThreadId 260 | [String]$CausalityActorProcessImageName 261 | [String]$CausalityActorProcessCommandLine 262 | [String]$CausalityActorProcessImagePath 263 | [String]$CausalityActorProcessSignatureVendor 264 | [String]$CausalityActorProcessSignatureStatus 265 | [String]$CausalityActorCausalityId 266 | [String]$CausalityActorProcessExecutionTime 267 | [String]$CausalityActorProcessImageMd5 268 | [String]$CausalityActorProcessImageSha256 269 | [String]$ActionFilePath 270 | [String]$ActionFileName 271 | [String]$ActionFileMd5 272 | [String]$ActionFileSha256 273 | [String]$ActionFileMacroSha256 274 | [String]$ActionRegistryData 275 | [String]$ActionRegistryKeyName 276 | [String]$ActionRegistryValueName 277 | [String]$ActionRegistryFullKey 278 | 279 | CortexEvent( 280 | [PSCustomObject]$CortexEvent 281 | ) { 282 | $this.AgentInstallType = $CortexEvent.agent_install_type 283 | $this.AgentHostBootTime = ConvertFrom-UnixTimestamp $CortexEvent.agent_host_boot_time 284 | $this.EventSubType = $CortexEvent.event_sub_type 285 | $this.ModuleId = $CortexEvent.module_id 286 | $this.AssociationStrength = $CortexEvent.association_strength 287 | $this.DstAssociationStrength = $CortexEvent.dst_association_strength 288 | $this.StoryId = $CortexEvent.story_id 289 | $this.EventId = $CortexEvent.event_id 290 | $this.EventType = $CortexEvent.event_type 291 | $this.EventTimestamp = ConvertFrom-UnixTimestamp $CortexEvent.event_timestamp 292 | $this.ActorProcessInstanceId = $CortexEvent.actor_process_instance_id 293 | $this.ActorProcessImagePath = $CortexEvent.actor_process_image_path 294 | $this.ActorProcessImageName = $CortexEvent.actor_process_image_name 295 | $this.ActorProcessCommandLine = $CortexEvent.actor_process_command_line 296 | $this.ActorProcessSignatureStatus = $CortexEvent.actor_process_signature_status 297 | $this.ActorProcessSignatureVendor = $CortexEvent.actor_process_signature_vendor 298 | $this.ActorProcessImageSha256 = $CortexEvent.actor_process_image_sha256 299 | $this.ActorProcessImageMd5 = $CortexEvent.actor_process_image_md5 300 | $this.ActorProcessCausalityId = $CortexEvent.actor_process_causality_id 301 | $this.ActorCausalityId = $CortexEvent.actor_causality_id 302 | $this.ActorProcessOsPid = $CortexEvent.actor_process_os_pid 303 | $this.ActorThreadThreadId = $CortexEvent.actor_thread_thread_id 304 | $this.CausalityActorProcessImageName = $CortexEvent.causality_actor_process_image_name 305 | $this.CausalityActorProcessCommandLine = $CortexEvent.causality_actor_process_command_line 306 | $this.CausalityActorProcessImagePath = $CortexEvent.causality_actor_process_image_path 307 | $this.CausalityActorProcessSignatureVendor = $CortexEvent.causality_actor_process_signature_vendor 308 | $this.CausalityActorProcessSignatureStatus = $CortexEvent.causality_actor_process_signature_status 309 | $this.CausalityActorCausalityId = $CortexEvent.causality_actor_causality_id 310 | $this.CausalityActorProcessExecutionTime = $CortexEvent.causality_actor_process_execution_time 311 | $this.CausalityActorProcessImageMd5 = $CortexEvent.causality_actor_process_image_md5 312 | $this.CausalityActorProcessImageSha256 = $CortexEvent.causality_actor_process_image_sha256 313 | $this.ActionFilePath = $CortexEvent.action_file_path 314 | $this.ActionFileName = $CortexEvent.action_file_name 315 | $this.ActionFileMd5 = $CortexEvent.action_file_md5 316 | $this.ActionFileSha256 = $CortexEvent.action_file_sha256 317 | $this.ActionFileMacroSha256 = $CortexEvent.action_file_macro_sha256 318 | $this.ActionRegistryData = $CortexEvent.action_registry_data 319 | $this.ActionRegistryKeyName = $CortexEvent.action_registry_key_name 320 | $this.ActionRegistryValueName = $CortexEvent.action_registry_value_name 321 | $this.ActionRegistryFullKey = $CortexEvent.action_registry_full_key 322 | } 323 | } 324 | 325 | class CortexAlert { 326 | [String]$ExternalId 327 | [String]$Severity 328 | [String]$MatchingStatus 329 | [Nullable[DateTime]]$EndMatchAttemptTimestamp 330 | [DateTime]$LocalInsertTimestamp 331 | [String]$BiocIndicator 332 | [String]$MatchingServiceRuleId 333 | [Int]$AttemptCounter 334 | [String]$BiocCategoryEnumKey 335 | [Boolean]$IsWhitelisted 336 | [Boolean]$Starred 337 | [String]$DeduplicateTokens 338 | [String]$FilterRuleId 339 | [String]$MitreTechniqueIdAndName 340 | [String]$MitreTacticIdAndName 341 | [String]$AgentVersion 342 | [String]$AgentDeviceDomain 343 | [String]$AgentFqdn 344 | [String]$AgentOsType 345 | [String]$AgentOsSubType 346 | [String]$AgentDataCollectionStatus 347 | [String]$Mac 348 | [CortexEvent[]]$Events 349 | [Int]$AlertId 350 | [DateTime]$DetectionTimestamp 351 | [String]$Name 352 | [String]$Category 353 | [String]$EndpointId 354 | [String]$Description 355 | [IPAddress[]]$HostIp 356 | [String]$HostName 357 | [String[]]$MacAddresses 358 | [String]$Source 359 | [String]$Action 360 | [String]$ActionPretty 361 | 362 | CortexAlert( 363 | [PSCustomObject]$Alert 364 | ) { 365 | $this.ExternalId = $Alert.external_id 366 | $this.Severity = ConvertTo-PascalCase $Alert.severity 367 | $this.MatchingStatus = $Alert.matching_status 368 | $this.EndMatchAttemptTimestamp = ConvertFrom-UnixTimestamp $Alert.end_match_attempt_ts 369 | $this.LocalInsertTimestamp = ConvertFrom-UnixTimestamp $Alert.local_insert_ts 370 | $this.BiocIndicator = $Alert.bioc_indicator 371 | $this.MatchingServiceRuleId = $Alert.matching_service_rule_id 372 | $this.AttemptCounter = $Alert.attempt_counter 373 | $this.BiocCategoryEnumKey = $Alert.bioc_category_enum_key 374 | $this.IsWhitelisted = $Alert.is_whitelisted 375 | $this.Starred = $Alert.starred 376 | $this.DeduplicateTokens = $Alert.deduplicate_tokens 377 | $this.FilterRuleId = $Alert.filter_rule_id 378 | $this.MitreTechniqueIdAndName = $Alert.mitre_technique_id_and_name 379 | $this.MitreTacticIdAndName = $Alert.mitre_tactic_id_and_name 380 | $this.AgentVersion = $Alert.agent_version 381 | $this.AgentDeviceDomain = $Alert.agent_device_domain 382 | $this.AgentFqdn = $Alert.agent_fqdn 383 | $this.AgentOsType = $Alert.agent_os_type 384 | $this.AgentOsSubType = $Alert.agent_os_sub_type 385 | $this.AgentDataCollectionStatus = $Alert.agent_data_collection_status 386 | $this.Mac = $Alert.mac 387 | $this.Events = $Alert.events -as [CortexEvent[]] 388 | $this.AlertId = $Alert.alert_id 389 | $this.DetectionTimestamp = ConvertFrom-UnixTimestamp $Alert.detection_timestamp 390 | $this.Name = $Alert.name 391 | $this.Category = $Alert.category 392 | $this.EndpointId = $Alert.endpoint_id 393 | $this.Description = $Alert.description 394 | $this.HostIp = $Alert.host_ip 395 | $this.HostName = $Alert.host_name 396 | $this.MacAddresses = $Alert.mac_addresses 397 | $this.Source = $Alert.source 398 | $this.Action = $Alert.action 399 | $this.ActionPretty = $Alert.action_pretty 400 | } 401 | } 402 | 403 | class CortexAuditAgentReport { 404 | [DateTime]$Timestamp 405 | [DateTime]$ReceivedTime 406 | [String]$EndpointId 407 | [String]$EndpointName 408 | [String]$Domain 409 | [String]$XdrVersion 410 | [String]$Category 411 | [String]$Type 412 | [String]$SubType 413 | [String]$Result 414 | [String]$Reason 415 | [String]$Description 416 | 417 | CortexAuditAgentReport( 418 | [PSCustomObject]$AuditAgentReport 419 | ) { 420 | $this.Timestamp = ConvertFrom-UnixTimestamp $AuditAgentReport.TIMESTAMP 421 | $this.ReceivedTime = ConvertFrom-UnixTimestamp $AuditAgentReport.RECEIVEDTIME 422 | $this.EndpointId = $AuditAgentReport.ENDPOINTID 423 | $this.EndpointName = $AuditAgentReport.ENDPOINTNAME 424 | $this.Domain = $AuditAgentReport.DOMAIN 425 | $this.XdrVersion = $AuditAgentReport.XDRVERSION 426 | $this.Category = $AuditAgentReport.CATEGORY 427 | $this.Type = $AuditAgentReport.TYPE 428 | $this.SubType = $AuditAgentReport.SUBTYPE 429 | $this.Result = $AuditAgentReport.RESULT 430 | $this.Reason = $AuditAgentReport.REASON 431 | $this.Description = $AuditAgentReport.DESCRIPTION 432 | } 433 | } 434 | 435 | class CortexAuditManagementLog { 436 | [Int]$AuditId 437 | [String]$AuditOwnerName 438 | [String]$AuditOwnerEmail 439 | [String]$AuditAssetJson 440 | [String]$AuditAssetNames 441 | [String]$AuditHostname 442 | [String]$AuditResult 443 | [String]$AuditReason 444 | [String]$AuditDescription 445 | [String]$AuditEntity 446 | [String]$AuditEntitySubtype 447 | [String]$AuditSessionId 448 | [String]$AuditCaseId 449 | [String]$AuditInsertTime 450 | [String]$AuditSeverity 451 | 452 | CortexAuditManagementLog( 453 | [PSCustomObject]$AuditManagementLog 454 | ) { 455 | $this.AuditId = $AuditManagementLog.AUDIT_ID 456 | $this.AuditOwnerName = $AuditManagementLog.AUDIT_OWNER_NAME 457 | $this.AuditOwnerEmail = $AuditManagementLog.AUDIT_OWNER_EMAIL 458 | $this.AuditAssetJson = $AuditManagementLog.AUDIT_ASSET_JSON 459 | $this.AuditAssetNames = $AuditManagementLog.AUDIT_ASSET_NAMES 460 | $this.AuditHostname = $AuditManagementLog.AUDIT_HOSTNAME 461 | $this.AuditResult = $AuditManagementLog.AUDIT_RESULT 462 | $this.AuditReason = $AuditManagementLog.AUDIT_REASON 463 | $this.AuditDescription = $AuditManagementLog.AUDIT_DESCRIPTION 464 | $this.AuditEntity = $AuditManagementLog.AUDIT_ENTITY 465 | $this.AuditEntitySubtype = $AuditManagementLog.AUDIT_ENTITY_SUBTYPE 466 | $this.AuditSessionId = $AuditManagementLog.AUDIT_SESSION_ID 467 | $this.AuditCaseId = $AuditManagementLog.AUDIT_CASE_ID 468 | $this.AuditInsertTime = ConvertFrom-UnixTimestamp $AuditManagementLog.AUDIT_INSERT_TIME 469 | $this.AuditSeverity = $AuditManagementLog.AUDIT_SEVERITY 470 | } 471 | } 472 | 473 | class CortexViolation { 474 | [String]$HostName 475 | [String]$UserName 476 | [String]$IPAddress 477 | [DateTime]$Timestamp 478 | [Int]$ViolationId 479 | [String]$Type 480 | [String]$VendorId 481 | [String]$Vendor 482 | [String]$ProductId 483 | [String]$Product 484 | [String]$Serial 485 | [String]$EndpointId 486 | 487 | CortexViolation( 488 | [PSCustomObject]$Violation 489 | ) { 490 | $this.HostName = $Violation.hostname 491 | $this.UserName = $Violation.username 492 | $this.IPAddress = $Violation.ip 493 | $this.Timestamp = ConvertFrom-UnixTimestamp $Violation.timestamp 494 | $this.ViolationId = $Violation.violation_id 495 | $this.Type = $Violation.type 496 | $this.VendorId = $Violation.vendor_id 497 | $this.Vendor = $Violation.vendor 498 | $this.ProductId = $Violation.product_id 499 | $this.Product = $Violation.product 500 | $this.Serial = $Violation.serial 501 | $this.EndpointId = $Violation.endpoint_id 502 | } 503 | } 504 | #endregion 505 | 506 | #region private functions 507 | function Get-CortexConfig { 508 | [CmdletBinding()] 509 | param() 510 | 511 | if ($Script:CortexConfig -is [CortexConfig]) { 512 | $Script:CortexConfig 513 | } 514 | else { 515 | throw "Please run Initialize-CortexConfig before calling any other functions." 516 | } 517 | } 518 | 519 | function Get-Nonce { 520 | [CmdletBinding()] 521 | param( 522 | [Int32] 523 | $Length = 64 524 | ) 525 | 526 | # 0..9 A..Z a..z 527 | (((48..57) + (65..90) + (97..122)) * $Length | Get-Random -Count $Length).ForEach( { [char]$_ }) -join '' 528 | } 529 | 530 | function Get-UnixTimestamp { 531 | [CmdletBinding()] 532 | [OutputType('System.Int64')] 533 | param( 534 | [DateTime] 535 | $DateTime 536 | ) 537 | 538 | if (-not $PSBoundParameters.ContainsKey('DateTime')) { 539 | $DateTime = (Get-Date -Millisecond 0).ToUniversalTime() 540 | } 541 | 542 | $UnixEpochUtc = [DateTime]::new(1970, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc) 543 | [Int64][Double]::Parse((New-TimeSpan -Start $UnixEpochUtc -End $DateTime.ToUniversalTime()).TotalMilliseconds.ToString()) 544 | } 545 | 546 | function ConvertFrom-UnixTimestamp { 547 | [CmdletBinding()] 548 | param( 549 | [Parameter(Mandatory)] 550 | [Int64] 551 | $UnixTimestamp 552 | ) 553 | 554 | if ($UnixTimestamp -gt 0) { 555 | (Get-Date -Year 1970 -Month 1 -Date 1).AddMilliseconds($UnixTimestamp).ToLocalTime() 556 | } 557 | else { 558 | $null 559 | } 560 | } 561 | 562 | function ConvertTo-PascalCase { 563 | [CmdletBinding()] 564 | param( 565 | [Parameter(Mandatory)] 566 | [String] 567 | $SnakeCase 568 | ) 569 | 570 | (Get-Culture).TextInfo.ToTitleCase(($SnakeCase.ToLower() -replace '_', ' ')) -replace ' ' 571 | } 572 | 573 | function Get-CortexApiKeyHash { 574 | [CmdletBinding()] 575 | param( 576 | [Parameter(Mandatory)] 577 | [String] 578 | $ApiKey, 579 | 580 | [Parameter(Mandatory)] 581 | [String] 582 | $Nonce, 583 | 584 | [Parameter(Mandatory)] 585 | [String] 586 | $Timestamp 587 | ) 588 | 589 | $AuthKey = '{0}{1}{2}' -f $ApiKey, $Nonce, $Timestamp 590 | $Hasher = [System.Security.Cryptography.HashAlgorithm]::Create('SHA256') 591 | $Hash = $Hasher.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($AuthKey)) 592 | [System.BitConverter]::ToString($Hash).Replace('-', '').ToLower() 593 | } 594 | 595 | function Get-CortexApiUri { 596 | [CmdletBinding()] 597 | param( 598 | [Parameter(Mandatory)] 599 | [String] 600 | $ApiName, 601 | 602 | [Parameter(Mandatory)] 603 | [String] 604 | $CallName 605 | ) 606 | 607 | '{0}/{1}/{2}/' -f $Script:CortexConfig.BaseUri.AbsoluteUri, $ApiName, $CallName 608 | } 609 | 610 | function Get-CortexApiHeader { 611 | [CmdletBinding()] 612 | [OutputType('System.Collections.Hashtable')] 613 | param() 614 | 615 | $Config = Get-CortexConfig 616 | $ApiKeyId = $Config.Credential.UserName.ToString() 617 | $ApiKey = $Config.Credential.GetNetworkCredential().Password 618 | $SecurityLevel = $Config.SecurityLevel 619 | 620 | switch ($SecurityLevel) { 621 | 'Advanced' { 622 | $Nonce = Get-Nonce 623 | $Timestamp = Get-UnixTimestamp 624 | $ApiKeyHash = Get-CortexApiKeyHash -ApiKey $ApiKey -Nonce $Nonce -Timestamp $Timestamp 625 | 626 | @{ 627 | 'x-xdr-timestamp' = $Timestamp 628 | 'x-xdr-nonce' = $Nonce 629 | 'x-xdr-auth-id' = $ApiKeyId 630 | 'Authorization' = $ApiKeyHash 631 | } 632 | } 633 | 634 | 'Standard' { 635 | @{ 636 | 'x-xdr-auth-id' = $ApiKeyId 637 | 'Authorization' = $ApiKey 638 | } 639 | } 640 | } 641 | } 642 | 643 | function Get-CortexUserAgent { 644 | [CmdletBinding()] 645 | param() 646 | 647 | $Module = $MyInvocation.MyCommand.ScriptBlock.Module.Name 648 | $Version = $MyInvocation.MyCommand.ScriptBlock.Module.Version 649 | 650 | try { 651 | $UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent].GetProperty( 652 | 'UserAgent', 653 | [System.Reflection.BindingFlags]::Static -bor 654 | [System.Reflection.BindingFlags]::NonPublic 655 | ).GetValue([Microsoft.PowerShell.Commands.PSUserAgent]) 656 | } catch { 657 | $UserAgent = $null 658 | } 659 | 660 | $UserAgent, "$Module/$Version" -join ' ' 661 | } 662 | 663 | function Invoke-CortexApiRequest { 664 | [CmdletBinding()] 665 | param( 666 | [String]$ApiName, 667 | [String]$CallName, 668 | [String]$Body 669 | ) 670 | 671 | $Headers = Get-CortexApiHeader 672 | $Uri = Get-CortexApiUri -ApiName $ApiName -CallName $CallName 673 | $UserAgent = Get-CortexUserAgent 674 | 675 | Write-Verbose $UserAgent 676 | 677 | (Invoke-RestMethod -Uri $Uri -Method Post -Headers $Headers -Body $Body -UserAgent $UserAgent).reply 678 | } 679 | 680 | function Get-CortexFilter { 681 | [CmdletBinding()] 682 | [OutputType('System.Collections.Hashtable')] 683 | param( 684 | [String]$Field, 685 | [String]$Operator, 686 | [PSObject]$Value 687 | ) 688 | 689 | $NewValue = switch ($Operator) { 690 | 'gte' { Get-UnixTimestamp $Value } 691 | 'lte' { Get-UnixTimestamp $Value } 692 | 'in' { ,@($Value) } 693 | 'eq' { $Value } 694 | } 695 | 696 | @{ 697 | field = $Field 698 | operator = $Operator 699 | value = $NewValue 700 | } 701 | } 702 | #endregion 703 | 704 | #region public functions 705 | function Initialize-CortexConfig { 706 | [CmdletBinding()] 707 | param( 708 | [Parameter(Mandatory)] 709 | [PSCredential] 710 | $Credential, 711 | 712 | [CortexSecurityLevel] 713 | $SecurityLevel = 'Advanced', 714 | 715 | [Parameter(Mandatory)] 716 | [String] 717 | $TenantName, 718 | 719 | [CortexRegion] 720 | $Region = 'EU' 721 | ) 722 | 723 | $Script:CortexConfig = [CortexConfig]::new($Credential, $SecurityLevel, $TenantName, $Region) 724 | } 725 | 726 | function Get-CortexEndpointList { 727 | [CmdletBinding(DefaultParameterSetName = 'Default')] 728 | [OutputType('CortexEndpointSummary')] 729 | param( 730 | [Parameter(ParameterSetName = 'Active')] 731 | [switch] 732 | $ActiveOnly, 733 | 734 | [Parameter(ParameterSetName = 'Inactive')] 735 | [switch] 736 | $InactiveOnly 737 | ) 738 | 739 | $Endpoints = (Invoke-CortexApiRequest -ApiName endpoints -CallName get_endpoints -Body '{}') -as [CortexEndpointSummary[]] 740 | 741 | switch ($PSCmdlet.ParameterSetName) { 742 | 'Active' { 743 | $Endpoints.Where({$_.AgentStatus -in 'Connected', 'Disconnected'}) 744 | } 745 | 'Inactive' { 746 | $Endpoints.Where({$_.AgentStatus -in 'Lost', 'Uninstalled'}) 747 | } 748 | 'Default' { 749 | $Endpoints 750 | } 751 | } 752 | } 753 | 754 | function Get-CortexEndpoint { 755 | [CmdletBinding()] 756 | param( 757 | [String[]] 758 | $EndpointId, 759 | 760 | [CortexEndpointStatus[]] 761 | $EndpointStatus, 762 | 763 | [String[]] 764 | $HostName, 765 | 766 | [String[]] 767 | $GroupName, 768 | 769 | [DateTime] 770 | $FirstSeenAfter, 771 | 772 | [DateTime] 773 | $FirstSeenBefore, 774 | 775 | [DateTime] 776 | $LastSeenAfter, 777 | 778 | [DateTime] 779 | $LastSeenBefore 780 | ) 781 | 782 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 783 | 784 | $Request = @{ 785 | request_data = @{ 786 | search_from = 0 787 | search_to = 100 788 | filters = $Filters 789 | sort = @{ 790 | field = 'endpoint_id' 791 | keyword = 'asc' 792 | } 793 | } 794 | } 795 | 796 | $TotalCount = 0 797 | $SearchFrom = 0 798 | 799 | if ($PSBoundParameters.ContainsKey('EndpointId')) { 800 | $Filters.Add((Get-CortexFilter -Field endpoint_id_list -Operator in -Value $EndpointId)) 801 | } 802 | 803 | if ($PSBoundParameters.ContainsKey('EndpointStatus')) { 804 | $Filters.Add((Get-CortexFilter -Field endpoint_status -Operator in -Value ($EndpointStatus -as [String[]]))) 805 | } 806 | 807 | if ($PSBoundParameters.ContainsKey('HostName')) { 808 | $Filters.Add((Get-CortexFilter -Field hostname -Operator in -Value $HostName)) 809 | } 810 | 811 | if ($PSBoundParameters.ContainsKey('GroupName')) { 812 | $Filters.Add((Get-CortexFilter -Field group_name -Operator in -Value $GroupName)) 813 | } 814 | 815 | if ($PSBoundParameters.ContainsKey('FirstSeenAfter')) { 816 | $Filters.Add((Get-CortexFilter -Field first_seen -Operator gte -Value $FirstSeenAfter)) 817 | } 818 | 819 | if ($PSBoundParameters.ContainsKey('FirstSeenBefore')) { 820 | $Filters.Add((Get-CortexFilter -Field first_seen -Operator lte -Value $FirstSeenBefore)) 821 | } 822 | 823 | if ($PSBoundParameters.ContainsKey('LastSeenAfter')) { 824 | $Filters.Add((Get-CortexFilter -Field last_seen -Operator gte -Value $LastSeenAfter)) 825 | } 826 | 827 | if ($PSBoundParameters.ContainsKey('LastSeenBefore')) { 828 | $Filters.Add((Get-CortexFilter -Field last_seen -Operator lte -Value $LastSeenBefore)) 829 | } 830 | 831 | while ($SearchFrom -le $TotalCount) { 832 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 833 | 834 | Write-Verbose $Body 835 | 836 | $Result = Invoke-CortexApiRequest -ApiName endpoints -CallName get_endpoint -Body $Body 837 | $Result.endpoints -as [CortexEndpoint[]] 838 | 839 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 840 | 841 | $Request.Item('request_data').Item('search_from') += 100 842 | $Request.Item('request_data').Item('search_to') += 100 843 | 844 | $SearchFrom = $Request.Item('request_data').Item('search_from') 845 | $TotalCount = $Result.total_count 846 | } 847 | } 848 | 849 | function Remove-CortexEndpoint { 850 | [CmdletBinding(SupportsShouldProcess)] 851 | param( 852 | [Parameter(Mandatory)] 853 | [String[]] 854 | $EndpointId 855 | ) 856 | 857 | $Body = @{ 858 | request_data = @{ 859 | filters = @( 860 | @{ 861 | field = 'endpoint_id_list' 862 | operator = 'in' 863 | value = @($EndpointId) 864 | } 865 | ) 866 | } 867 | } | ConvertTo-Json -Depth 4 -Compress 868 | 869 | if ($PSCmdlet.ShouldProcess($EndpointId, 'delete')) { 870 | Invoke-CortexApiRequest -ApiName endpoints -CallName delete -Body $Body 871 | } 872 | } 873 | 874 | function Get-CortexIncident { 875 | [CmdletBinding()] 876 | param( 877 | [CortexIncidentStatus] 878 | $Status, 879 | 880 | [DateTime] 881 | $CreatedAfter, 882 | 883 | [DateTime] 884 | $CreatedBefore 885 | ) 886 | 887 | $AllowedStatus = @{ 888 | ResolvedThreatHandled = 'resolved_threat_handled' 889 | UnderInvestigation = 'under_investigation' 890 | New = 'new' 891 | ResolvedFalsePositive = 'resolved_false_positive' 892 | ResolvedKnownIssue = 'resolved_known_issue' 893 | ResolvedAuto = 'resolved_auto' 894 | ResolvedDuplicate = 'resolved_duplicate' 895 | ResolvedOther = 'resolved_other' 896 | } 897 | 898 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 899 | 900 | $Request = @{ 901 | request_data = @{ 902 | search_from = 0 903 | search_to = 100 904 | filters = $Filters 905 | sort = @{ 906 | field = 'creation_time' 907 | keyword = 'asc' 908 | } 909 | } 910 | } 911 | 912 | $TotalCount = 0 913 | $SearchFrom = 0 914 | 915 | if ($PSBoundParameters.ContainsKey('Status')) { 916 | $Filters.Add((Get-CortexFilter -Field status -Operator eq -Value $AllowedStatus[[String]$Status])) 917 | } 918 | 919 | if ($PSBoundParameters.ContainsKey('CreatedAfter')) { 920 | $Filters.Add((Get-CortexFilter -Field creation_time -Operator gte -Value $CreatedAfter)) 921 | } 922 | 923 | if ($PSBoundParameters.ContainsKey('CreatedBefore')) { 924 | $Filters.Add((Get-CortexFilter -Field creation_time -Operator lte -Value $CreatedBefore)) 925 | } 926 | 927 | while ($SearchFrom -le $TotalCount) { 928 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 929 | 930 | Write-Verbose $Body 931 | 932 | $Result = Invoke-CortexApiRequest -ApiName incidents -CallName get_incidents -Body $Body 933 | $Result.incidents -as [CortexIncident[]] 934 | 935 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 936 | 937 | $Request.Item('request_data').Item('search_from') += 100 938 | $Request.Item('request_data').Item('search_to') += 100 939 | 940 | $SearchFrom = $Request.Item('request_data').Item('search_from') 941 | $TotalCount = $Result.total_count 942 | } 943 | } 944 | 945 | function Get-CortexIncidentExtraData { 946 | [CmdletBinding()] 947 | param( 948 | [Parameter(Mandatory)] 949 | [String] 950 | $IncidentId 951 | ) 952 | 953 | $Body = @{ 954 | request_data = @{ 955 | incident_id = $IncidentId 956 | } 957 | } | ConvertTo-Json -Compress 958 | 959 | Invoke-CortexApiRequest -ApiName incidents -CallName get_incident_extra_data -Body $Body 960 | } 961 | 962 | function Get-CortexAlert { 963 | [CmdletBinding()] 964 | param( 965 | [Int[]] 966 | $AlertId, 967 | 968 | [CortexAlertSeverity[]] 969 | $Severity, 970 | 971 | [DateTime] 972 | $CreatedAfter, 973 | 974 | [DateTime] 975 | $CreatedBefore 976 | ) 977 | 978 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 979 | 980 | $Request = @{ 981 | request_data = @{ 982 | search_from = 0 983 | search_to = 100 984 | filters = $Filters 985 | sort = @{ 986 | field = 'creation_time' 987 | keyword = 'asc' 988 | } 989 | } 990 | } 991 | 992 | $TotalCount = 0 993 | $SearchFrom = 0 994 | 995 | if ($PSBoundParameters.ContainsKey('AlertId')) { 996 | $Filters.Add((Get-CortexFilter -Field alert_id_list -Operator in -Value $AlertId)) 997 | } 998 | 999 | if ($PSBoundParameters.ContainsKey('Severity')) { 1000 | $Filters.Add((Get-CortexFilter -Field severity -Operator in -Value ($Severity -as [String[]]))) 1001 | } 1002 | 1003 | if ($PSBoundParameters.ContainsKey('CreatedAfter')) { 1004 | $Filters.Add((Get-CortexFilter -Field creation_time -Operator gte -Value $CreatedAfter)) 1005 | } 1006 | 1007 | if ($PSBoundParameters.ContainsKey('CreatedBefore')) { 1008 | $Filters.Add((Get-CortexFilter -Field creation_time -Operator lte -Value $CreatedBefore)) 1009 | } 1010 | 1011 | while ($SearchFrom -le $TotalCount) { 1012 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 1013 | 1014 | Write-Verbose $Body 1015 | 1016 | $Result = Invoke-CortexApiRequest -ApiName alerts -CallName get_alerts_multi_events -Body $Body 1017 | $Result.alerts -as [CortexAlert[]] 1018 | 1019 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 1020 | 1021 | $Request.Item('request_data').Item('search_from') += 100 1022 | $Request.Item('request_data').Item('search_to') += 100 1023 | 1024 | $SearchFrom = $Request.Item('request_data').Item('search_from') 1025 | $TotalCount = $Result.total_count 1026 | } 1027 | } 1028 | 1029 | function Get-CortexAuditAgentReport { 1030 | [CmdletBinding()] 1031 | param( 1032 | [String[]] 1033 | $EndpointName, 1034 | 1035 | [CortexAuditAgentReportCategory[]] 1036 | $Category, 1037 | 1038 | [DateTime] 1039 | $CreatedAfter, 1040 | 1041 | [DateTime] 1042 | $CreatedBefore 1043 | ) 1044 | 1045 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 1046 | 1047 | $Request = @{ 1048 | request_data = @{ 1049 | search_from = 0 1050 | search_to = 100 1051 | filters = $Filters 1052 | sort = @{ 1053 | field = 'timestamp' 1054 | keyword = 'asc' 1055 | } 1056 | } 1057 | } 1058 | 1059 | $TotalCount = 0 1060 | $SearchFrom = 0 1061 | 1062 | if ($PSBoundParameters.ContainsKey('EndpointName')) { 1063 | $Filters.Add((Get-CortexFilter -Field endpoint_name -Operator in -Value $EndpointName)) 1064 | } 1065 | 1066 | if ($PSBoundParameters.ContainsKey('Category')) { 1067 | $Filters.Add((Get-CortexFilter -Field category -Operator in -Value ($Category -as [String[]]))) 1068 | } 1069 | 1070 | if ($PSBoundParameters.ContainsKey('CreatedAfter')) { 1071 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator gte -Value $CreatedAfter)) 1072 | } 1073 | 1074 | if ($PSBoundParameters.ContainsKey('CreatedBefore')) { 1075 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator lte -Value $CreatedBefore)) 1076 | } 1077 | 1078 | while ($SearchFrom -le $TotalCount) { 1079 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 1080 | 1081 | Write-Verbose $Body 1082 | 1083 | $Result = Invoke-CortexApiRequest -ApiName audits -CallName agents_reports -Body $Body 1084 | $Result.data -as [CortexAuditAgentReport[]] 1085 | 1086 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 1087 | 1088 | $Request.Item('request_data').Item('search_from') += 100 1089 | $Request.Item('request_data').Item('search_to') += 100 1090 | 1091 | $SearchFrom = $Request.Item('request_data').Item('search_from') 1092 | $TotalCount = $Result.total_count 1093 | } 1094 | } 1095 | 1096 | function Get-CortexAuditManagementLog { 1097 | [CmdletBinding()] 1098 | param( 1099 | [String[]] 1100 | $EmailAddress, 1101 | 1102 | [DateTime] 1103 | $CreatedAfter, 1104 | 1105 | [DateTime] 1106 | $CreatedBefore 1107 | ) 1108 | 1109 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 1110 | 1111 | $Request = @{ 1112 | request_data = @{ 1113 | search_from = 0 1114 | search_to = 100 1115 | filters = $Filters 1116 | sort = @{ 1117 | field = 'timestamp' 1118 | keyword = 'asc' 1119 | } 1120 | } 1121 | } 1122 | 1123 | $TotalCount = 0 1124 | $SearchFrom = 0 1125 | 1126 | if ($PSBoundParameters.ContainsKey('EmailAddress')) { 1127 | $Filters.Add((Get-CortexFilter -Field email -Operator in -Value $EmailAddress)) 1128 | } 1129 | 1130 | if ($PSBoundParameters.ContainsKey('CreatedAfter')) { 1131 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator gte -Value $CreatedAfter)) 1132 | } 1133 | 1134 | if ($PSBoundParameters.ContainsKey('CreatedBefore')) { 1135 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator lte -Value $CreatedBefore)) 1136 | } 1137 | 1138 | while ($SearchFrom -le $TotalCount) { 1139 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 1140 | 1141 | Write-Verbose $Body 1142 | 1143 | $Result = Invoke-CortexApiRequest -ApiName audits -CallName management_logs -Body $Body 1144 | $Result.data -as [CortexAuditManagementLog[]] 1145 | 1146 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 1147 | 1148 | $Request.Item('request_data').Item('search_from') += 100 1149 | $Request.Item('request_data').Item('search_to') += 100 1150 | 1151 | $SearchFrom = $Request.Item('request_data').Item('search_from') 1152 | $TotalCount = $Result.total_count 1153 | } 1154 | } 1155 | 1156 | function Get-CortexViolation { 1157 | [CmdletBinding()] 1158 | param( 1159 | [String[]] 1160 | $HostName, 1161 | 1162 | [CortexViolationType] 1163 | $Type, 1164 | 1165 | [String[]] 1166 | $EndpointId, 1167 | 1168 | [DateTime] 1169 | $CreatedAfter, 1170 | 1171 | [DateTime] 1172 | $CreatedBefore 1173 | ) 1174 | 1175 | $AllowedType = @{ 1176 | CdRom = 'cd-rom' 1177 | DiskDrive = 'disk drive' 1178 | FloppyDisk = 'floppy disk' 1179 | PortableDevice = 'windows portable devices' 1180 | } 1181 | 1182 | $Filters = New-Object 'System.Collections.Generic.List[hashtable]' 1183 | 1184 | $Request = @{ 1185 | request_data = @{ 1186 | search_from = 0 1187 | search_to = 100 1188 | filters = $Filters 1189 | sort = @{ 1190 | field = 'timestamp' 1191 | keyword = 'asc' 1192 | } 1193 | } 1194 | } 1195 | 1196 | $TotalCount = 0 1197 | $SearchFrom = 0 1198 | 1199 | if ($PSBoundParameters.ContainsKey('HostName')) { 1200 | $Filters.Add((Get-CortexFilter -Field hostname -Operator in -Value $HostName)) 1201 | } 1202 | 1203 | if ($PSBoundParameters.ContainsKey('Type')) { 1204 | $Filters.Add((Get-CortexFilter -Field type -Operator in -Value $AllowedType[[String]$Type])) 1205 | } 1206 | 1207 | if ($PSBoundParameters.ContainsKey('EndpointId')) { 1208 | $Filters.Add((Get-CortexFilter -Field endpoint_id_list -Operator in -Value $EndpointId)) 1209 | } 1210 | 1211 | if ($PSBoundParameters.ContainsKey('CreatedAfter')) { 1212 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator gte -Value $CreatedAfter)) 1213 | } 1214 | 1215 | if ($PSBoundParameters.ContainsKey('CreatedBefore')) { 1216 | $Filters.Add((Get-CortexFilter -Field timestamp -Operator lte -Value $CreatedBefore)) 1217 | } 1218 | 1219 | while ($SearchFrom -le $TotalCount) { 1220 | $Body = $Request | ConvertTo-Json -Depth 4 -Compress 1221 | 1222 | Write-Verbose $Body 1223 | 1224 | $Result = Invoke-CortexApiRequest -ApiName device_control -CallName get_violations -Body $Body 1225 | $Result.violations -as [CortexViolation[]] 1226 | 1227 | Write-Verbose ($Result | Select-Object result_count, total_count | ConvertTo-Json -Compress) 1228 | 1229 | $Request.Item('request_data').Item('search_from') += 100 1230 | $Request.Item('request_data').Item('search_to') += 100 1231 | 1232 | $SearchFrom = $Request.Item('request_data').Item('search_from') 1233 | $TotalCount = $Result.total_count 1234 | } 1235 | } 1236 | #endregion 1237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/PSCortex?color=808000&logo=powershell&logoColor=lightgrey&style=flat-square) 2 | ![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/PSCortex?color=808000&style=flat-square) 3 | ![GitHub](https://img.shields.io/github/license/lahell/PSCortex?color=808000&style=flat-square) 4 | ## PSCortex 5 | 6 | Get endpoints, incidents and alerts from the Cortex XDR API. 7 | 8 | > [!IMPORTANT] 9 | > This module is no longer maintained. 10 | 11 | ### Before you begin 12 | First of all you have to obtain a API Key and API Key ID: [Get Started with Cortex XDR APIs](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR/Cortex-XDR-API-Reference/Get-Started-with-APIs) 13 | 14 | ### Installation 15 | 16 | ```PowerShell 17 | Install-Module -Name PSCortex 18 | ``` 19 | 20 | ### Usage 21 | Below are some examples of how you can use this module. Please use `Get-Help` for more details about each function. 22 | 23 | Store API Key ID and API Key as `$Credential` and pass it to `Initialize-CortexConfig`. 24 | ```PowerShell 25 | $Credential = Get-Credential 26 | Initialize-CortexConfig -TenantName yourcompany -SecurityLevel Advanced -Region EU -Credential $Credential 27 | ``` 28 | 29 | [Get All Endpoints](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-All-Endpoints). Returns a list of all endpoints with a limited number of properties. 30 | ```PowerShell 31 | Get-CortexEndpointList 32 | ``` 33 | 34 | [Get Endpoint](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-Endpoint) where status is lost and [Delete Endpoints](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Delete-Endpoints). Running `Get-CortexEndpoint` without parameters will return all endpoints. 35 | ```PowerShell 36 | $LostEndpoints = Get-CortexEndpoint -EndpointStatus Lost 37 | Remove-CortexEndpoint -EndpointId $LostEndpoints.EndpointId -WhatIf 38 | ``` 39 | 40 | [Get Incidents](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-all-Incidents). Running `Get-CortexIncident` without parameters will return all incidents. 41 | ```PowerShell 42 | Get-CortexIncident -Status New 43 | ``` 44 | 45 | [Get Alerts](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-all-Alerts). Running `Get-CortexAlert` without parameters will return all alerts. 46 | ```PowerShell 47 | Get-CortexAlert -Severity High 48 | ``` 49 | 50 | [Get Audit Agent Report](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-Audit-Agent-Report). Running `Get-CortexAuditAgentReport` without parameters will return all reports. 51 | ```PowerShell 52 | Get-CortexAuditAgentReport -Category Status 53 | ``` 54 | 55 | [Get Audit Management Log](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-Audit-Management-Log). Running `Get-CortexAuditManagementLog` without parameters will return all logs. 56 | ```PowerShell 57 | Get-CortexAuditManagementLog -CreatedAfter (Get-Date).AddDays(-7) 58 | ``` 59 | 60 | [Get Violations](https://docs-cortex.paloaltonetworks.com/r/Cortex-XDR-REST-API/Get-Violations). Running `Get-CortexViolation` without parameters will return all violations. 61 | ```PowerShell 62 | Get-CortexViolation -CreatedAfter (Get-Date).AddDays(-7) -Type PortableDevice 63 | ``` 64 | 65 | ### Use Case: Find Duplicates 66 | If a computer is reinstalled you could end up with duplicates in Cortex XDR. 67 | ```PowerShell 68 | Get-CortexEndpointList | Group-Object HostName | Where-Object Count -gt 1 | Select-Object -ExpandProperty Group 69 | ``` 70 | 71 | ### Use Case: Delete Endpoints that do not exist in AD 72 | If the endpoint is uninstalled or lost and the computer no longer exist in AD you probably want to remove it from Cortex XDR. 73 | ```PowerShell 74 | $Endpoints = Get-CortexEndpointList -InactiveOnly | Where-Object HostName -notin (Get-ADComputer -Filter *).Name 75 | Remove-CortexEndpoint -EndpointId $Endpoints.AgentId -WhatIf 76 | ``` 77 | -------------------------------------------------------------------------------- /images/PSCortex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lahell/PSCortex/c9c6fcdd6f515cf3b4c333a4a69ae63115b6b994/images/PSCortex.png -------------------------------------------------------------------------------- /images/PSCortex.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | PSCortex 21 | 23 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | SNMPv3 51 | 52 | 53 | 54 | 59 | 63 | 68 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 104 | 108 | 112 | 116 | 120 | 124 | 128 | 132 | 136 | 137 | 138 | 139 | 140 | --------------------------------------------------------------------------------