From 6deb0c65f0765df0783aabc4a887a4abde5cd090 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sat, 18 Jan 2025 15:26:04 -0500 Subject: [PATCH 01/53] Added a very valuable comment :) --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 62bb3c7b..b32a27a1 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -10,7 +10,7 @@ Get-IPGeolocation Gets all IP Geolocation data of IPs that recieved .NOTES - General notes + General notes (DCODEV was here) #> Function Get-IPGeolocation { From 21773ac866861aff0da431a8b25c176948776659 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sat, 18 Jan 2025 15:46:32 -0500 Subject: [PATCH 02/53] Better comment, aye, aye sir! --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index b32a27a1..40e3a885 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -10,7 +10,7 @@ Get-IPGeolocation Gets all IP Geolocation data of IPs that recieved .NOTES - General notes (DCODEV was here) + General notes (DCODEV was here <-> If you say so, sir!) #> Function Get-IPGeolocation { From 288597d3d7cd718cfac3c8f71f5aab4a7132f432 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sat, 18 Jan 2025 20:01:29 -0500 Subject: [PATCH 03/53] Added logic for checking ipstack.com API Key with some error handling. --- Hawk/internal/functions/Add-HawkAppData.ps1 | 2 +- Hawk/internal/functions/Get-IPGeolocation.ps1 | 188 +++++++++++------- 2 files changed, 116 insertions(+), 74 deletions(-) diff --git a/Hawk/internal/functions/Add-HawkAppData.ps1 b/Hawk/internal/functions/Add-HawkAppData.ps1 index 2b259b74..b433ced2 100644 --- a/Hawk/internal/functions/Add-HawkAppData.ps1 +++ b/Hawk/internal/functions/Add-HawkAppData.ps1 @@ -28,7 +28,7 @@ Function Add-HawkAppData { # Test if our HawkAppData variable exists if ([bool](get-variable HawkAppData -ErrorAction SilentlyContinue)) { - $global:HawkAppData | Add-Member -MemberType NoteProperty -Name $Name -Value $Value + $global:HawkAppData | Add-Member -MemberType NoteProperty -Name $Name -Value $Value -Force } else { $global:HawkAppData = New-Object -TypeName PSObject diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 40e3a885..0cfba281 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -1,5 +1,4 @@ - -<# +<# .SYNOPSIS Get the Location of an IP using the freegeoip.net rest API .DESCRIPTION @@ -10,7 +9,7 @@ Get-IPGeolocation Gets all IP Geolocation data of IPs that recieved .NOTES - General notes (DCODEV was here <-> If you say so, sir!) + General notes #> Function Get-IPGeolocation { @@ -20,89 +19,132 @@ Function Get-IPGeolocation { $IPAddress ) - # If we don't have a HawkAppData variable then we need to read it in - if (!([bool](get-variable HawkAppData -erroraction silentlycontinue))) { - Read-HawkAppData + begin { + # Read in existing HawkAppData + if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { + Read-HawkAppData + } } - # if there is no value of access_key then we need to get it from the user - if ($null -eq $HawkAppData.access_key) { + process { + try { + # if there is no value of access_key then we need to get it from the user + if ([string]::IsNullOrEmpty($HawkAppData.access_key)) { - Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API.`nPlease get a Free access key from https://ipstack.com/ and provide it below." -Information + Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API.`nPlease get a Free access key from https://ipstack.com/ and provide it below." -Information - # get the access key from the user - # get the access key from the user - Out-LogFile "ipstack.com accesskey" -isPrompt -NoNewLine - $Accesskey = (Read-Host).Trim() + Out-LogFile "`nIP Stack API Key Configuration" -Action + Out-LogFile "Get your free API key at: https://ipstack.com/`n" -Action - # add the access key to the appdata file - Add-HawkAppData -name access_key -Value $Accesskey - } - else { - $Accesskey = $HawkAppData.access_key - } + # get the access key from the user + Out-LogFile "ipstack.com accesskey " -isPrompt -NoNewLine + $AccessKey = (Read-Host "Enter your IP Stack API key").Trim() - # Check the global IP cache and see if we already have the IP there - if ($IPLocationCache.ip -contains $IPAddress) { - return ($IPLocationCache | Where-Object { $_.ip -eq $IPAddress } ) - Write-Verbose ("IP Cache Hit: " + [string]$IPAddress) - } - elseif ($IPAddress -eq ""){ - write-Verbose ("Null IP Provided: " + $IPAddress) - $hash = @{ - IP = $IPAddress - CountryName = "NULL IP" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" - KnownMicrosoftIP = "Unknown" + # Validate key format (basic check) + if ([string]::IsNullOrWhiteSpace($AccessKey)) { + Out-LogFile "API key cannot be empty or whitespace." -isError + throw | Out-Null + } + + # If testing is requested, validate the key + if ($AccessKey) { + Out-LogFile "Testing API key against Google DNS..." -Action + $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" + + try { + $response = Invoke-RestMethod -Uri $testUrl -Method Get + if ($response.success -eq $false) { + Out-LogFile "API key validation failed: $($response.error.info)" -isError + throw | Out-Null + } + Out-LogFile "API key validated successfully!" -Information + + # Save to disk + Out-HawkAppData + } + catch { + Out-LogFile "API key validation failed: $_" -isError + throw | Out-Null + + } + } + + # The ipstack API key is valid. Add the access key to the appdata file + Add-HawkAppData -name access_key -Value $AccessKey } - } - # If not then we need to look it up and populate it into the cache - else { - # URI to pull the data from - $resource = "http://api.ipstack.com/" + $ipaddress + "?access_key=" + $Accesskey - - # Return Data from web - $Error.Clear() - $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction SilentlyContinue - - if (($Error.Count -gt 0) -or ($null -eq $geoip.type)) { - Out-LogFile ("Failed to retreive location for IP " + $IPAddress) -isError - $hash = @{ - IP = $IPAddress - CountryName = "Failed to Resolve" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" - KnownMicrosoftIP = "Unknown" + else { + $AccessKey = $HawkAppData.access_key } } + catch { + Out-LogFile "Failed to update IP Stack API key: $_" -isError + throw | Out-Null + } + + + + # Check the global IP cache and see if we already have the IP there + if ($IPLocationCache.ip -contains $IPAddress) { + return ($IPLocationCache | Where-Object { $_.ip -eq $IPAddress } ) + Write-Verbose ("IP Cache Hit: " + [string]$IPAddress) + } + elseif ($IPAddress -eq ""){ + write-Verbose ("Null IP Provided: " + $IPAddress) + $hash = @{ + IP = $IPAddress + CountryName = "NULL IP" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" + KnownMicrosoftIP = "Unknown" + } + } + # If not then we need to look it up and populate it into the cache else { - # Determine if this IP is known to be owned by Microsoft - [string]$isMSFTIP = Test-MicrosoftIP -IPToTest $IPAddress -type $geoip.type - if ($isMSFTIP){ - $MSFTIP = $isMSFTIP + # URI to pull the data from + $resource = "http://api.ipstack.com/" + $ipaddress + "?access_key=" + $AccessKey + + # Return Data from web + $Error.Clear() + $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction SilentlyContinue + + if (($Error.Count) -or ([string]::IsNullOrEmpty($geoip.type))) { + Out-LogFile ("Failed to retreive location for IP " + $IPAddress) -isError + $hash = @{ + IP = $IPAddress + CountryName = "Failed to Resolve" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" + KnownMicrosoftIP = "Unknown" + } } - # Push return into a response object - $hash = @{ - IP = $geoip.ip - CountryName = $geoip.country_name - ContinentName = $geoip.continent_name - RegionName = $geoip.region_name - RegionCode = $geoip.region_code - City = $geoip.City - KnownMicrosoftIP = $MSFTIP + else { + # Determine if this IP is known to be owned by Microsoft + [string]$isMSFTIP = Test-MicrosoftIP -IPToTest $IPAddress -type $geoip.type + if ($isMSFTIP){ + $MSFTIP = $isMSFTIP + } + # Push return into a response object + $hash = @{ + IP = $geoip.ip + CountryName = $geoip.country_name + ContinentName = $geoip.continent_name + RegionName = $geoip.region_name + RegionCode = $geoip.region_code + City = $geoip.City + KnownMicrosoftIP = $MSFTIP + } + $result = New-Object PSObject -Property $hash } - $result = New-Object PSObject -Property $hash - } - # Push the result to the global IPLocationCache - [array]$Global:IPlocationCache += $result + # Push the result to the global IPLocationCache + [array]$Global:IPlocationCache += $result - # Return the result to the user - return $result + # Return the result to the user + return $result + } } } \ No newline at end of file From e1f7352c04944b7ca2f25f2b061a0db645f222e1 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sat, 18 Jan 2025 20:10:47 -0500 Subject: [PATCH 04/53] Added logic for checking ipstack.com API Key with some error handling. --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 0cfba281..84f44730 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -43,7 +43,7 @@ Function Get-IPGeolocation { # Validate key format (basic check) if ([string]::IsNullOrWhiteSpace($AccessKey)) { Out-LogFile "API key cannot be empty or whitespace." -isError - throw | Out-Null + throw "API key cannot be empty or whitespace." } # If testing is requested, validate the key @@ -55,7 +55,7 @@ Function Get-IPGeolocation { $response = Invoke-RestMethod -Uri $testUrl -Method Get if ($response.success -eq $false) { Out-LogFile "API key validation failed: $($response.error.info)" -isError - throw | Out-Null + throw "API key validation failed: $($response.error.info)" } Out-LogFile "API key validated successfully!" -Information @@ -64,7 +64,7 @@ Function Get-IPGeolocation { } catch { Out-LogFile "API key validation failed: $_" -isError - throw | Out-Null + throw "API key validation failed: $_" } } @@ -78,7 +78,7 @@ Function Get-IPGeolocation { } catch { Out-LogFile "Failed to update IP Stack API key: $_" -isError - throw | Out-Null + throw "Failed to update IP Stack API key: $_" } From f9340ece71b3b5a8e0688b19000cfbbfd563b766 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sun, 19 Jan 2025 03:23:36 -0500 Subject: [PATCH 05/53] added -EnableGeoIPLocation command line switch and IPSTACK API logic to check and test IPSTACK API keys. --- .../Tenant/Start-HawkTenantInvestigation.ps1 | 5 ++- .../User/Get-HawkUserAuthHistory.ps1 | 8 ++-- .../User/Start-HawkUserInvestigation.ps1 | 41 +++++++++++++---- .../functions/Clear-HawkEnvironment.ps1 | 45 +++++++++++++++++++ Hawk/internal/functions/Get-IPGeolocation.ps1 | 5 ++- .../functions/Initialize-HawkGlobalObject.ps1 | 37 ++++++++++++--- .../functions/Test-HawkNonInteractiveMode.ps1 | 3 +- 7 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 Hawk/internal/functions/Clear-HawkEnvironment.ps1 diff --git a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 index d104c98a..a80822a8 100644 --- a/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 +++ b/Hawk/functions/Tenant/Start-HawkTenantInvestigation.ps1 @@ -228,6 +228,9 @@ } } - + end { + Out-LogFile "User investigation completed, clearning global environment variables" -Information + Clear-HawkEnvironment + } } \ No newline at end of file diff --git a/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 b/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 index 738897ea..e3593d5e 100644 --- a/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 +++ b/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 @@ -101,8 +101,8 @@ # Add IP Geo Location information to the data - if ($ResolveIPLocations) { - Out-File "Resolving IP Locations" + if ($PSBoundParameters.ContainsKey('ResolveIPLocations')) { + Out-LogFile "Resolving IP Locations" -Action # Setup our counter $i = 0 @@ -110,7 +110,7 @@ while ($i -lt $ExpandedUserLogonLogs.Count) { if ([bool]($i % 25)) { } - Else { + else { Write-Progress -Activity "Looking Up Ip Address Locations" -CurrentOperation $i -PercentComplete (($i / $ExpandedUserLogonLogs.count) * 100) } @@ -144,6 +144,4 @@ } } - - } diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index dbaa9440..2384a73d 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -97,12 +97,12 @@ param ( [Parameter(Mandatory = $true)] [array]$UserPrincipalName, - [DateTime]$StartDate, [DateTime]$EndDate, [int]$DaysToLookBack, [string]$FilePath, - [switch]$SkipUpdate + [switch]$SkipUpdate, + [switch]$EnableGeoIPLocation ) begin { @@ -125,9 +125,15 @@ } try { - Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` - -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` - -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { + Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` + -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive -EnableGeoIPLocation:$EnableGeoIPLocation + } else { + Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` + -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` + -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive + } } catch { Stop-PSFFunction -Message "Failed to initialize Hawk: $_" -EnableException $true @@ -138,9 +144,19 @@ process { if (Test-PSFFunctionInterrupt) { return } + if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { + Initialize-HawkGlobalObject -EnableGeoIPLocation + Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject with EnableGeoIPLocation" -Information + } else { + Initialize-HawkGlobalObject + Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject without EnableGeoIPLocation" -Information + } + # Check if Hawk object exists and is fully initialized if (Test-HawkGlobalObject) { - Initialize-HawkGlobalObject + Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to TRUE!" -Information + } else { + Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to FALSE!" -Information } if ($PSCmdlet.ShouldProcess("Investigating Users")) { @@ -178,7 +194,13 @@ if ($PSCmdlet.ShouldProcess("Running Get-HawkUserAuthHistory for $User")) { Out-LogFile "Running Get-HawkUserAuthHistory" -Action - Get-HawkUserAuthHistory -User $User -ResolveIPLocations + if ($Hawk.EnableGeoIPLocation -or $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { + Out-LogFile "Calling Get-HawkUserAuthHistory WITH ResolveIPLocations enabled." -Information + Get-HawkUserAuthHistory -User $User -ResolveIPLocations + } else { + Out-LogFile "Calling Get-HawkUserAuthHistory WITHOUT ResolveIPLocations enabled." -Information + Get-HawkUserAuthHistory -User $User + } } if ($PSCmdlet.ShouldProcess("Running Get-HawkUserMailboxAuditing for $User")) { @@ -207,7 +229,10 @@ } } } - + } + end { + Out-LogFile "User investigation completed, clearning global environment variables" -Information + Clear-HawkEnvironment } } diff --git a/Hawk/internal/functions/Clear-HawkEnvironment.ps1 b/Hawk/internal/functions/Clear-HawkEnvironment.ps1 new file mode 100644 index 00000000..7d63a3cf --- /dev/null +++ b/Hawk/internal/functions/Clear-HawkEnvironment.ps1 @@ -0,0 +1,45 @@ +Function Clear-HawkEnvironment { + <# + .SYNOPSIS + Cleans up Hawk global variables and environment state. + + .DESCRIPTION + Removes Hawk-specific global variables and cleans up the PowerShell environment + after Hawk operations complete. This prevents state persistence between runs + and ensures a clean environment for subsequent executions. + + .EXAMPLE + Clear-HawkEnvironment + + Cleans up all Hawk global variables and environment state. + #> + [CmdletBinding()] + param() + + try { + # List of known Hawk global variables to remove + $hawkGlobals = @( + 'Hawk', + 'HawkAppData', + 'MSFTIPList', + 'IPLocationCache' + ) + + # Remove each Hawk global variable if it exists + foreach ($varName in $hawkGlobals) { + if (Get-Variable -Name $varName -ErrorAction SilentlyContinue) { + Remove-Variable -Name $varName -Scope Global -Force -ErrorAction SilentlyContinue + Write-Verbose "Removed global variable: $varName" + } + } + + # Clear the error variable + $Error.Clear() + + Write-Verbose "Hawk environment cleanup completed successfully" + } + catch { + Write-Warning "Error during Hawk environment cleanup: $_" + # Don't throw here - we don't want cleanup failures to affect the user + } +} \ No newline at end of file diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 84f44730..21953e5f 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -27,6 +27,7 @@ Function Get-IPGeolocation { } process { + # MOVE THIS IPSTACK API CODE CHECK TO Get-HawkUserAuthHistory within the ResolveIPLocations block!! try { # if there is no value of access_key then we need to get it from the user if ([string]::IsNullOrEmpty($HawkAppData.access_key)) { @@ -37,8 +38,8 @@ Function Get-IPGeolocation { Out-LogFile "Get your free API key at: https://ipstack.com/`n" -Action # get the access key from the user - Out-LogFile "ipstack.com accesskey " -isPrompt -NoNewLine - $AccessKey = (Read-Host "Enter your IP Stack API key").Trim() + Out-LogFile "Provide your IP Stack API key: " -isPrompt -NoNewLine + $AccessKey = (Read-Host).Trim() # Validate key format (basic check) if ([string]::IsNullOrWhiteSpace($AccessKey)) { diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 5b9fe4a0..3b2cd26e 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -48,7 +48,8 @@ [string]$FilePath, [switch]$SkipUpdate, [switch]$NonInteractive, - [switch]$Force + [switch]$Force, + [switch]$EnableGeoIPLocation ) @@ -214,6 +215,8 @@ StartDate = $null EndDate = $null WhenCreated = $null + EnableGeoIPLocation = $null + } # Set up the file path first, before any other operations @@ -317,8 +320,6 @@ # At this point, we do not yet have EndDate set. So temporarily anchor from "today": [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-$StartRead)).Date - - Write-Output "" Out-LogFile -string "Start date set to: $StartDate" -Information } @@ -344,7 +345,6 @@ if ($StartDate -lt ((Get-Date).ToUniversalTime().AddDays(-365))) { Out-LogFile -string "The date cannot exceed 365 days. Setting to the maximum limit of 365 days." -isWarning [DateTime]$StartDate = ((Get-Date).ToUniversalTime().AddDays(-365)).Date - } Out-LogFile -string "Start Date (UTC): $StartDate" -Information @@ -471,10 +471,37 @@ $StartDate = (Get-Date).ToUniversalTime().Date } - Out-LogFile -string "Final StartDate (UTC) after re-anchoring: $StartDate" -Information } + # Or if you want to be more specific and check if it was the immediate caller: + $wasDirectlyCalledByUserInvestigation = (Get-PSCallStack)[1].FunctionName -eq "Start-HawkUserInvestigation" + Out-LogFile "Was directly called by UserInvestigation: $wasDirectlyCalledByUserInvestigation" -Information + Out-LogFile (Get-PSCallStack)[1].FunctionName -Information + + if ((-not $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) -and $wasDirectlyCalledByUserInvestigation) { + Out-LogFile "Would you like to enable GeoIP Location?" -Information + Out-LogFile "An API key from ipstack.com is required." -Information + + $GeoIPResponse = '' + while ($GeoIPResponse -notin @('Y','N')) { + Out-LogFile "Enable GeoIP Location? (Y/N): " -isPrompt -NoNewLine + $GeoIPResponse = ((Read-Host).Trim()).ToUpper() + + if ($GeoIPResponse -notin @('Y','N')) { + Out-LogFile "Please enter Y or N" -Information + } + if ($GeoIPResponse -eq 'Y') { + $Hawk.EnableGeoIPLocation = $true + break + } + if ($GeoIPResponse -eq 'N') { + $Hawk.EnableGeoIPLocation = $false + break + } + } + } + # Configuration Example, currently not used #TODO: Implement Configuration system across entire project diff --git a/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 b/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 index c3141cda..c13abbc2 100644 --- a/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 +++ b/Hawk/internal/functions/Test-HawkNonInteractiveMode.ps1 @@ -38,5 +38,6 @@ Function Test-HawkNonInteractiveMode { $PSBoundParameters.ContainsKey('EndDate') -or $PSBoundParameters.ContainsKey('DaysToLookBack') -or $PSBoundParameters.ContainsKey('FilePath') -or - $PSBoundParameters.ContainsKey('SkipUpdate') + $PSBoundParameters.ContainsKey('SkipUpdate') -or + $PSBoundParameters.ContainsKey('EnableGeoIPLocation') } \ No newline at end of file From 75de8779c31935def0e53f10eb9166fe622fb7a4 Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sun, 19 Jan 2025 03:28:12 -0500 Subject: [PATCH 06/53] added help .PARAMETERS for EnableGeoIPLocation for 2 files to pass pester tests. --- Hawk/functions/User/Start-HawkUserInvestigation.ps1 | 5 +++++ Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 2384a73d..32df0a00 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -61,6 +61,11 @@ .PARAMETER WhatIf Shows what would happen if the command runs. The command is not executed. Use this parameter to understand which investigation steps would be performed without actually collecting data. + + .PARAMETER EnableGeoIPLocation + Switch to enable resolving IP addresses to geographic locations in the investigation. + This option requires an active internet connection and may increase the time needed to complete the investigation. + Providing this parameter automatically enables non-interactive mode. .OUTPUTS Creates multiple CSV and JSON files containing investigation results. diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 3b2cd26e..e7043e10 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -24,6 +24,10 @@ .PARAMETER NonInteractive Switch to run the command in non-interactive mode. Requires all necessary parameters to be provided via command line rather than through interactive prompts. + .PARAMETER EnableGeoIPLocation + Switch to enable resolving IP addresses to geographic locations in the investigation. + This option requires an active internet connection and may increase the time needed to complete the investigation. + Providing this parameter automatically enables non-interactive mode. .OUTPUTS Creates the $Hawk global variable and populates it with a custom PS object with the following properties From 54cd8327dc24e9db1c6b11d7345c2e22c4f75ecb Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sun, 19 Jan 2025 03:30:16 -0500 Subject: [PATCH 07/53] added additional information for help parameters for EnableGeoIPLocation --- Hawk/functions/User/Start-HawkUserInvestigation.ps1 | 2 ++ Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 32df0a00..29a666e5 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -67,6 +67,8 @@ This option requires an active internet connection and may increase the time needed to complete the investigation. Providing this parameter automatically enables non-interactive mode. + REQUIRED: An API key from ipstack.com is required to use this feature. + .OUTPUTS Creates multiple CSV and JSON files containing investigation results. All outputs are organized in user-specific folders under the specified FilePath directory. diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index e7043e10..ada3aa07 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -28,6 +28,8 @@ Switch to enable resolving IP addresses to geographic locations in the investigation. This option requires an active internet connection and may increase the time needed to complete the investigation. Providing this parameter automatically enables non-interactive mode. + + REQUIRED: An API key from ipstack.com is required to use this feature. .OUTPUTS Creates the $Hawk global variable and populates it with a custom PS object with the following properties From 599f733b47a2718038cb2745dbaa75eebd75dc0b Mon Sep 17 00:00:00 2001 From: dcodev1702 Date: Sun, 19 Jan 2025 12:13:50 -0500 Subject: [PATCH 08/53] Enhanced comments for deeper understanding and minor refactoring of user output. GeoIP Location working well. --- Hawk/functions/User/Get-HawkUserAuthHistory.ps1 | 6 ++++-- Hawk/functions/User/Start-HawkUserInvestigation.ps1 | 7 ++++++- Hawk/internal/functions/Clear-HawkEnvironment.ps1 | 2 +- Hawk/internal/functions/Get-IPGeolocation.ps1 | 11 ++++++----- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 b/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 index e3593d5e..4f482098 100644 --- a/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 +++ b/Hawk/functions/User/Get-HawkUserAuthHistory.ps1 @@ -13,6 +13,8 @@ Single UPN of a user, comma seperated list of UPNs, or array of objects that contain UPNs. .PARAMETER ResolveIPLocations Resolved IP Locations + + If this option is specified, it will attempt to resolve IP locations (GeoIP) using ipstack.com API (API Key Required) .OUTPUTS File: Converted_Authentication_Logs.csv @@ -100,13 +102,13 @@ } - # Add IP Geo Location information to the data + # Add Geo IP location information to the data if ($PSBoundParameters.ContainsKey('ResolveIPLocations')) { Out-LogFile "Resolving IP Locations" -Action # Setup our counter $i = 0 - # Loop thru each connection and get the location + # Loop thru each connection and get the Geo IP location while ($i -lt $ExpandedUserLogonLogs.Count) { if ([bool]($i % 25)) { } diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 29a666e5..230cd9db 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -132,11 +132,13 @@ } try { + # Call Initialize-HawkGlobalObject in case of non-interactive mode if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive -EnableGeoIPLocation:$EnableGeoIPLocation - } else { + } else { + # Call Initialize-HawkGlobalObject in case of interactive mode for EnableGeoIPLocation Initialize-HawkGlobalObject -StartDate $StartDate -EndDate $EndDate ` -DaysToLookBack $DaysToLookBack -FilePath $FilePath ` -SkipUpdate:$SkipUpdate -NonInteractive:$NonInteractive @@ -201,6 +203,9 @@ if ($PSCmdlet.ShouldProcess("Running Get-HawkUserAuthHistory for $User")) { Out-LogFile "Running Get-HawkUserAuthHistory" -Action + # Two different use cases (interactive and non-interactive) have to be considered here + # $Hawk.EnableGeoIPLocation is to account for interactive mode + # $PSBoundParameters.ContainsKey('EnableGeoIPLocation') is to account for non-interactive mode if ($Hawk.EnableGeoIPLocation -or $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { Out-LogFile "Calling Get-HawkUserAuthHistory WITH ResolveIPLocations enabled." -Information Get-HawkUserAuthHistory -User $User -ResolveIPLocations diff --git a/Hawk/internal/functions/Clear-HawkEnvironment.ps1 b/Hawk/internal/functions/Clear-HawkEnvironment.ps1 index 7d63a3cf..1e232813 100644 --- a/Hawk/internal/functions/Clear-HawkEnvironment.ps1 +++ b/Hawk/internal/functions/Clear-HawkEnvironment.ps1 @@ -33,7 +33,7 @@ Function Clear-HawkEnvironment { } } - # Clear the error variable + # Clear error variables $Error.Clear() Write-Verbose "Hawk environment cleanup completed successfully" diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 21953e5f..a9d9d59b 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -32,10 +32,10 @@ Function Get-IPGeolocation { # if there is no value of access_key then we need to get it from the user if ([string]::IsNullOrEmpty($HawkAppData.access_key)) { - Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API.`nPlease get a Free access key from https://ipstack.com/ and provide it below." -Information + Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API." -Information + Out-LogFile "Please get a Free access key from https://ipstack.com/ and provide it below." -Information - Out-LogFile "`nIP Stack API Key Configuration" -Action - Out-LogFile "Get your free API key at: https://ipstack.com/`n" -Action + Out-LogFile "Get your free API key at: https://ipstack.com/" -Information # get the access key from the user Out-LogFile "Provide your IP Stack API key: " -isPrompt -NoNewLine @@ -47,7 +47,7 @@ Function Get-IPGeolocation { throw "API key cannot be empty or whitespace." } - # If testing is requested, validate the key + # Geo IP location is requested, validate the key first (using Google DNS). if ($AccessKey) { Out-LogFile "Testing API key against Google DNS..." -Action $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" @@ -60,7 +60,7 @@ Function Get-IPGeolocation { } Out-LogFile "API key validated successfully!" -Information - # Save to disk + # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) Out-HawkAppData } catch { @@ -74,6 +74,7 @@ Function Get-IPGeolocation { Add-HawkAppData -name access_key -Value $AccessKey } else { + # API Key is already exists from the appdata file (Hawk\Hawk.json) $AccessKey = $HawkAppData.access_key } } From 1cfbc10faa5511daaffa2cec7c750b54853f36fa Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 20 Jan 2025 10:29:30 -0500 Subject: [PATCH 09/53] Add internal helper function to rest hawk environment. --- .../functions/Reset-HawkEnvironment.ps1 | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 Hawk/internal/functions/Reset-HawkEnvironment.ps1 diff --git a/Hawk/internal/functions/Reset-HawkEnvironment.ps1 b/Hawk/internal/functions/Reset-HawkEnvironment.ps1 new file mode 100644 index 00000000..aed0b4b1 --- /dev/null +++ b/Hawk/internal/functions/Reset-HawkEnvironment.ps1 @@ -0,0 +1,87 @@ +Function Reset-HawkEnvironment { + <# + .SYNOPSIS + Resets all Hawk-related variables to allow for a fresh instance. + + .DESCRIPTION + This function removes all global variables used by Hawk, including the main Hawk object, + IP location cache, and Microsoft IP list. This allows you to start fresh with Hawk + without needing to close and reopen your PowerShell window. + + Variables removed: + - $Hawk (Main configuration object) + - $IPlocationCache (IP geolocation cache) + - $MSFTIPList (Microsoft IP address list) + - $HawkAppData (Application data) + + .EXAMPLE + Reset-HawkEnvironment + + Removes all Hawk-related variables and confirms when ready for a fresh start. + + .EXAMPLE + Reset-HawkEnvironment -Verbose + + Removes all Hawk-related variables with detailed progress messages. + + .EXAMPLE + Reset-HawkEnvironment -WhatIf + + Shows what variables would be removed without actually removing them. + + .NOTES + Author: Jonathan Butler + Version: 1.0 + Last Modified: 2025-01-20 + + This function should be used when you need to start a fresh Hawk investigation + without closing your PowerShell session. + #> + [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] + param() + + # Store original preference + $originalInformationPreference = $InformationPreference + $InformationPreference = 'Continue' + + Write-Information "Beginning Hawk environment cleanup..." + + # List of known Hawk-related variables to remove + $hawkVariables = @( + 'Hawk', # Main Hawk configuration object + 'IPlocationCache', # IP geolocation cache + 'MSFTIPList', # Microsoft IP address list + 'HawkAppData' # Hawk application data + ) + + foreach ($varName in $hawkVariables) { + if (Get-Variable -Name $varName -ErrorAction SilentlyContinue) { + try { + if ($PSCmdlet.ShouldProcess("Variable $varName", "Remove")) { + Remove-Variable -Name $varName -Scope Global -Force -ErrorAction Stop + Write-Information "Successfully removed `$$varName" + } + } + catch { + Write-Warning "Failed to remove `$$varName : $_" + } + } + else { + Write-Information "`$$varName was not present" + } + } + + # Clear any PSFramework configuration cache + if ($PSCmdlet.ShouldProcess("PSFramework cache", "Clear")) { + if (Get-Command -Name 'Clear-PSFResultCache' -ErrorAction SilentlyContinue) { + Clear-PSFResultCache + Write-Information "Cleared PSFramework result cache" + } + } + + Write-Information "`nHawk environment has been reset!" + Write-Information "You can now run Initialize-HawkGlobalObject for a fresh start.`n" + + # Restore original preference + $InformationPreference = $originalInformationPreference +} \ No newline at end of file From 33bc25a8d00628e57e5dbcb1b245bd31ad03afac Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 20 Jan 2025 10:30:35 -0500 Subject: [PATCH 10/53] Add internal helper function to rest hawk environment. --- Hawk/internal/functions/Reset-HawkEnvironment.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Hawk/internal/functions/Reset-HawkEnvironment.ps1 b/Hawk/internal/functions/Reset-HawkEnvironment.ps1 index aed0b4b1..cb0a9af7 100644 --- a/Hawk/internal/functions/Reset-HawkEnvironment.ps1 +++ b/Hawk/internal/functions/Reset-HawkEnvironment.ps1 @@ -14,6 +14,14 @@ Function Reset-HawkEnvironment { - $MSFTIPList (Microsoft IP address list) - $HawkAppData (Application data) + .PARAMETER Confirm + Prompts for confirmation before executing the command. + Specify -Confirm:$false to suppress confirmation prompts. + + .PARAMETER WhatIf + Shows what would happen if the command runs. + The command is not executed. + .EXAMPLE Reset-HawkEnvironment From 2fb3c961f18c626f5c96b92e6d64db7cdc20baad Mon Sep 17 00:00:00 2001 From: Jonathan Butler Date: Mon, 20 Jan 2025 15:52:39 -0500 Subject: [PATCH 11/53] Delete Hawk/internal/functions/Reset-HawkEnvironment.ps1 --- .../functions/Reset-HawkEnvironment.ps1 | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 Hawk/internal/functions/Reset-HawkEnvironment.ps1 diff --git a/Hawk/internal/functions/Reset-HawkEnvironment.ps1 b/Hawk/internal/functions/Reset-HawkEnvironment.ps1 deleted file mode 100644 index cb0a9af7..00000000 --- a/Hawk/internal/functions/Reset-HawkEnvironment.ps1 +++ /dev/null @@ -1,95 +0,0 @@ -Function Reset-HawkEnvironment { - <# - .SYNOPSIS - Resets all Hawk-related variables to allow for a fresh instance. - - .DESCRIPTION - This function removes all global variables used by Hawk, including the main Hawk object, - IP location cache, and Microsoft IP list. This allows you to start fresh with Hawk - without needing to close and reopen your PowerShell window. - - Variables removed: - - $Hawk (Main configuration object) - - $IPlocationCache (IP geolocation cache) - - $MSFTIPList (Microsoft IP address list) - - $HawkAppData (Application data) - - .PARAMETER Confirm - Prompts for confirmation before executing the command. - Specify -Confirm:$false to suppress confirmation prompts. - - .PARAMETER WhatIf - Shows what would happen if the command runs. - The command is not executed. - - .EXAMPLE - Reset-HawkEnvironment - - Removes all Hawk-related variables and confirms when ready for a fresh start. - - .EXAMPLE - Reset-HawkEnvironment -Verbose - - Removes all Hawk-related variables with detailed progress messages. - - .EXAMPLE - Reset-HawkEnvironment -WhatIf - - Shows what variables would be removed without actually removing them. - - .NOTES - Author: Jonathan Butler - Version: 1.0 - Last Modified: 2025-01-20 - - This function should be used when you need to start a fresh Hawk investigation - without closing your PowerShell session. - #> - [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] - param() - - # Store original preference - $originalInformationPreference = $InformationPreference - $InformationPreference = 'Continue' - - Write-Information "Beginning Hawk environment cleanup..." - - # List of known Hawk-related variables to remove - $hawkVariables = @( - 'Hawk', # Main Hawk configuration object - 'IPlocationCache', # IP geolocation cache - 'MSFTIPList', # Microsoft IP address list - 'HawkAppData' # Hawk application data - ) - - foreach ($varName in $hawkVariables) { - if (Get-Variable -Name $varName -ErrorAction SilentlyContinue) { - try { - if ($PSCmdlet.ShouldProcess("Variable $varName", "Remove")) { - Remove-Variable -Name $varName -Scope Global -Force -ErrorAction Stop - Write-Information "Successfully removed `$$varName" - } - } - catch { - Write-Warning "Failed to remove `$$varName : $_" - } - } - else { - Write-Information "`$$varName was not present" - } - } - - # Clear any PSFramework configuration cache - if ($PSCmdlet.ShouldProcess("PSFramework cache", "Clear")) { - if (Get-Command -Name 'Clear-PSFResultCache' -ErrorAction SilentlyContinue) { - Clear-PSFResultCache - Write-Information "Cleared PSFramework result cache" - } - } - - Write-Information "`nHawk environment has been reset!" - Write-Information "You can now run Initialize-HawkGlobalObject for a fresh start.`n" - - # Restore original preference - $InformationPreference = $originalInformationPreference -} \ No newline at end of file From c0c3a6bf0205ced7f31f051217f54a87af764c99 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Thu, 6 Feb 2025 20:31:16 -0500 Subject: [PATCH 12/53] Initial commit on new workstation --- .../User/Start-HawkUserInvestigation.ps1 | 15 ++++++--------- .../functions/Initialize-HawkGlobalObject.ps1 | 6 ++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 79d66364..1d4dc4ea 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -153,6 +153,7 @@ process { if (Test-PSFFunctionInterrupt) { return } + # Be sure to remove comments after testing! if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { Initialize-HawkGlobalObject -EnableGeoIPLocation Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject with EnableGeoIPLocation" -Information @@ -206,21 +207,17 @@ Get-HawkUserEntraIDSignInLog -UserPrincipalName $User } - if ($PSCmdlet.ShouldProcess("Running Get-HawkUserAuthHistory for $User")) { - Out-LogFile "Running Get-HawkUserAuthHistory" -Action + if ($PSCmdlet.ShouldProcess("Running Get-HawkUserUALSignInLog for $User")) { # Two different use cases (interactive and non-interactive) have to be considered here # $Hawk.EnableGeoIPLocation is to account for interactive mode # $PSBoundParameters.ContainsKey('EnableGeoIPLocation') is to account for non-interactive mode if ($Hawk.EnableGeoIPLocation -or $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { - Out-LogFile "Calling Get-HawkUserAuthHistory WITH ResolveIPLocations enabled." -Information - Get-HawkUserAuthHistory -User $User -ResolveIPLocations + Out-LogFile "Running Get-HawkUserUALSignInLog and resolving IP locations" -Action + Get-HawkUserUALSignInLog -User $User -ResolveIPLocations } else { - Out-LogFile "Calling Get-HawkUserAuthHistory WITHOUT ResolveIPLocations enabled." -Information - Get-HawkUserAuthHistory -User $User + Out-LogFile "Running Get-HawkUserUALSignInLog without resolving IP locations" -Action + Get-HawkUserUALSignInLog -User $User } - if ($PSCmdlet.ShouldProcess("Running Get-HawkUserUALSignInLog for $User")) { - Out-LogFile "Running Get-HawkUserUALSignInLog" -Action - Get-HawkUserUALSignInLog -User $User -ResolveIPLocations } if ($PSCmdlet.ShouldProcess("Running Get-HawkUserMailboxAuditing for $User")) { diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 8e08d681..e304ed44 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -530,7 +530,9 @@ } + # Be sure to remove the two comments below once you're done validating the logic # Or if you want to be more specific and check if it was the immediate caller: + # I believe the logic is incorrect here. It should be checking if the immediate caller was UserInvestigation $wasDirectlyCalledByUserInvestigation = (Get-PSCallStack)[1].FunctionName -eq "Start-HawkUserInvestigation" Out-LogFile "Was directly called by UserInvestigation: $wasDirectlyCalledByUserInvestigation" -Information Out-LogFile (Get-PSCallStack)[1].FunctionName -Information @@ -558,6 +560,10 @@ } } + if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { + $Hawk.EnableGeoIPLocation = $EnableGeoIPLocation + } + # Configuration Example, currently not used From 3bea42ab545fbaa3714181ba2afc29c9fcd245a4 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Thu, 6 Feb 2025 22:27:19 -0500 Subject: [PATCH 13/53] Validated use cases for EnableGeoIPLocation for interactive and non-interactive modes where an API Key (IPSTACK) does not exist and is provided. --- .../User/Start-HawkUserInvestigation.ps1 | 20 +++++---- .../functions/Clear-HawkEnvironment.ps1 | 45 ------------------- 2 files changed, 12 insertions(+), 53 deletions(-) delete mode 100644 Hawk/internal/functions/Clear-HawkEnvironment.ps1 diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index 1d4dc4ea..ce703ba4 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -154,17 +154,18 @@ if (Test-PSFFunctionInterrupt) { return } # Be sure to remove comments after testing! - if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { - Initialize-HawkGlobalObject -EnableGeoIPLocation - Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject with EnableGeoIPLocation" -Information - } else { - Initialize-HawkGlobalObject - Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject without EnableGeoIPLocation" -Information - } + #if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { + # Initialize-HawkGlobalObject -EnableGeoIPLocation + # Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject with EnableGeoIPLocation" -Information + #} else { + # Initialize-HawkGlobalObject + # Out-LogFile "START-HAWKUSERINVESTIGATION -> Calling Initialize-HawkGlobalObject without EnableGeoIPLocation" -Information + #} # Check if Hawk object exists and is fully initialized if (Test-HawkGlobalObject) { Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to TRUE!" -Information + Initialize-HawkGlobalObject } else { Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to FALSE!" -Information } @@ -182,6 +183,7 @@ foreach ($Object in $UserArray) { [string]$User = $Object.UserPrincipalName + <# if ($PSCmdlet.ShouldProcess("Running Get-HawkUserConfiguration for $User")) { Out-LogFile "Running Get-HawkUserConfiguration" -Action Get-HawkUserConfiguration -User $User @@ -206,7 +208,7 @@ Out-LogFile "Running Get-HawkUserEntraIDSignInLog" -Action Get-HawkUserEntraIDSignInLog -UserPrincipalName $User } - + #> if ($PSCmdlet.ShouldProcess("Running Get-HawkUserUALSignInLog for $User")) { # Two different use cases (interactive and non-interactive) have to be considered here # $Hawk.EnableGeoIPLocation is to account for interactive mode @@ -220,6 +222,7 @@ } } + <# if ($PSCmdlet.ShouldProcess("Running Get-HawkUserMailboxAuditing for $User")) { Out-LogFile "Running Get-HawkUserMailboxAuditing" -Action Get-HawkUserMailboxAuditing -User $User @@ -258,6 +261,7 @@ Out-LogFile "Running Get-HawkUserMobileDevice" -Action Get-HawkUserMobileDevice -User $User } + #> } } diff --git a/Hawk/internal/functions/Clear-HawkEnvironment.ps1 b/Hawk/internal/functions/Clear-HawkEnvironment.ps1 deleted file mode 100644 index 1e232813..00000000 --- a/Hawk/internal/functions/Clear-HawkEnvironment.ps1 +++ /dev/null @@ -1,45 +0,0 @@ -Function Clear-HawkEnvironment { - <# - .SYNOPSIS - Cleans up Hawk global variables and environment state. - - .DESCRIPTION - Removes Hawk-specific global variables and cleans up the PowerShell environment - after Hawk operations complete. This prevents state persistence between runs - and ensures a clean environment for subsequent executions. - - .EXAMPLE - Clear-HawkEnvironment - - Cleans up all Hawk global variables and environment state. - #> - [CmdletBinding()] - param() - - try { - # List of known Hawk global variables to remove - $hawkGlobals = @( - 'Hawk', - 'HawkAppData', - 'MSFTIPList', - 'IPLocationCache' - ) - - # Remove each Hawk global variable if it exists - foreach ($varName in $hawkGlobals) { - if (Get-Variable -Name $varName -ErrorAction SilentlyContinue) { - Remove-Variable -Name $varName -Scope Global -Force -ErrorAction SilentlyContinue - Write-Verbose "Removed global variable: $varName" - } - } - - # Clear error variables - $Error.Clear() - - Write-Verbose "Hawk environment cleanup completed successfully" - } - catch { - Write-Warning "Error during Hawk environment cleanup: $_" - # Don't throw here - we don't want cleanup failures to affect the user - } -} \ No newline at end of file From 7c9b2b82e3ffd40967bccabaeee5ce572752f76c Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Thu, 6 Feb 2025 22:56:26 -0500 Subject: [PATCH 14/53] Fixed cosmetic output in Hawk Configuration Summary - 'Enable Geo IP Location' --- Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 4 ++++ .../functions/Write-HawkConfigurationComplete.ps1 | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index e304ed44..eead138a 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -533,6 +533,10 @@ # Be sure to remove the two comments below once you're done validating the logic # Or if you want to be more specific and check if it was the immediate caller: # I believe the logic is incorrect here. It should be checking if the immediate caller was UserInvestigation + # FIX-ME: modify -eq to startswith bec there are cases where we can have or and we just + # need to account for Start-HawkUserInvestigation getting called! + # FIX-ME: Account for the logic case where Get-HawkUserUALSignInLogs is called + # ((Get-PSCallStack)[1].FunctionName -contains "Get-HawkUserUALSignInLog") -or (Get-PSCallStack)[1].FunctionName -startswith "Start-HawkUserInvestigation) $wasDirectlyCalledByUserInvestigation = (Get-PSCallStack)[1].FunctionName -eq "Start-HawkUserInvestigation" Out-LogFile "Was directly called by UserInvestigation: $wasDirectlyCalledByUserInvestigation" -Information Out-LogFile (Get-PSCallStack)[1].FunctionName -Information diff --git a/Hawk/internal/functions/Write-HawkConfigurationComplete.ps1 b/Hawk/internal/functions/Write-HawkConfigurationComplete.ps1 index 43e9df0e..5317c909 100644 --- a/Hawk/internal/functions/Write-HawkConfigurationComplete.ps1 +++ b/Hawk/internal/functions/Write-HawkConfigurationComplete.ps1 @@ -53,10 +53,15 @@ } # Format property names and create array of formatted names + # Use Claude to help format properties $formattedNames = @() foreach ($prop in $properties) { - $name = $prop.Name -creplace '([A-Z])', ' $1' -replace '_', ' ' - $formattedNames += $name.Trim() + if ($prop.Name -eq 'EnableGeoIPLocation') { + $formattedNames += "Enable Geo IP Location" + } else { + $name = $prop.Name -creplace '([A-Z])', ' $1' -replace '_', ' ' + $formattedNames += $name.Trim() + } } # Find the longest property name From 7726dfedadc0ef06d92e9b17f20b3e9183bd2396 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 00:36:45 -0500 Subject: [PATCH 15/53] Added checks for any 1 of 3 CmdLets were called to validate the use of EnableGeoIPLocation --- .../functions/Initialize-HawkGlobalObject.ps1 | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index eead138a..b0abeb11 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -536,12 +536,13 @@ # FIX-ME: modify -eq to startswith bec there are cases where we can have or and we just # need to account for Start-HawkUserInvestigation getting called! # FIX-ME: Account for the logic case where Get-HawkUserUALSignInLogs is called - # ((Get-PSCallStack)[1].FunctionName -contains "Get-HawkUserUALSignInLog") -or (Get-PSCallStack)[1].FunctionName -startswith "Start-HawkUserInvestigation) - $wasDirectlyCalledByUserInvestigation = (Get-PSCallStack)[1].FunctionName -eq "Start-HawkUserInvestigation" - Out-LogFile "Was directly called by UserInvestigation: $wasDirectlyCalledByUserInvestigation" -Information + $BoolDirectlyCalledByUserInvestigation = ((Get-PSCallStack)[1].FunctionName -like "Start-HawkUserInvestigation*") -or ` + ((Get-PSCallStack)[1].FunctionName -like "Get-HawkUserUALSignInLog*") -or ` + ((Get-PSCallStack)[1].FunctionName -like "Get-HawkUserEntraIDSignInLog*") + Out-LogFile "Was directly called by: $BoolDirectlyCalledByUserInvestigation" -Information Out-LogFile (Get-PSCallStack)[1].FunctionName -Information - if ((-not $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) -and $wasDirectlyCalledByUserInvestigation) { + if ((-not $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) -and $BoolDirectlyCalledByUserInvestigation) { Out-LogFile "Would you like to enable GeoIP Location?" -Information Out-LogFile "An API key from ipstack.com is required." -Information @@ -569,7 +570,6 @@ } - # Configuration Example, currently not used #TODO: Implement Configuration system across entire project Set-PSFConfig -Module 'Hawk' -Name 'DaysToLookBack' -Value $Days -PassThru | Register-PSFConfig @@ -578,8 +578,6 @@ } - - # Continue populating the Hawk object with other properties $Hawk.DaysToLookBack = $DaysToLookBack $Hawk.StartDate = $StartDate From b7acab58887cd73aa24c6d2063b2613b46e341b0 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 10:19:40 -0500 Subject: [PATCH 16/53] Fleshing out logic for API Access Key if absored from file or provided by user input. --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 80 ++++++++++++------- Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 63 +++++++++++++++ 2 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 Hawk/internal/functions/Test-GeoIPAPIKey.ps1 diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index a9d9d59b..a6bc90a4 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -23,6 +23,7 @@ Function Get-IPGeolocation { # Read in existing HawkAppData if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { Read-HawkAppData + [string]$AccessKeyOnFile = $HawkAppData.access_key } } @@ -30,7 +31,7 @@ Function Get-IPGeolocation { # MOVE THIS IPSTACK API CODE CHECK TO Get-HawkUserAuthHistory within the ResolveIPLocations block!! try { # if there is no value of access_key then we need to get it from the user - if ([string]::IsNullOrEmpty($HawkAppData.access_key)) { + if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API." -Information Out-LogFile "Please get a Free access key from https://ipstack.com/ and provide it below." -Information @@ -41,42 +42,65 @@ Function Get-IPGeolocation { Out-LogFile "Provide your IP Stack API key: " -isPrompt -NoNewLine $AccessKey = (Read-Host).Trim() - # Validate key format (basic check) - if ([string]::IsNullOrWhiteSpace($AccessKey)) { - Out-LogFile "API key cannot be empty or whitespace." -isError - throw "API key cannot be empty or whitespace." + # TEST FOR EMPTY STRING BEFORE CALLING TEST-GEOIPAPIKEY + # Check for empty string or null entered by the user. + if ([string]::IsNullOrEmpty($AccessKey)) { + Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError + throw "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." } + $IsValidAccessKey = Test-GeoIPAPIKey -Key $AccessKey - # Geo IP location is requested, validate the key first (using Google DNS). - if ($AccessKey) { - Out-LogFile "Testing API key against Google DNS..." -Action - $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" - - try { - $response = Invoke-RestMethod -Uri $testUrl -Method Get - if ($response.success -eq $false) { - Out-LogFile "API key validation failed: $($response.error.info)" -isError - throw "API key validation failed: $($response.error.info)" - } - Out-LogFile "API key validated successfully!" -Information - - # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) - Out-HawkAppData + } + + if (![string]::IsNullOrEmpty($AccessKeyFromFile)) { + try { + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + $AccessKey = $AccessKeyFromFile + $IsValidAccessKey = $true } - catch { - Out-LogFile "API key validation failed: $_" -isError - throw "API key validation failed: $_" + } catch { + Out-LogFile "API key is malformed!" -isError + throw "API key validation failed: $($response.error.info)" + } + + } + # Validate key format (basic check) + #if ([string]::IsNullOrWhiteSpace($AccessKey)) { + # Out-LogFile "API key cannot be empty or whitespace." -isError + # throw "API key cannot be empty or whitespace." + #} + + # Geo IP location is requested, validate the key first (using Google DNS). + if ($IsValidAccessKey) { + Out-LogFile "Testing API key against Google DNS..." -Action + $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" + + try { + $response = Invoke-RestMethod -Uri $testUrl -Method Get + if ($response.success -eq $false) { + Out-LogFile "API key validation failed: $($response.error.info)" -isError + throw "API key validation failed: $($response.error.info)" } + Out-LogFile "API key validated successfully!" -Information + + # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) + # PROMPT USER TO SEE IF THEY WANT TO WRITE API KEY TO DISK (PLAINTEXT) + # The ipstack API key is valid. Add the access key to the appdata file + Add-HawkAppData -name access_key -Value $AccessKey + Out-HawkAppData } + catch { + Out-LogFile "API key validation failed: $_" -isError + throw "API key validation failed: $_" - # The ipstack API key is valid. Add the access key to the appdata file - Add-HawkAppData -name access_key -Value $AccessKey + } } - else { + + #else { # API Key is already exists from the appdata file (Hawk\Hawk.json) - $AccessKey = $HawkAppData.access_key - } + # $AccessKey = $HawkAppData.access_key + #} } catch { Out-LogFile "Failed to update IP Stack API key: $_" -isError diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 new file mode 100644 index 00000000..9a5a016e --- /dev/null +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -0,0 +1,63 @@ +Function Test-GeoIPAPIKey { + <# + .SYNOPSIS + Create global variable $Hawk for use by all Hawk cmdlets. + .DESCRIPTION + Creates the global variable $Hawk and populates it with information needed by the other Hawk cmdlets. + + * Checks for latest version of the Hawk module + * Creates path for output files + * Records target start and end dates for searches (in UTC) + .PARAMETER Force + Switch to force the function to run and allow the variable to be recreated + .PARAMETER SkipUpdate + Skips checking for the latest version of the Hawk Module + .PARAMETER DaysToLookBack + Defines the # of days to look back in the availible logs. + Valid values are 1-90 + .PARAMETER StartDate + First day that data will be retrieved (in UTC) + .PARAMETER EndDate + Last day that data will be retrieved (in UTC) + .PARAMETER FilePath + Provide an output file path. + .PARAMETER NonInteractive + Switch to run the command in non-interactive mode. Requires all necessary parameters + to be provided via command line rather than through interactive prompts. + .PARAMETER EnableGeoIPLocation + Switch to enable resolving IP addresses to geographic locations in the investigation. + This option requires an active internet connection and may increase the time needed to complete the investigation. + Providing this parameter automatically enables non-interactive mode. + + REQUIRED: An API key from ipstack.com is required to use this feature. + .OUTPUTS + Creates the $Hawk global variable and populates it with a custom PS object with the following properties + + Property Name Contents + ========== ========== + FilePath Path to output files + DaysToLookBack Number of day back in time we are searching + StartDate Calculated start date for searches based on DaysToLookBack (UTC) + EndDate One day in the future (UTC) + WhenCreated Date and time that the variable was created (UTC) + .EXAMPLE + Initialize-HawkGlobalObject -Force + + This Command will force the creation of a new $Hawk variable even if one already exists. + #> + param ( + [Parameter(Mandatory)] + [string]$Key + ) + + process { + + # Check length is 32 characters + if ($Key.Length -ne 32) { + return $false + } + + # Check each character is valid hex using regex + return ($Key -match '^[0-9a-f]{32}$') + } +} \ No newline at end of file From bb91e8f0bec8d599feb7a65c465e839b69f8cba1 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 13:26:35 -0500 Subject: [PATCH 17/53] Still narrowing down the bug. Additional checks for malformed and bad keys are working. User is now being prompted to save API KEY. Need to return when the correct key is entered and saved to disk or not --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 116 +++++++++++------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index a6bc90a4..c51dc281 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -28,84 +28,108 @@ Function Get-IPGeolocation { } process { - # MOVE THIS IPSTACK API CODE CHECK TO Get-HawkUserAuthHistory within the ResolveIPLocations block!! try { - # if there is no value of access_key then we need to get it from the user + # If there is no value of access_key then we need to get it from the user if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { - Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API." -Information Out-LogFile "Please get a Free access key from https://ipstack.com/ and provide it below." -Information - Out-LogFile "Get your free API key at: https://ipstack.com/" -Information - - # get the access key from the user + + # Get the access key from the user Out-LogFile "Provide your IP Stack API key: " -isPrompt -NoNewLine - $AccessKey = (Read-Host).Trim() - - # TEST FOR EMPTY STRING BEFORE CALLING TEST-GEOIPAPIKEY - # Check for empty string or null entered by the user. + $AccessKey = (Read-Host).Trim() + + # Check for empty string or null entered by the user if ([string]::IsNullOrEmpty($AccessKey)) { Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError - throw "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." + return } + + # Only test the key if it's not empty $IsValidAccessKey = Test-GeoIPAPIKey -Key $AccessKey - + if (-not $IsValidAccessKey) { + Out-LogFile "API key validation failed" -isError + return + } } - - if (![string]::IsNullOrEmpty($AccessKeyFromFile)) { + # Handle existing key from file + elseif (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { try { if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { $AccessKey = $AccessKeyFromFile $IsValidAccessKey = $true } - } catch { + } + catch { Out-LogFile "API key is malformed!" -isError throw "API key validation failed: $($response.error.info)" } - } + } + catch { + Out-LogFile "An unexpected error occurred: $_" -isError + throw + } - # Validate key format (basic check) - #if ([string]::IsNullOrWhiteSpace($AccessKey)) { - # Out-LogFile "API key cannot be empty or whitespace." -isError - # throw "API key cannot be empty or whitespace." - #} + # Validate key format (basic check) + #if ([string]::IsNullOrWhiteSpace($AccessKey)) { + # Out-LogFile "API key cannot be empty or whitespace." -isError + # throw "API key cannot be empty or whitespace." + #} - # Geo IP location is requested, validate the key first (using Google DNS). - if ($IsValidAccessKey) { - Out-LogFile "Testing API key against Google DNS..." -Action - $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" - - try { - $response = Invoke-RestMethod -Uri $testUrl -Method Get - if ($response.success -eq $false) { - Out-LogFile "API key validation failed: $($response.error.info)" -isError - throw "API key validation failed: $($response.error.info)" - } - Out-LogFile "API key validated successfully!" -Information + # Geo IP location is requested, validate the key first (using Google DNS). + if ($IsValidAccessKey) { + Out-LogFile "Testing API key against Google DNS..." -Action + $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" + + try { + $response = Invoke-RestMethod -Uri $testUrl -Method Get + if ($response.success -eq $false) { + Out-LogFile "API key validation failed: $($response.error.info)" -isError + return "API key validation failed: $($response.error.info)" + } + Out-LogFile "API key validated successfully!" -Information + + # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) + # PROMPT USER TO SEE IF THEY WANT TO WRITE API KEY TO DISK (PLAINTEXT) + # The ipstack API key is valid. Add the access key to the appdata file + # Prompt user about saving the API key + Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine + $saveChoice = (Read-Host).Trim().ToUpper() - # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) - # PROMPT USER TO SEE IF THEY WANT TO WRITE API KEY TO DISK (PLAINTEXT) - # The ipstack API key is valid. Add the access key to the appdata file + if ($saveChoice -eq 'Y') { + # Save to disk Add-HawkAppData -name access_key -Value $AccessKey - Out-HawkAppData - } - catch { - Out-LogFile "API key validation failed: $_" -isError - throw "API key validation failed: $_" + # Display warning banner about storage location + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "`nWARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + return } + else { + Out-LogFile "API key will not be saved to disk." -Information + return + } + #Add-HawkAppData -name access_key -Value $AccessKey + #Out-HawkAppData + } + catch { + Out-LogFile "API key validation failed: $_" -isError + throw "API key validation failed: $_" + } + } #else { # API Key is already exists from the appdata file (Hawk\Hawk.json) # $AccessKey = $HawkAppData.access_key #} - } - catch { - Out-LogFile "Failed to update IP Stack API key: $_" -isError - throw "Failed to update IP Stack API key: $_" - } + #} + #catch { + # Out-LogFile "Failed to update IP Stack API key: $_" -isError + # throw "Failed to update IP Stack API key: $_" + #} From 595fd23e62baa54c23a5b973340a9f41e6d7087b Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 13:27:55 -0500 Subject: [PATCH 18/53] Still narrowing down the bug. Additional checks for malformed and bad keys are working. User is now being prompted to save API KEY. Need to return when the correct key is entered and saved to disk or not --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index c51dc281..b58c0d38 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -111,8 +111,6 @@ Function Get-IPGeolocation { Out-LogFile "API key will not be saved to disk." -Information return } - #Add-HawkAppData -name access_key -Value $AccessKey - #Out-HawkAppData } catch { Out-LogFile "API key validation failed: $_" -isError @@ -121,17 +119,6 @@ Function Get-IPGeolocation { } } - #else { - # API Key is already exists from the appdata file (Hawk\Hawk.json) - # $AccessKey = $HawkAppData.access_key - #} - #} - #catch { - # Out-LogFile "Failed to update IP Stack API key: $_" -isError - # throw "Failed to update IP Stack API key: $_" - #} - - # Check the global IP cache and see if we already have the IP there if ($IPLocationCache.ip -contains $IPAddress) { From 26dfdbc409a7d04ad3d9fd206fd69d29254248fb Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 15:53:28 -0500 Subject: [PATCH 19/53] If key exists on disk and user chooses to use it, we will validate its still good but will NOT prompt the user the option to save it to disk. We will detect bad keys, malformed keys, and if an access key is provided from the user, they will be prompted the option to save the key to disk with the proper warning at the API key is currently stored in plaintext. --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 97 +++++++++++++------ Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 6 ++ 2 files changed, 72 insertions(+), 31 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index b58c0d38..c62922be 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -23,13 +23,13 @@ Function Get-IPGeolocation { # Read in existing HawkAppData if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { Read-HawkAppData - [string]$AccessKeyOnFile = $HawkAppData.access_key + [string]$AccessKeyFromFile = $HawkAppData.access_key } } process { try { - # If there is no value of access_key then we need to get it from the user + # If there is no access_key on disk (file) then we need to get it from the user if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API." -Information Out-LogFile "Please get a Free access key from https://ipstack.com/ and provide it below." -Information @@ -45,9 +45,9 @@ Function Get-IPGeolocation { return } - # Only test the key if it's not empty - $IsValidAccessKey = Test-GeoIPAPIKey -Key $AccessKey - if (-not $IsValidAccessKey) { + # Test the existing key + $IsExistingValidAccessKey = Test-GeoIPAPIKey -Key $AccessKey + if (-not $IsExistingValidAccessKey) { Out-LogFile "API key validation failed" -isError return } @@ -55,9 +55,41 @@ Function Get-IPGeolocation { # Handle existing key from file elseif (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { try { - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - $AccessKey = $AccessKeyFromFile - $IsValidAccessKey = $true + # Get last 6 characters of the API key + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + + # Prompt user about using existing key + Out-LogFile "Found existing API key ending in: $maskedKey" -Information + Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine + $useExistingKey = (Read-Host).Trim().ToUpper() + + if ($useExistingKey -eq 'Y') { + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + # Set the API access key from the file to $AccessKey, which is used to test against IP Stack API. + $AccessKey = $AccessKeyFromFile + + # This is to ensure the user doesn't get prompted to save a key that is already on disk + $IsExistingValidAccessKey = $true + Out-LogFile "Using existing API key from disk." -Information + break # USE RETURN OR BREAK!!!! + } + + } + else { + # No access key was obtained from the file on disk + # This is to ensure the user doesn't get prompted to save a key that is already on disk + $IsExistingValidAccessKey = $false + Out-LogFile "Please provide a new IP Stack API key: " -isPrompt -NoNewLine + $AccessKey = (Read-Host).Trim() + + # Check for empty string or null entered by the user + if ([string]::IsNullOrEmpty($AccessKey)) { + Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError + return + } + + # Test the new key + $IsValidUserEnteredAccessKey = Test-GeoIPAPIKey -Key $AccessKey } } catch { @@ -68,7 +100,7 @@ Function Get-IPGeolocation { } catch { Out-LogFile "An unexpected error occurred: $_" -isError - throw + throw "An unexpected error occurred: $_" } # Validate key format (basic check) @@ -78,7 +110,7 @@ Function Get-IPGeolocation { #} # Geo IP location is requested, validate the key first (using Google DNS). - if ($IsValidAccessKey) { + if ($IsValidUserEnteredAccessKey -or $IsExistingValidAccessKey) { Out-LogFile "Testing API key against Google DNS..." -Action $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" @@ -86,36 +118,38 @@ Function Get-IPGeolocation { $response = Invoke-RestMethod -Uri $testUrl -Method Get if ($response.success -eq $false) { Out-LogFile "API key validation failed: $($response.error.info)" -isError - return "API key validation failed: $($response.error.info)" + throw "API key validation failed: $($response.error.info)" } Out-LogFile "API key validated successfully!" -Information # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) # PROMPT USER TO SEE IF THEY WANT TO WRITE API KEY TO DISK (PLAINTEXT) - # The ipstack API key is valid. Add the access key to the appdata file - # Prompt user about saving the API key - Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = (Read-Host).Trim().ToUpper() - - if ($saveChoice -eq 'Y') { - # Save to disk - Add-HawkAppData -name access_key -Value $AccessKey - - # Display warning banner about storage location - $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "`nWARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "NOTE: The API key is stored in plaintext format" -Information - return - } - else { - Out-LogFile "API key will not be saved to disk." -Information - return + # No key is on disk or was omitted AND the user entered a proper functioning IP Stack API access key + if (!$IsExistingValidAccessKey -and $IsValidUserEnteredAccessKey) { + Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine + $saveChoice = (Read-Host).Trim().ToUpper() + + if ($saveChoice -eq 'Y') { + # Save to disk + Add-HawkAppData -name access_key -Value $AccessKey + + # Display warning banner about storage location + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "`nWARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + break + } + else { + Out-LogFile "API key will not be saved to disk." -Information + break + } } + break # TRYING TO PREVENT THE PROMPT OF IP STACK API KEY FROM LOOPING! } catch { Out-LogFile "API key validation failed: $_" -isError - throw "API key validation failed: $_" - + #throw "API key validation failed: $_" + return } } @@ -183,5 +217,6 @@ Function Get-IPGeolocation { # Return the result to the user return $result } + return } } \ No newline at end of file diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 index 9a5a016e..79c72fda 100644 --- a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -52,6 +52,12 @@ Function Test-GeoIPAPIKey { process { + # Check for empty string or null entered by the user + if ([string]::IsNullOrEmpty($Key)) { + Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError + return $false + } + # Check length is 32 characters if ($Key.Length -ne 32) { return $false From 2cd6c076021e32aa87f220deb66336190009bbdc Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 15:57:33 -0500 Subject: [PATCH 20/53] cosmetics --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index c62922be..e5d535c3 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -71,7 +71,7 @@ Function Get-IPGeolocation { # This is to ensure the user doesn't get prompted to save a key that is already on disk $IsExistingValidAccessKey = $true Out-LogFile "Using existing API key from disk." -Information - break # USE RETURN OR BREAK!!!! + break } } @@ -103,12 +103,6 @@ Function Get-IPGeolocation { throw "An unexpected error occurred: $_" } - # Validate key format (basic check) - #if ([string]::IsNullOrWhiteSpace($AccessKey)) { - # Out-LogFile "API key cannot be empty or whitespace." -isError - # throw "API key cannot be empty or whitespace." - #} - # Geo IP location is requested, validate the key first (using Google DNS). if ($IsValidUserEnteredAccessKey -or $IsExistingValidAccessKey) { Out-LogFile "Testing API key against Google DNS..." -Action @@ -135,7 +129,7 @@ Function Get-IPGeolocation { # Display warning banner about storage location $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "`nWARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action Out-LogFile "NOTE: The API key is stored in plaintext format" -Information break } @@ -144,11 +138,11 @@ Function Get-IPGeolocation { break } } - break # TRYING TO PREVENT THE PROMPT OF IP STACK API KEY FROM LOOPING! + # We have a valid key from disk and do not need to prompt the user to save what is already saved. + break } catch { Out-LogFile "API key validation failed: $_" -isError - #throw "API key validation failed: $_" return } } From e9dd6b8ffbb4e890186c7477755d7fc827d8da6f Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Fri, 7 Feb 2025 15:58:25 -0500 Subject: [PATCH 21/53] cosmetics --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index e5d535c3..5c6b30c1 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -20,7 +20,7 @@ Function Get-IPGeolocation { ) begin { - # Read in existing HawkAppData + # Read in existing HawkAppData and IP Stack API Access Key if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { Read-HawkAppData [string]$AccessKeyFromFile = $HawkAppData.access_key From c0c89c22af6a5cbab77530e74a56b9bf9424ef35 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 23 Feb 2025 11:20:40 -0500 Subject: [PATCH 22/53] Prepping for all the AI's --- .../Get-HawkTenantRiskyServicePrincipals.ps1 | 16 +- Hawk/internal/functions/Get-IPGeolocation.ps1 | 292 ++++++++---------- Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 1 + 3 files changed, 130 insertions(+), 179 deletions(-) diff --git a/Hawk/functions/Tenant/Get-HawkTenantRiskyServicePrincipals.ps1 b/Hawk/functions/Tenant/Get-HawkTenantRiskyServicePrincipals.ps1 index b13276b3..5f87698d 100644 --- a/Hawk/functions/Tenant/Get-HawkTenantRiskyServicePrincipals.ps1 +++ b/Hawk/functions/Tenant/Get-HawkTenantRiskyServicePrincipals.ps1 @@ -65,14 +65,14 @@ Function Get-HawkTenantRiskyServicePrincipals { Send-AIEvent -Event "CmdRun" # Check for required license - $licenseCheck = Test-EntraWorkloadIDPremium - if (-not $licenseCheck.HasLicense) { - Out-LogFile "Entra Workload ID Premium license not found" -isWarning - Out-LogFile "No Entra Workload ID Premium capable licenses found." -Information - Out-LogFile "Required licenses: AAD_PREMIUM_P2, ENTERPRISEPREMIUM, SPE_E5, IDENTITY_THREAT_PROTECTION" -Information - Out-LogFile "The service principal risk detection requires one of these licenses to function" -Information - return - } + # $licenseCheck = Test-EntraWorkloadIDPremium + # if (-not $licenseCheck.HasLicense) { + # Out-LogFile "Entra Workload ID Premium license not found" -isWarning + # Out-LogFile "No Entra Workload ID Premium capable licenses found." -Information + # Out-LogFile "Required licenses: AAD_PREMIUM_P2, ENTERPRISEPREMIUM, SPE_E5, IDENTITY_THREAT_PROTECTION" -Information + # Out-LogFile "The service principal risk detection requires one of these licenses to function" -Information + # return + # } Out-LogFile "Retrieving risky service principals from Microsoft Entra ID" -Action diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 5c6b30c1..0294096c 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -11,151 +11,101 @@ .NOTES General notes #> -Function Get-IPGeolocation { +function Get-IPStackAPIKey { + [CmdletBinding()] + param() - Param - ( - [Parameter(Mandatory = $true)] - $IPAddress - ) - - begin { - # Read in existing HawkAppData and IP Stack API Access Key + try { + # Read in existing HawkAppData if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { Read-HawkAppData [string]$AccessKeyFromFile = $HawkAppData.access_key } - } - process { - try { - # If there is no access_key on disk (file) then we need to get it from the user - if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { - Out-LogFile "IpStack.com now requires an API access key to gather GeoIP information from their API." -Information - Out-LogFile "Please get a Free access key from https://ipstack.com/ and provide it below." -Information - Out-LogFile "Get your free API key at: https://ipstack.com/" -Information - - # Get the access key from the user - Out-LogFile "Provide your IP Stack API key: " -isPrompt -NoNewLine - $AccessKey = (Read-Host).Trim() - - # Check for empty string or null entered by the user - if ([string]::IsNullOrEmpty($AccessKey)) { - Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError - return - } - - # Test the existing key - $IsExistingValidAccessKey = Test-GeoIPAPIKey -Key $AccessKey - if (-not $IsExistingValidAccessKey) { - Out-LogFile "API key validation failed" -isError - return - } - } - # Handle existing key from file - elseif (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - try { - # Get last 6 characters of the API key - $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) - - # Prompt user about using existing key - Out-LogFile "Found existing API key ending in: $maskedKey" -Information - Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine - $useExistingKey = (Read-Host).Trim().ToUpper() - - if ($useExistingKey -eq 'Y') { - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - # Set the API access key from the file to $AccessKey, which is used to test against IP Stack API. - $AccessKey = $AccessKeyFromFile - - # This is to ensure the user doesn't get prompted to save a key that is already on disk - $IsExistingValidAccessKey = $true - Out-LogFile "Using existing API key from disk." -Information - break - } - - } - else { - # No access key was obtained from the file on disk - # This is to ensure the user doesn't get prompted to save a key that is already on disk - $IsExistingValidAccessKey = $false - Out-LogFile "Please provide a new IP Stack API key: " -isPrompt -NoNewLine - $AccessKey = (Read-Host).Trim() - - # Check for empty string or null entered by the user - if ([string]::IsNullOrEmpty($AccessKey)) { - Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError - return - } - - # Test the new key - $IsValidUserEnteredAccessKey = Test-GeoIPAPIKey -Key $AccessKey - } + #$IsValidAPIKey = $false + + # Check for existing key + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + Out-LogFile "Found existing API key ending in: $maskedKey" -Information + Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine + $useExistingKey = (Read-Host).Trim().ToUpper() + + if ($useExistingKey -eq 'Y') { + # Test existing key + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + Out-LogFile "Using existing API key from disk." -Information + return $AccessKeyFromFile } - catch { - Out-LogFile "API key is malformed!" -isError - throw "API key validation failed: $($response.error.info)" + else { + $AccessKeyFromFile = $null + Out-LogFile "Existing API key validation failed." -isError + return $AccessKeyFromFile } + } } - catch { - Out-LogFile "An unexpected error occurred: $_" -isError - throw "An unexpected error occurred: $_" - } - # Geo IP location is requested, validate the key first (using Google DNS). - if ($IsValidUserEnteredAccessKey -or $IsExistingValidAccessKey) { - Out-LogFile "Testing API key against Google DNS..." -Action - $testUrl = "http://api.ipstack.com/8.8.8.8?access_key=$AccessKey" + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - try { - $response = Invoke-RestMethod -Uri $testUrl -Method Get - if ($response.success -eq $false) { - Out-LogFile "API key validation failed: $($response.error.info)" -isError - throw "API key validation failed: $($response.error.info)" - } - Out-LogFile "API key validated successfully!" -Information - - # Save to disk (C:\Users\%USERPROFILE%\AppData\Local\Hawk\Hawk.json) - # PROMPT USER TO SEE IF THEY WANT TO WRITE API KEY TO DISK (PLAINTEXT) - # No key is on disk or was omitted AND the user entered a proper functioning IP Stack API access key - if (!$IsExistingValidAccessKey -and $IsValidUserEnteredAccessKey) { - Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = (Read-Host).Trim().ToUpper() - - if ($saveChoice -eq 'Y') { - # Save to disk - Add-HawkAppData -name access_key -Value $AccessKey - - # Display warning banner about storage location - $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "NOTE: The API key is stored in plaintext format" -Information - break - } - else { - Out-LogFile "API key will not be saved to disk." -Information - break - } - } - # We have a valid key from disk and do not need to prompt the user to save what is already saved. - break + # Get new key from user + Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information + Out-LogFile "Get your free API key at: https://ipstack.com/" -Information + Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine + $newKey = (Read-Host).Trim() + + #if ([string]::IsNullOrEmpty($newKey)) { + # throw "Cannot use empty API key" + #} + + # Validate new key + if (-not (Test-GeoIPAPIKey -Key $newKey)) { + throw "API key validation failed" } - catch { - Out-LogFile "API key validation failed: $_" -isError - return + + # Prompt to save new key + Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine + $saveChoice = (Read-Host).Trim().ToUpper() + + if ($saveChoice -eq 'Y') { + Add-HawkAppData -name access_key -Value $newKey + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "NOTE: The API key is stored in plaintext format" -Information } + + return $newKey } - + } + catch { + Out-LogFile $_.Exception.Message -isError + throw "$($_.Exception.Message)" + } +} - # Check the global IP cache and see if we already have the IP there - if ($IPLocationCache.ip -contains $IPAddress) { - return ($IPLocationCache | Where-Object { $_.ip -eq $IPAddress } ) - Write-Verbose ("IP Cache Hit: " + [string]$IPAddress) +function Get-IPGeolocation { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$IPAddress, + + [Parameter()] + [string]$AccessKey + ) + + begin { + # If no access key provided, get one + if ([string]::IsNullOrEmpty($AccessKey)) { + $AccessKey = Get-IPStackAPIKey } - elseif ($IPAddress -eq ""){ - write-Verbose ("Null IP Provided: " + $IPAddress) - $hash = @{ + } + + process { + try { + # Handle null IP address + if ($IPAddress -eq "") { + Write-Verbose "Null IP Provided: $IPAddress" + return [PSCustomObject]@{ IP = $IPAddress CountryName = "NULL IP" RegionName = "Unknown" @@ -164,53 +114,53 @@ Function Get-IPGeolocation { City = "Unknown" KnownMicrosoftIP = "Unknown" } - } - # If not then we need to look it up and populate it into the cache - else { - # URI to pull the data from - $resource = "http://api.ipstack.com/" + $ipaddress + "?access_key=" + $AccessKey - - # Return Data from web - $Error.Clear() - $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction SilentlyContinue - - if (($Error.Count) -or ([string]::IsNullOrEmpty($geoip.type))) { - Out-LogFile ("Failed to retreive location for IP " + $IPAddress) -isError - $hash = @{ - IP = $IPAddress - CountryName = "Failed to Resolve" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" - KnownMicrosoftIP = "Unknown" - } } - else { - # Determine if this IP is known to be owned by Microsoft - [string]$isMSFTIP = Test-MicrosoftIP -IPToTest $IPAddress -type $geoip.type - if ($isMSFTIP){ - $MSFTIP = $isMSFTIP - } - # Push return into a response object - $hash = @{ - IP = $geoip.ip - CountryName = $geoip.country_name - ContinentName = $geoip.continent_name - RegionName = $geoip.region_name - RegionCode = $geoip.region_code - City = $geoip.City - KnownMicrosoftIP = $MSFTIP - } - $result = New-Object PSObject -Property $hash + + # Check cache + if ($Global:IPLocationCache.ip -contains $IPAddress) { + Write-Verbose "IP Cache Hit: $IPAddress" + return ($Global:IPLocationCache | Where-Object { $_.ip -eq $IPAddress }) + } + + # Make API call + $resource = "http://api.ipstack.com/$IPAddress?access_key=$AccessKey" + $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction Stop + + # Create result object + $isMSFTIP = Test-MicrosoftIP -IPToTest $IPAddress -type $geoip.type + $result = [PSCustomObject]@{ + IP = $geoip.ip + CountryName = $geoip.country_name + ContinentName = $geoip.continent_name + RegionName = $geoip.region_name + RegionCode = $geoip.region_code + City = $geoip.city + KnownMicrosoftIP = $isMSFTIP } - # Push the result to the global IPLocationCache - [array]$Global:IPlocationCache += $result + # Update cache + [array]$Global:IPLocationCache += $result - # Return the result to the user return $result } - return + catch { + Out-LogFile "Failed to retrieve location for IP $IPAddress : $_" -isError + return [PSCustomObject]@{ + IP = $IPAddress + CountryName = "Failed to Resolve" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" + KnownMicrosoftIP = "Unknown" + } + } } -} \ No newline at end of file +} + +# Example usage: +# $key = Get-IPStackAPIKey +# Get-IPGeolocation -IPAddress "8.8.8.8" -AccessKey $key + +# Or simply: +# Get-IPGeolocation -IPAddress "8.8.8.8" \ No newline at end of file diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 index 79c72fda..3f618afe 100644 --- a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -64,6 +64,7 @@ Function Test-GeoIPAPIKey { } # Check each character is valid hex using regex + # ADD: CALL IPSTACK API AND DEPENDING ON RESULT, RETURN TRUE OR FALSE return ($Key -match '^[0-9a-f]{32}$') } } \ No newline at end of file From d05dcc0f1d4f6b47388a99132f1d63ca1763d1af Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 01:36:35 -0500 Subject: [PATCH 23/53] moved IP Stack API Key check to a better / sane location && FIXED GET-IPGEOLOCATION ISSUE!!!! LINE: 69 (ConvertTo-Json) --- Hawk/debug.ps1 | 2 + .../User/Get-HawkUserUALSignInLog.ps1 | 6 +- Hawk/internal/functions/Get-IPGeolocation.ps1 | 102 ++++----------- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 85 +++++++++++++ Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 119 +++++++++++------- 5 files changed, 188 insertions(+), 126 deletions(-) create mode 100644 Hawk/debug.ps1 create mode 100644 Hawk/internal/functions/Get-IPStackAPIKey.ps1 diff --git a/Hawk/debug.ps1 b/Hawk/debug.ps1 new file mode 100644 index 00000000..c92d8a0c --- /dev/null +++ b/Hawk/debug.ps1 @@ -0,0 +1,2 @@ +Import-Module "C:\Users\Lorenzo\hawk\Hawk\Hawk.psd1" +Start-HawkUserInvestigation -UserPrincipalName irelandl@semperhunt.onmicrosoft.com -DaysToLookBack 60 -FilePath C:\Temp \ No newline at end of file diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index 6c8245a5..e970f770 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -108,6 +108,10 @@ # Setup our counter $i = 0 + $AccessKey = Get-IPStackAPIKey + Set-Variable -Name AccessKey -Value $AccessKey -Scope Global + Write-Host "GET-HAWKUSERUALSIGNINLOG::GET-IPSTACKAPIKEY() -> AccessKey: $($AccessKey.GetType()) :: $AccessKey" -ForeGround Red + # Loop thru each connection and get the Geo IP location while ($i -lt $ExpandedUserLogonLogs.Count) { @@ -118,7 +122,7 @@ # Get the location information for this IP address if($ExpandedUserLogonLogs.item($i).clientip){ - $Location = Get-IPGeolocation -ipaddress $ExpandedUserLogonLogs.item($i).clientip + $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey } else { $Location = "IP Address Null" diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 0294096c..b56649a4 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -11,98 +11,37 @@ .NOTES General notes #> -function Get-IPStackAPIKey { - [CmdletBinding()] - param() - - try { - # Read in existing HawkAppData - if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { - Read-HawkAppData - [string]$AccessKeyFromFile = $HawkAppData.access_key - } - - #$IsValidAPIKey = $false - - # Check for existing key - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) - Out-LogFile "Found existing API key ending in: $maskedKey" -Information - Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine - $useExistingKey = (Read-Host).Trim().ToUpper() - - if ($useExistingKey -eq 'Y') { - # Test existing key - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - Out-LogFile "Using existing API key from disk." -Information - return $AccessKeyFromFile - } - else { - $AccessKeyFromFile = $null - Out-LogFile "Existing API key validation failed." -isError - return $AccessKeyFromFile - } - - } - } - - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - - # Get new key from user - Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information - Out-LogFile "Get your free API key at: https://ipstack.com/" -Information - Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine - $newKey = (Read-Host).Trim() - - #if ([string]::IsNullOrEmpty($newKey)) { - # throw "Cannot use empty API key" - #} - - # Validate new key - if (-not (Test-GeoIPAPIKey -Key $newKey)) { - throw "API key validation failed" - } - - # Prompt to save new key - Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = (Read-Host).Trim().ToUpper() - - if ($saveChoice -eq 'Y') { - Add-HawkAppData -name access_key -Value $newKey - $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "NOTE: The API key is stored in plaintext format" -Information - } - - return $newKey - } - } - catch { - Out-LogFile $_.Exception.Message -isError - throw "$($_.Exception.Message)" - } -} - function Get-IPGeolocation { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$IPAddress, - [Parameter()] + [Parameter(Mandatory = $false)] [string]$AccessKey ) begin { + # If no access key provided, get one - if ([string]::IsNullOrEmpty($AccessKey)) { - $AccessKey = Get-IPStackAPIKey - } + # You need to test to see if the switch is set + #if ([string]::IsNullOrEmpty($AccessKey)) { + # THIS IS NOT WORKING FOR SOME REASON!!!! + # Get-IPStackAPIKey returns a valid key, but it is not being passed to the function + # $AccessKey = Get-IPStackAPIKey + # $AccessKey = Get-IPStackAPIKey + #} } process { try { # Handle null IP address + + #$AccessKey = Get-IPStackAPIKey + #Write-Host "INSIDE GET-IPGEOLOCATION -> RETURNED FROM GET-IPSTACKAPIKEY(): $($AccessKey.GetType()) :: $AccessKey" -ForeGround Yellow + + #$AccessKey = 'b084134b5cbb9f1752c47c3ba90be95d' + if ($IPAddress -eq "") { Write-Verbose "Null IP Provided: $IPAddress" return [PSCustomObject]@{ @@ -123,11 +62,18 @@ function Get-IPGeolocation { } # Make API call - $resource = "http://api.ipstack.com/$IPAddress?access_key=$AccessKey" + #$resource = "http://api.ipstack.com/$IPAddress?access_key=b084134b5cbb9f1752c47c3ba90be95d" + $resource = "http://api.ipstack.com/$($IPAddress)?access_key=$AccessKey" $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction Stop + #$geoip | ConvertTo-Json -Depth 10 | Out-LogFile "GEOIP: $($geoip.ip) | TYPE: $($geoip.type)" -Information + $geoip = $geoip | ConvertTo-Json -Depth 10 + #Out-LogFile $geoip -Information + + + #Out-LogFile "GEOIP: $($geoip.ip) | TYPE: $($geoip.type)" -Information # Create result object - $isMSFTIP = Test-MicrosoftIP -IPToTest $IPAddress -type $geoip.type + # $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -type $geoip.type $result = [PSCustomObject]@{ IP = $geoip.ip CountryName = $geoip.country_name diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 new file mode 100644 index 00000000..7f350be5 --- /dev/null +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -0,0 +1,85 @@ +<# +.SYNOPSIS + Get the Location of an IP using the freegeoip.net rest API +.DESCRIPTION + Get the Location of an IP using the freegeoip.net rest API +.PARAMETER IPAddress + IP address of geolocation +.EXAMPLE + Get-IPStackAPIKey + Gets all IP Geolocation data of IPs that recieved +.NOTES + General notes +#> +function Get-IPStackAPIKey { + [CmdletBinding()] + param() + + try { + # Read in existing HawkAppData + if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { + Read-HawkAppData + [string]$AccessKeyFromFile = $HawkAppData.access_key + } + + #$IsValidAPIKey = $false + + # Check for existing key + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + Out-LogFile "Found existing API key ending in: $maskedKey" -Information + Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine + $useExistingKey = (Read-Host).Trim().ToUpper() + + if ($useExistingKey -eq 'Y') { + # Test existing key + Out-LogFile "Validating existing API key: $maskedKey" -Information + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + Out-LogFile "API KEY VALID: $AccessKeyFromFile - Using existing API key from disk." -Information + return $AccessKeyFromFile + } + else { + $AccessKeyFromFile = $null + Out-LogFile "Existing API key validation failed." -isError + #return $AccessKeyFromFile + } + + } + } + + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + + # Get new key from user + Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information + Out-LogFile "Get your free API key at: https://ipstack.com/" -Information + Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine + $newKey = (Read-Host).Trim() + + #if ([string]::IsNullOrEmpty($newKey)) { + # throw "Cannot use empty API key" + #} + + # Validate new key + if (-not (Test-GeoIPAPIKey -Key $newKey)) { + throw "API key validation failed" + } + + # Prompt to save new key + Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine + $saveChoice = (Read-Host).Trim().ToUpper() + + if ($saveChoice -eq 'Y') { + Add-HawkAppData -name access_key -Value $newKey + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + } + + return $newKey + } + } + catch { + Out-LogFile $_.Exception.Message -isError + throw "$($_.Exception.Message)" + } +} \ No newline at end of file diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 index 3f618afe..62535026 100644 --- a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -1,49 +1,17 @@ Function Test-GeoIPAPIKey { - <# + <# .SYNOPSIS - Create global variable $Hawk for use by all Hawk cmdlets. + Validates the supplied API key for ipstack.com. .DESCRIPTION - Creates the global variable $Hawk and populates it with information needed by the other Hawk cmdlets. - - * Checks for latest version of the Hawk module - * Creates path for output files - * Records target start and end dates for searches (in UTC) - .PARAMETER Force - Switch to force the function to run and allow the variable to be recreated - .PARAMETER SkipUpdate - Skips checking for the latest version of the Hawk Module - .PARAMETER DaysToLookBack - Defines the # of days to look back in the availible logs. - Valid values are 1-90 - .PARAMETER StartDate - First day that data will be retrieved (in UTC) - .PARAMETER EndDate - Last day that data will be retrieved (in UTC) - .PARAMETER FilePath - Provide an output file path. - .PARAMETER NonInteractive - Switch to run the command in non-interactive mode. Requires all necessary parameters - to be provided via command line rather than through interactive prompts. - .PARAMETER EnableGeoIPLocation - Switch to enable resolving IP addresses to geographic locations in the investigation. - This option requires an active internet connection and may increase the time needed to complete the investigation. - Providing this parameter automatically enables non-interactive mode. - - REQUIRED: An API key from ipstack.com is required to use this feature. + Checks if the provided API key is valid by performing format checks and making a request to ipstack.com, + using Google's DNS (8.8.8.8) for domain resolution. Returns a boolean indicating the key's validity. + .PARAMETER Key + The API key to validate. Must be a 32-character hexadecimal string. .OUTPUTS - Creates the $Hawk global variable and populates it with a custom PS object with the following properties - - Property Name Contents - ========== ========== - FilePath Path to output files - DaysToLookBack Number of day back in time we are searching - StartDate Calculated start date for searches based on DaysToLookBack (UTC) - EndDate One day in the future (UTC) - WhenCreated Date and time that the variable was created (UTC) + Boolean: $true if the API key is valid, $false otherwise. .EXAMPLE - Initialize-HawkGlobalObject -Force - - This Command will force the creation of a new $Hawk variable even if one already exists. + Test-GeoIPAPIKey -Key "your32characterhexkeyhere" + Tests whether the provided key is valid for use with ipstack.com. #> param ( [Parameter(Mandatory)] @@ -51,20 +19,77 @@ Function Test-GeoIPAPIKey { ) process { - - # Check for empty string or null entered by the user + # Check for null or empty string if ([string]::IsNullOrEmpty($Key)) { Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError return $false } - # Check length is 32 characters + # Verify the key is exactly 32 characters if ($Key.Length -ne 32) { + Out-LogFile "API key length is not 32 characters." -isError + return $false + } + + # Check if the key contains only hexadecimal characters (0-9, a-f) + if (-not ($Key -match '^[0-9a-f]{32}$')) { + Out-LogFile "API key contains invalid characters. Must be hexadecimal (0-9, a-f)." -isError return $false } - # Check each character is valid hex using regex - # ADD: CALL IPSTACK API AND DEPENDING ON RESULT, RETURN TRUE OR FALSE - return ($Key -match '^[0-9a-f]{32}$') + # Resolve api.ipstack.com using Google's DNS (8.8.8.8) + try { + $dnsResult = Resolve-DnsName -Name api.ipstack.com -Server 8.8.8.8 -Type A -ErrorAction Stop + if ($null -eq $dnsResult -or $dnsResult.Count -eq 0) { + Out-LogFile "No IP addresses resolved for api.ipstack.com using Google's DNS." -isError + return $false + } + # Take the first IPv4 address + $ip = $dnsResult.IPAddress | Select-Object -First 1 + } + catch { + Out-LogFile "Failed to resolve api.ipstack.com using Google's DNS: $_" -isError + return $false + } + + # Construct the API request URI using the resolved IP and the API key + $uri = "http://$ip/check?access_key=$Key" + $headers = @{ "Host" = "api.ipstack.com" } + + # Make the API request + try { + $response = Invoke-WebRequest -Uri $uri -Headers $headers -Method Get -ErrorAction Stop + } + catch { + Out-LogFile "Failed to contact ipstack API: $_" -isError + return $false + } + + # Verify the response status code is 200 OK + if ($response.StatusCode -ne 200) { + Out-LogFile "ipstack API returned status code $($response.StatusCode), expected 200." -isError + return $false + } + + # Parse the JSON response + try { + $content = $response.Content | ConvertFrom-Json + } + catch { + Out-LogFile "Failed to parse ipstack API response: $_" -isError + return $false + } + + # Check the response for validity + # ipstack returns "success": false with an "error" object for invalid keys + if ($content.success -eq $false) { + Out-LogFile "API key validation failed: $($content.error.info)" -isError + return $false + } + else { + # Successful responses lack "success" (it's null) and contain data like "ip" + Out-LogFile "Test-GeoIPAPIKey: API Key validated successfully." + return $true + } } } \ No newline at end of file From d0cac862d03ef2845f1c6313c0fb9530a420d196 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 02:41:34 -0500 Subject: [PATCH 24/53] Get-IPGeolocation - Line: 59 :: ConvertTo-Json -Depth 10 --- .../User/Get-HawkUserUALSignInLog.ps1 | 1 - Hawk/internal/functions/Get-IPGeolocation.ps1 | 54 +++++-------------- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index e970f770..cc47dc28 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -110,7 +110,6 @@ $AccessKey = Get-IPStackAPIKey Set-Variable -Name AccessKey -Value $AccessKey -Scope Global - Write-Host "GET-HAWKUSERUALSIGNINLOG::GET-IPSTACKAPIKEY() -> AccessKey: $($AccessKey.GetType()) :: $AccessKey" -ForeGround Red # Loop thru each connection and get the Geo IP location while ($i -lt $ExpandedUserLogonLogs.Count) { diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index b56649a4..5674cb8a 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -21,26 +21,11 @@ function Get-IPGeolocation { [string]$AccessKey ) - begin { - - # If no access key provided, get one - # You need to test to see if the switch is set - #if ([string]::IsNullOrEmpty($AccessKey)) { - # THIS IS NOT WORKING FOR SOME REASON!!!! - # Get-IPStackAPIKey returns a valid key, but it is not being passed to the function - # $AccessKey = Get-IPStackAPIKey - # $AccessKey = Get-IPStackAPIKey - #} - } + begin {} process { try { - # Handle null IP address - - #$AccessKey = Get-IPStackAPIKey - #Write-Host "INSIDE GET-IPGEOLOCATION -> RETURNED FROM GET-IPSTACKAPIKEY(): $($AccessKey.GetType()) :: $AccessKey" -ForeGround Yellow - - #$AccessKey = 'b084134b5cbb9f1752c47c3ba90be95d' + if ($IPAddress -eq "") { Write-Verbose "Null IP Provided: $IPAddress" @@ -61,26 +46,20 @@ function Get-IPGeolocation { return ($Global:IPLocationCache | Where-Object { $_.ip -eq $IPAddress }) } - # Make API call - #$resource = "http://api.ipstack.com/$IPAddress?access_key=b084134b5cbb9f1752c47c3ba90be95d" + # Make API calls to IP Stack to look up IP addresses $resource = "http://api.ipstack.com/$($IPAddress)?access_key=$AccessKey" $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction Stop - #$geoip | ConvertTo-Json -Depth 10 | Out-LogFile "GEOIP: $($geoip.ip) | TYPE: $($geoip.type)" -Information - $geoip = $geoip | ConvertTo-Json -Depth 10 - #Out-LogFile $geoip -Information - + $geoip | ConvertTo-Json -Depth 10 - #Out-LogFile "GEOIP: $($geoip.ip) | TYPE: $($geoip.type)" -Information - # Create result object - # $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -type $geoip.type + $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -Type $geoip.type $result = [PSCustomObject]@{ IP = $geoip.ip CountryName = $geoip.country_name ContinentName = $geoip.continent_name - RegionName = $geoip.region_name - RegionCode = $geoip.region_code - City = $geoip.city + RegionName = $geoip.region_name + RegionCode = $geoip.region_code + City = $geoip.city KnownMicrosoftIP = $isMSFTIP } @@ -94,19 +73,12 @@ function Get-IPGeolocation { return [PSCustomObject]@{ IP = $IPAddress CountryName = "Failed to Resolve" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" KnownMicrosoftIP = "Unknown" } } } -} - -# Example usage: -# $key = Get-IPStackAPIKey -# Get-IPGeolocation -IPAddress "8.8.8.8" -AccessKey $key - -# Or simply: -# Get-IPGeolocation -IPAddress "8.8.8.8" \ No newline at end of file +} \ No newline at end of file From f27d3938cf8b78488cd0c386133c96440b8157b6 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 02:56:57 -0500 Subject: [PATCH 25/53] Removed Global variable for --- Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 | 4 ++-- Hawk/internal/functions/Get-IPGeolocation.ps1 | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index cc47dc28..e524ad23 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -108,8 +108,8 @@ # Setup our counter $i = 0 + # Conduct IPStack API Key validation (once) so the API Key can be passed to Get-IPGeolocation $AccessKey = Get-IPStackAPIKey - Set-Variable -Name AccessKey -Value $AccessKey -Scope Global # Loop thru each connection and get the Geo IP location while ($i -lt $ExpandedUserLogonLogs.Count) { @@ -121,7 +121,7 @@ # Get the location information for this IP address if($ExpandedUserLogonLogs.item($i).clientip){ - $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey + $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey } else { $Location = "IP Address Null" diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 5674cb8a..52c8322c 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -26,7 +26,6 @@ function Get-IPGeolocation { process { try { - if ($IPAddress -eq "") { Write-Verbose "Null IP Provided: $IPAddress" return [PSCustomObject]@{ From 4c1f2f741147db37bd0c38cd8bda43a80d9b3d90 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 03:10:05 -0500 Subject: [PATCH 26/53] Inserted a while loop to select Y/N to use existing AccessKey --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 7f350be5..bff9b642 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -22,28 +22,31 @@ function Get-IPStackAPIKey { [string]$AccessKeyFromFile = $HawkAppData.access_key } - #$IsValidAPIKey = $false - # Check for existing key - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) - Out-LogFile "Found existing API key ending in: $maskedKey" -Information - Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine - $useExistingKey = (Read-Host).Trim().ToUpper() + while ($useExistingKey -notin @('Y','N')) { + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + Out-LogFile "Found existing API key ending in: $maskedKey" -Information + Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine + $useExistingKey = (Read-Host).Trim().ToUpper() - if ($useExistingKey -eq 'Y') { - # Test existing key - Out-LogFile "Validating existing API key: $maskedKey" -Information - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - Out-LogFile "API KEY VALID: $AccessKeyFromFile - Using existing API key from disk." -Information - return $AccessKeyFromFile + if ($useExistingKey -notin @('Y','N')) { + Out-LogFile "Please enter Y or N" -Information } - else { + + if ($useExistingKey -eq 'Y') { + # Test existing key + Out-LogFile "Validating existing API key: $maskedKey" -Information + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + Out-LogFile "API KEY VALID: $AccessKeyFromFile - Using existing API key from disk." -Information + return $AccessKeyFromFile + } + } + if ($useExistingKey -eq 'N') { $AccessKeyFromFile = $null - Out-LogFile "Existing API key validation failed." -isError - #return $AccessKeyFromFile + Out-LogFile "Existing API key Unkown or Disabled." -Information + break } - } } @@ -55,10 +58,6 @@ function Get-IPStackAPIKey { Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine $newKey = (Read-Host).Trim() - #if ([string]::IsNullOrEmpty($newKey)) { - # throw "Cannot use empty API key" - #} - # Validate new key if (-not (Test-GeoIPAPIKey -Key $newKey)) { throw "API key validation failed" From 3ef29a2b3f13c13e3929a8b484348c58f93c1df1 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 03:24:04 -0500 Subject: [PATCH 27/53] Updated output --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 2 +- Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index bff9b642..b5922d33 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -38,7 +38,7 @@ function Get-IPStackAPIKey { # Test existing key Out-LogFile "Validating existing API key: $maskedKey" -Information if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - Out-LogFile "API KEY VALID: $AccessKeyFromFile - Using existing API key from disk." -Information + Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information return $AccessKeyFromFile } } diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 index 62535026..ec6df226 100644 --- a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -88,7 +88,7 @@ Function Test-GeoIPAPIKey { } else { # Successful responses lack "success" (it's null) and contain data like "ip" - Out-LogFile "Test-GeoIPAPIKey: API Key validated successfully." + Out-LogFile "Test-GeoIPAPIKey: API Key validated successfully." -Information return $true } } From a04911a6b7d0ef36f4061739925968f42935081b Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 03:32:07 -0500 Subject: [PATCH 28/53] cosmetic / removed START-USERINVESTIGATION output --- Hawk/functions/User/Start-HawkUserInvestigation.ps1 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 index ce703ba4..279c4021 100644 --- a/Hawk/functions/User/Start-HawkUserInvestigation.ps1 +++ b/Hawk/functions/User/Start-HawkUserInvestigation.ps1 @@ -164,10 +164,8 @@ # Check if Hawk object exists and is fully initialized if (Test-HawkGlobalObject) { - Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to TRUE!" -Information + #Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to TRUE!" -Information Initialize-HawkGlobalObject - } else { - Out-LogFile "START-USERINVESTIGATION::Test-HawkGlobalOjbect evaluated to FALSE!" -Information } $investigationStartTime = Get-Date From 0af959bedcb86d4c8c7d6d58a6923f4e79ccad11 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 04:52:47 -0500 Subject: [PATCH 29/53] Added do/while loop to repeatedly prompt the user for a valid IP Stack API Key. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 126 ++++++++++-------- 1 file changed, 71 insertions(+), 55 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index b5922d33..57534748 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -15,70 +15,86 @@ function Get-IPStackAPIKey { [CmdletBinding()] param() - try { - # Read in existing HawkAppData - if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { - Read-HawkAppData - [string]$AccessKeyFromFile = $HawkAppData.access_key - } + begin { + $newKey = $null + $useExistingKey = $null + } - # Check for existing key - while ($useExistingKey -notin @('Y','N')) { - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) - Out-LogFile "Found existing API key ending in: $maskedKey" -Information - Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine - $useExistingKey = (Read-Host).Trim().ToUpper() + process { - if ($useExistingKey -notin @('Y','N')) { - Out-LogFile "Please enter Y or N" -Information - } + try { + # Read in existing HawkAppData + if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { + Read-HawkAppData + [string]$AccessKeyFromFile = $HawkAppData.access_key + } + + # Check for existing key + while ($useExistingKey -notin @('Y','N')) { + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + Out-LogFile "Found existing API key ending in: $maskedKey" -Information + Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine + $useExistingKey = (Read-Host).Trim().ToUpper() - if ($useExistingKey -eq 'Y') { - # Test existing key - Out-LogFile "Validating existing API key: $maskedKey" -Information - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information - return $AccessKeyFromFile + if ($useExistingKey -notin @('Y','N')) { + Out-LogFile "Please enter Y or N" -Information + } + + if ($useExistingKey -eq 'Y') { + # Test existing key + Out-LogFile "Validating existing API key: $maskedKey" -Information + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information + return $AccessKeyFromFile + } + } + if ($useExistingKey -eq 'N') { + $AccessKeyFromFile = $null + Out-LogFile "Existing API key Unkown or Disabled -> Prompt for user provided API key." -Information + break } - } - if ($useExistingKey -eq 'N') { - $AccessKeyFromFile = $null - Out-LogFile "Existing API key Unkown or Disabled." -Information - break } } - } - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { + # Display informational messages once before looping + Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information + Out-LogFile "Get your free API key at: https://ipstack.com/" -Information - # Get new key from user - Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information - Out-LogFile "Get your free API key at: https://ipstack.com/" -Information - Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine - $newKey = (Read-Host).Trim() - - # Validate new key - if (-not (Test-GeoIPAPIKey -Key $newKey)) { - throw "API key validation failed" - } - - # Prompt to save new key - Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = (Read-Host).Trim().ToUpper() - - if ($saveChoice -eq 'Y') { - Add-HawkAppData -name access_key -Value $newKey - $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + # Loop until a valid key is provided + do { + # Prompt user for the API key + Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine + $newKey = (Read-Host).Trim() + + # Validate the key and store the result + $isValid = Test-GeoIPAPIKey -Key $newKey + + # If invalid, inform the user and loop again + if (-not $isValid) { + Out-LogFile "Invalid API key. Please try again." -Action + } + } while (-not $isValid) + + # Once a valid key is entered, prompt to save it + Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine + $saveChoice = (Read-Host).Trim().ToUpper() + + if ($saveChoice -eq 'Y') { + Add-HawkAppData -name access_key -Value $newKey + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + } + + # Return the validated key + return $newKey } - - return $newKey } - } - catch { - Out-LogFile $_.Exception.Message -isError - throw "$($_.Exception.Message)" + catch { + Out-LogFile $_.Exception.Message -isError + throw "$($_.Exception.Message)" + } } } \ No newline at end of file From fcf286f72195a44f6a65f6265925e235d15ea070 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 12:32:50 -0500 Subject: [PATCH 30/53] modified Read-HawkAppData and more importantly restructured the logic in Get-IPStackAPIKey :: Line 42-65 --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 54 +++++++++++-------- Hawk/internal/functions/Read-HawkAppData.ps1 | 10 ++-- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 57534748..02d4853e 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -16,8 +16,9 @@ function Get-IPStackAPIKey { param() begin { - $newKey = $null - $useExistingKey = $null + [string]$newKey = $null + [string]$AccessKeyFromFile = $null + #$useExistingKey = $null } process { @@ -25,38 +26,45 @@ function Get-IPStackAPIKey { try { # Read in existing HawkAppData if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { - Read-HawkAppData - [string]$AccessKeyFromFile = $HawkAppData.access_key + if (Read-HawkAppData) { + Out-LogFile "HawkAppData JSON file read successfully." -Information + [string]$AccessKeyFromFile = $HawkAppData.access_key + Out-LogFile "Access Key from HawkAppData: $AccessKeyFromFile" -Information + } + else { + Out-LogFile "HawkAppData Access Key is null/empty." -Information + #$AccessKeyFromFile = $null + } + } - # Check for existing key - while ($useExistingKey -notin @('Y','N')) { - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + # Check for existing access key on disk + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)){ + do { $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) Out-LogFile "Found existing API key ending in: $maskedKey" -Information Out-LogFile "Would you like to use this existing key? (Y/N): " -isPrompt -NoNewLine $useExistingKey = (Read-Host).Trim().ToUpper() - + if ($useExistingKey -notin @('Y','N')) { Out-LogFile "Please enter Y or N" -Information - } - - if ($useExistingKey -eq 'Y') { - # Test existing key - Out-LogFile "Validating existing API key: $maskedKey" -Information - if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { - Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information - return $AccessKeyFromFile + } else { + if ($useExistingKey -eq 'Y') { + # Test existing key + Out-LogFile "Validating existing API key: $maskedKey" -Information + if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { + Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information + return $AccessKeyFromFile + } + } elseif ($useExistingKey -eq 'N') { + $AccessKeyFromFile = $null + Out-LogFile "Existing API key Unkown or Disabled -> Prompt for user provided API key." -Information } } - if ($useExistingKey -eq 'N') { - $AccessKeyFromFile = $null - Out-LogFile "Existing API key Unkown or Disabled -> Prompt for user provided API key." -Information - break - } - } - } + } while ($useExistingKey -notin @('Y','N')) + } + # If no existing access key is found on disk, prompt for a new one if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { # Display informational messages once before looping Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information diff --git a/Hawk/internal/functions/Read-HawkAppData.ps1 b/Hawk/internal/functions/Read-HawkAppData.ps1 index 288626f6..2214541a 100644 --- a/Hawk/internal/functions/Read-HawkAppData.ps1 +++ b/Hawk/internal/functions/Read-HawkAppData.ps1 @@ -16,13 +16,17 @@ Function Read-HawkAppData { $HawkAppdataPath = join-path $env:LOCALAPPDATA "Hawk\Hawk.json" - # check to see if our xml file is there + # check to see if our JSON file is there if (test-path $HawkAppdataPath) { Out-LogFile ("Reading file " + $HawkAppdataPath) -Action - $global:HawkAppData = ConvertFrom-Json -InputObject ([string](Get-Content $HawkAppdataPath)) + if (-not [string]::IsNullOrEmpty($global:HawkAppData)) { + Out-LogFile ("HawkAppData JSON exists, not overwriting") -Information + $global:HawkAppData = ConvertFrom-Json -InputObject ([string](Get-Content $HawkAppdataPath)) + } } - # if we don't have an xml file then do nothing + # if we don't have an JSON file then do nothing else { Out-LogFile ("No HawkAppData File found " + $HawkAppdataPath) -Information + $global:HawkAppData = @{ "access_key" = $null } } } \ No newline at end of file From 0ffebc4ef7abbcc1f5adb0d6966eb87380078596 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 12:50:03 -0500 Subject: [PATCH 31/53] Fixed issue with the API Key not correctly writing itself to Hawk.json file on disk [Add-HawkData]. --- Hawk/internal/functions/Read-HawkAppData.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Read-HawkAppData.ps1 b/Hawk/internal/functions/Read-HawkAppData.ps1 index 2214541a..a208e417 100644 --- a/Hawk/internal/functions/Read-HawkAppData.ps1 +++ b/Hawk/internal/functions/Read-HawkAppData.ps1 @@ -27,6 +27,7 @@ Function Read-HawkAppData { # if we don't have an JSON file then do nothing else { Out-LogFile ("No HawkAppData File found " + $HawkAppdataPath) -Information - $global:HawkAppData = @{ "access_key" = $null } + Add-HawkAppData -name access_key -Value $null + #$global:HawkAppData = @{ "access_key" = "null" } } } \ No newline at end of file From f3183d7bafd9f7d03e644c0185d3cb59e1ef168c Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 12:54:43 -0500 Subject: [PATCH 32/53] cleaning up commented out code / variables. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 6 +----- Hawk/internal/functions/Read-HawkAppData.ps1 | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 02d4853e..7df08ef6 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -18,7 +18,6 @@ function Get-IPStackAPIKey { begin { [string]$newKey = $null [string]$AccessKeyFromFile = $null - #$useExistingKey = $null } process { @@ -29,13 +28,10 @@ function Get-IPStackAPIKey { if (Read-HawkAppData) { Out-LogFile "HawkAppData JSON file read successfully." -Information [string]$AccessKeyFromFile = $HawkAppData.access_key - Out-LogFile "Access Key from HawkAppData: $AccessKeyFromFile" -Information } else { Out-LogFile "HawkAppData Access Key is null/empty." -Information - #$AccessKeyFromFile = $null } - } # Check for existing access key on disk @@ -93,7 +89,7 @@ function Get-IPStackAPIKey { Add-HawkAppData -name access_key -Value $newKey $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "NOTE: The API key is stored in plaintext format" -Information + Out-LogFile "WARNING: Your API key is stored in plaintext." -Information } # Return the validated key diff --git a/Hawk/internal/functions/Read-HawkAppData.ps1 b/Hawk/internal/functions/Read-HawkAppData.ps1 index a208e417..bf40f771 100644 --- a/Hawk/internal/functions/Read-HawkAppData.ps1 +++ b/Hawk/internal/functions/Read-HawkAppData.ps1 @@ -28,6 +28,5 @@ Function Read-HawkAppData { else { Out-LogFile ("No HawkAppData File found " + $HawkAppdataPath) -Information Add-HawkAppData -name access_key -Value $null - #$global:HawkAppData = @{ "access_key" = "null" } } } \ No newline at end of file From 7ca6f48abe36a91afe88e0399172e8a7066195d7 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 14:42:01 -0500 Subject: [PATCH 33/53] Specifically looked for value of .access_key to determine which branch to execute from. if(Read-HawkAppData) was not working as intended. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 3 ++- Hawk/internal/functions/Read-HawkAppData.ps1 | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 7df08ef6..d8976f65 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -25,7 +25,8 @@ function Get-IPStackAPIKey { try { # Read in existing HawkAppData if (!([bool](Get-Variable HawkAppData -ErrorAction SilentlyContinue))) { - if (Read-HawkAppData) { + Read-HawkAppData + if ($HawkAppData.access_key) { Out-LogFile "HawkAppData JSON file read successfully." -Information [string]$AccessKeyFromFile = $HawkAppData.access_key } diff --git a/Hawk/internal/functions/Read-HawkAppData.ps1 b/Hawk/internal/functions/Read-HawkAppData.ps1 index bf40f771..3779426c 100644 --- a/Hawk/internal/functions/Read-HawkAppData.ps1 +++ b/Hawk/internal/functions/Read-HawkAppData.ps1 @@ -19,14 +19,16 @@ Function Read-HawkAppData { # check to see if our JSON file is there if (test-path $HawkAppdataPath) { Out-LogFile ("Reading file " + $HawkAppdataPath) -Action - if (-not [string]::IsNullOrEmpty($global:HawkAppData)) { + $global:HawkAppData = ConvertFrom-Json -InputObject ([string](Get-Content $HawkAppdataPath)) + if (-not [string]::IsNullOrEmpty($global:HawkAppData.access_key)) { + Out-LogFile ("HawkAppData JSON read successfully") -Information Out-LogFile ("HawkAppData JSON exists, not overwriting") -Information - $global:HawkAppData = ConvertFrom-Json -InputObject ([string](Get-Content $HawkAppdataPath)) + + } + # if we don't have an JSON file then do nothing + else { + Out-LogFile ("No HawkAppData File found " + $HawkAppdataPath) -Information + Add-HawkAppData -name access_key -Value $null } - } - # if we don't have an JSON file then do nothing - else { - Out-LogFile ("No HawkAppData File found " + $HawkAppdataPath) -Information - Add-HawkAppData -name access_key -Value $null } } \ No newline at end of file From 770b816cf1af320b79364713d57064de1cb34814 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 24 Feb 2025 17:16:42 -0500 Subject: [PATCH 34/53] Cosmetic mod to Add-HawkAppData method. --- Hawk/internal/functions/Add-HawkAppData.ps1 | 2 +- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Hawk/internal/functions/Add-HawkAppData.ps1 b/Hawk/internal/functions/Add-HawkAppData.ps1 index b433ced2..614161f6 100644 --- a/Hawk/internal/functions/Add-HawkAppData.ps1 +++ b/Hawk/internal/functions/Add-HawkAppData.ps1 @@ -24,7 +24,7 @@ Function Add-HawkAppData { [string]$Value ) - Out-LogFile ("Adding " + $value + " to " + $Name + " in HawkAppData") -Action + Out-LogFile ("Adding `"$Value`" to `"$Name`" in HawkAppData") -Action # Test if our HawkAppData variable exists if ([bool](get-variable HawkAppData -ErrorAction SilentlyContinue)) { diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index d8976f65..a28ff1d7 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -1,10 +1,10 @@ <# .SYNOPSIS - Get the Location of an IP using the freegeoip.net rest API + Get the Location of an IP using the ipstack.com rest API .DESCRIPTION - Get the Location of an IP using the freegeoip.net rest API -.PARAMETER IPAddress - IP address of geolocation + Get the Location of an IP using the ipstack.com rest API +.PARAMETER None + No parameters .EXAMPLE Get-IPStackAPIKey Gets all IP Geolocation data of IPs that recieved From 70b2775902920954ff72bc7804e8577e6e060fdf Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 04:36:05 -0500 Subject: [PATCH 35/53] If access key on file is invalid, set access key to null so user is prompted for a new key. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index a28ff1d7..32bd847d 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -52,6 +52,10 @@ function Get-IPStackAPIKey { if (Test-GeoIPAPIKey -Key $AccessKeyFromFile) { Out-LogFile "API KEY VALIDATED :: Using existing API key from disk -> $maskedKey" -Information return $AccessKeyFromFile + }else { + # If access key on file is invalid, set access key to null and prompt for new key + Out-LogFile "API KEY `"$maskedKey`" INVALID :: Prompt user for new API key" -Information + $AccessKeyFromFile = $null } } elseif ($useExistingKey -eq 'N') { $AccessKeyFromFile = $null From 4bb3b093f695d139e97b13a48272452de2a4779c Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 04:45:47 -0500 Subject: [PATCH 36/53] Coded USE CASE: Invalid/Expired key exists on disk, check key, comes back invalid, user prompted to enter new key and enters in an empty space and presses enter. INPUT-VALIDATION. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 32bd847d..8bcd77eb 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -77,8 +77,15 @@ function Get-IPStackAPIKey { Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine $newKey = (Read-Host).Trim() - # Validate the key and store the result - $isValid = Test-GeoIPAPIKey -Key $newKey + # Ensure user input provided for API key is valid + if ([string]::IsNullOrEmpty($newKey)) { + Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError + $isValid = $false + }else { + Out-LogFile "Validating API key: $newKey" -Information + $isValid = Test-GeoIPAPIKey -Key $newKey + } + # If invalid, inform the user and loop again if (-not $isValid) { From 63161208efbadb3c44147879fc274322aec8fdba Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 04:57:18 -0500 Subject: [PATCH 37/53] Updated comment section of Get-IPGeolocation.ps1 --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 52c8322c..6244bcb4 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -1,12 +1,14 @@ <# .SYNOPSIS - Get the Location of an IP using the freegeoip.net rest API + Get the Location of an IP using the ipstack.com REST API .DESCRIPTION - Get the Location of an IP using the freegeoip.net rest API + Get the Location of an IP using the ipstack.com REST API .PARAMETER IPAddress - IP address of geolocation + IP address to look up for geolocation +.PARAMETER AccessKey + Access key for the API .EXAMPLE - Get-IPGeolocation + Get-IPGeolocation -IPAddress 8.8.8.8 -AccessKey "your_access_key" Gets all IP Geolocation data of IPs that recieved .NOTES General notes From 6c129af5c5adbd0f3a77cc1710e7c22188b5b639 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 18:24:00 -0500 Subject: [PATCH 38/53] added documentation and comments throughout code. --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 4 +++- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 14 +++++++------- Hawk/internal/functions/Test-GeoIPAPIKey.ps1 | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 6244bcb4..8462b366 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -1,6 +1,8 @@ <# .SYNOPSIS - Get the Location of an IP using the ipstack.com REST API + Get-IPGeolocation is called by Get-HawkUserUALSignInLog to resolve IP addresses to geolocation data. + An IP address and IP Stack API Key is passed to the function, as it returns a PSCustomObject with the geolocation data. + .DESCRIPTION Get the Location of an IP using the ipstack.com REST API .PARAMETER IPAddress diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 8bcd77eb..0d81b546 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -1,15 +1,15 @@ <# .SYNOPSIS - Get the Location of an IP using the ipstack.com rest API + Get-IPStackAPIKey is called by Get-HawkUserUALSignInLog to ensure a valid API key is available for use with ipstack.com. + Once a valid key is provided, it is saved and used by Get-IPGeolocation to resolve IP addresses to geolocation data. .DESCRIPTION - Get the Location of an IP using the ipstack.com rest API + Validate REST API key from ipstack.com .PARAMETER None No parameters .EXAMPLE - Get-IPStackAPIKey - Gets all IP Geolocation data of IPs that recieved + [string]$AccessKey = Get-IPStackAPIKey .NOTES - General notes + Get-IPStackAPIKey also uses Test-GeoIPAPIKey to validate the API key. #> function Get-IPStackAPIKey { [CmdletBinding()] @@ -77,7 +77,7 @@ function Get-IPStackAPIKey { Out-LogFile "Please provide your IP Stack API key: " -isPrompt -NoNewLine $newKey = (Read-Host).Trim() - # Ensure user input provided for API key is valid + # Ensure user input provided for API key is not null or empty before testing the API key for validity if ([string]::IsNullOrEmpty($newKey)) { Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError $isValid = $false @@ -86,7 +86,6 @@ function Get-IPStackAPIKey { $isValid = Test-GeoIPAPIKey -Key $newKey } - # If invalid, inform the user and loop again if (-not $isValid) { Out-LogFile "Invalid API key. Please try again." -Action @@ -98,6 +97,7 @@ function Get-IPStackAPIKey { $saveChoice = (Read-Host).Trim().ToUpper() if ($saveChoice -eq 'Y') { + # Save the ipstack REST API key to HawkAppData Add-HawkAppData -name access_key -Value $newKey $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action diff --git a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 index ec6df226..f0332547 100644 --- a/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 +++ b/Hawk/internal/functions/Test-GeoIPAPIKey.ps1 @@ -1,7 +1,7 @@ Function Test-GeoIPAPIKey { <# .SYNOPSIS - Validates the supplied API key for ipstack.com. + Validates the supplied data in the form of a REST API key for ipstack.com. .DESCRIPTION Checks if the provided API key is valid by performing format checks and making a request to ipstack.com, using Google's DNS (8.8.8.8) for domain resolution. Returns a boolean indicating the key's validity. From d798ad050582e22b3ac12682a117e8ae2f1c5fbc Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 18:50:01 -0500 Subject: [PATCH 39/53] Validated Y/N input when user is asked if they want to save REST API Key to disk. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 0d81b546..91c98fa9 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -94,16 +94,28 @@ function Get-IPStackAPIKey { # Once a valid key is entered, prompt to save it Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = (Read-Host).Trim().ToUpper() + $saveChoice = '' + while ($saveChoice -notin @('Y','N')) { + $saveChoice = (Read-Host).Trim().ToUpper() + + if ($GeoIPResponse -notin @('Y','N')) { + Out-LogFile "Please enter Y or N for your response: " -isPrompt -NoNewLine + } - if ($saveChoice -eq 'Y') { - # Save the ipstack REST API key to HawkAppData - Add-HawkAppData -name access_key -Value $newKey - $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" - Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action - Out-LogFile "WARNING: Your API key is stored in plaintext." -Information + if ($saveChoice -eq 'Y') { + # Save the ipstack REST API key to HawkAppData + Add-HawkAppData -name access_key -Value $newKey + $appDataPath = Join-Path $env:LOCALAPPDATA "Hawk\Hawk.json" + Out-LogFile "WARNING: Your API key has been saved to: $appDataPath" -Action + Out-LogFile "WARNING: Your API key is stored in plaintext." -Information + break + } + if ($GeoIPResponse -eq 'N') { + Out-LogFile "REST API Key for ipstack.com is not saved to disk." -Information + break + } } - + # Return the validated key return $newKey } From f5daaad2ddb68332d0b800a85922231f07e5828b Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 18:59:30 -0500 Subject: [PATCH 40/53] Validated Y/N input when user is asked if they want to save REST API Key to disk. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 91c98fa9..208f22d3 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -94,7 +94,7 @@ function Get-IPStackAPIKey { # Once a valid key is entered, prompt to save it Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = '' + $saveChoice = $null while ($saveChoice -notin @('Y','N')) { $saveChoice = (Read-Host).Trim().ToUpper() From 690f0db5498914254d6c92c6b479ce7da7bef240 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Tue, 25 Feb 2025 20:12:51 -0500 Subject: [PATCH 41/53] cosmetics / minor refactoring --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 208f22d3..029e0a08 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -16,8 +16,9 @@ function Get-IPStackAPIKey { param() begin { - [string]$newKey = $null + [string]$newKey = $null [string]$AccessKeyFromFile = $null + [string]$saveChoice = $null } process { @@ -94,7 +95,6 @@ function Get-IPStackAPIKey { # Once a valid key is entered, prompt to save it Out-LogFile "Would you like to save your API key to disk? (Y/N): " -isPrompt -NoNewLine - $saveChoice = $null while ($saveChoice -notin @('Y','N')) { $saveChoice = (Read-Host).Trim().ToUpper() From fc580b936b33c55a649cf19bd7858cc911b1625a Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Wed, 26 Feb 2025 15:10:25 -0500 Subject: [PATCH 42/53] comments / corrections --- Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 | 4 ++-- Hawk/internal/functions/Get-IPGeolocation.ps1 | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index e524ad23..6d16ce2c 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -116,7 +116,7 @@ if ([bool]($i % 25)) { } else { - Write-Progress -Activity "Looking Up Ip Address Locations" -CurrentOperation $i -PercentComplete (($i / $ExpandedUserLogonLogs.count) * 100) + Write-Progress -Activity "Looking Up IP Address Locations" -CurrentOperation $i -PercentComplete (($i / $ExpandedUserLogonLogs.count) * 100) } # Get the location information for this IP address @@ -134,7 +134,7 @@ $i++ } - Write-Progress -Completed -Activity "Looking Up Ip Address Locations" -Status " " + Write-Progress -Completed -Activity "Looking Up IP Address Locations" -Status " " } else { Out-LogFile "ResolveIPLocations not specified" -Information diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 8462b366..fa9c9e62 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -4,13 +4,13 @@ An IP address and IP Stack API Key is passed to the function, as it returns a PSCustomObject with the geolocation data. .DESCRIPTION - Get the Location of an IP using the ipstack.com REST API + Get the Geographic Location of an IP address using the ipstack.com REST API .PARAMETER IPAddress - IP address to look up for geolocation + IP address to look up for its geographic location .PARAMETER AccessKey - Access key for the API + Access key for ipstack.com's REST API .EXAMPLE - Get-IPGeolocation -IPAddress 8.8.8.8 -AccessKey "your_access_key" + Get-IPGeolocation -IPAddress 8.8.8.8 -AccessKey e904134b5cbb91f752a79f3ba9cbe59a Gets all IP Geolocation data of IPs that recieved .NOTES General notes From cefb42b32bb250a987c66fd72296a7262a4345c0 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 15:19:06 -0500 Subject: [PATCH 43/53] updated changelog.md and Hawk.psd1 to reflect version 4.0.1 --- Hawk/Hawk.psd1 | 2 +- Hawk/changelog.md | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Hawk/Hawk.psd1 b/Hawk/Hawk.psd1 index 1794b8b6..788814d2 100644 --- a/Hawk/Hawk.psd1 +++ b/Hawk/Hawk.psd1 @@ -3,7 +3,7 @@ RootModule = 'Hawk.psm1' # Version number of this module. - ModuleVersion = '4.0' + ModuleVersion = '4.0.1' # ID used to uniquely identify this module GUID = '1f6b6b91-79c4-4edf-83a1-66d2dc8c3d85' diff --git a/Hawk/changelog.md b/Hawk/changelog.md index b96db134..af6049c1 100644 --- a/Hawk/changelog.md +++ b/Hawk/changelog.md @@ -89,7 +89,7 @@ - Updated Change Log URI. - Removed improperly formatted JSON from Get-HawkTenantAdminInboxRuleHistory, Get-HawkTenantAdminInboxRuleRemoval, Get-HawkTenantRBACChange, Get-HawkUserAdminAudit, Search-HawkTenantEXOAuditLog -## 4.0 (2025-2-XX) +## 4.0 (2025-2-23) - Implemented UTC timestamps to avoid using local timestamps - Implemented PROMPT tag to display to screen when prompting user @@ -106,3 +106,8 @@ - Implemented check to verify that an Exchange operation is enabled for auditing before attempting to pull logs - Added log pull of user Send activity to the User Investigation (Get-HawkUserMailSendActivity) - Added log pull of user SharePoint Search activity to the User Investigation (Get-HawkUserSharePointSearchQuery) + +## 4.0.1 (2025-3-02) +- Fixed bug in Get-IPGeolocation where API Keys from ipstack.com were not validated +- Fixed bug in Get-IPGeolocation where API Keys were not validated to meet basic sanity checks/requirements +- Added commandline agrument '-EnableGeoIPLocation' to Start-HawkUserInvestigation providing the ability to skip interactive prompts when conducting investigations. \ No newline at end of file From cd474b6d13e28c944461c0e90f07631a8f7abbe8 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 16:49:55 -0500 Subject: [PATCH 44/53] Need to add Hawk GeoIPLocation Automation logic. --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 1 + Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 7 +++++-- Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 2 -- Hawk/internal/functions/Test-MicrosoftIP.ps1 | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index fa9c9e62..943a1bd7 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -55,6 +55,7 @@ function Get-IPGeolocation { $geoip | ConvertTo-Json -Depth 10 # Create result object + Write-Output "`n" $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -Type $geoip.type $result = [PSCustomObject]@{ IP = $geoip.ip diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 029e0a08..b52a9401 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -36,6 +36,9 @@ function Get-IPStackAPIKey { } } + # If EnableGeoIPLocation is set to true and key exists on disk (supports Hawk automation) + # If the key comes back invalid, just run the program without lookuping up GeoIP data + # Check for existing access key on disk if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)){ do { @@ -83,13 +86,13 @@ function Get-IPStackAPIKey { Out-LogFile "Failed to update IP Stack API key: Cannot bind argument to parameter 'Key' because it is an empty string." -isError $isValid = $false }else { - Out-LogFile "Validating API key: $newKey" -Information + Out-LogFile "Validating API key: $newKey" -Action $isValid = Test-GeoIPAPIKey -Key $newKey } # If invalid, inform the user and loop again if (-not $isValid) { - Out-LogFile "Invalid API key. Please try again." -Action + Out-LogFile "Invalid API key. Please try again." -Information } } while (-not $isValid) diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index b0abeb11..bcda27ff 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -539,8 +539,6 @@ $BoolDirectlyCalledByUserInvestigation = ((Get-PSCallStack)[1].FunctionName -like "Start-HawkUserInvestigation*") -or ` ((Get-PSCallStack)[1].FunctionName -like "Get-HawkUserUALSignInLog*") -or ` ((Get-PSCallStack)[1].FunctionName -like "Get-HawkUserEntraIDSignInLog*") - Out-LogFile "Was directly called by: $BoolDirectlyCalledByUserInvestigation" -Information - Out-LogFile (Get-PSCallStack)[1].FunctionName -Information if ((-not $PSBoundParameters.ContainsKey('EnableGeoIPLocation')) -and $BoolDirectlyCalledByUserInvestigation) { Out-LogFile "Would you like to enable GeoIP Location?" -Information diff --git a/Hawk/internal/functions/Test-MicrosoftIP.ps1 b/Hawk/internal/functions/Test-MicrosoftIP.ps1 index f75ae2b3..05914bd6 100644 --- a/Hawk/internal/functions/Test-MicrosoftIP.ps1 +++ b/Hawk/internal/functions/Test-MicrosoftIP.ps1 @@ -29,6 +29,7 @@ Function Test-MicrosoftIP { # Check if we have imported all of our IP Addresses if ($null -eq $MSFTIPList) { + Write-Output "`n" Out-Logfile "Building MSFTIPList" -Action # Load our networking dll pulled from https://github.com/lduchosal/ipnetwork From b89f0c4d064419fea04e43405d87880ae01a620b Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 20:45:52 -0500 Subject: [PATCH 45/53] added logic to support to get Geo IP data in non-interactive mode --- .../User/Get-HawkUserUALSignInLog.ps1 | 5 +-- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 36 ++++++++++++++----- .../functions/Initialize-HawkGlobalObject.ps1 | 1 + 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index 6d16ce2c..ab217038 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -120,11 +120,12 @@ } # Get the location information for this IP address - if($ExpandedUserLogonLogs.item($i).clientip){ + # Need to perform access key value only once instead of for each IP address + if($ExpandedUserLogonLogs.item($i).clientip -and ([string]::IsNullOrEmpty($AccessKey) -eq $false)) { $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey } else { - $Location = "IP Address Null" + $Location = "Valid REST API Key was not provided or IP address was not found" } # Combine the connection object and the location object so that we have a single output ready diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index b52a9401..9933b672 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -16,9 +16,12 @@ function Get-IPStackAPIKey { param() begin { - [string]$newKey = $null - [string]$AccessKeyFromFile = $null - [string]$saveChoice = $null + [string]$newKey = $null + [string]$AccessKeyFromFile = $null + [string]$saveChoice = $null + [bool]$AccessKeyValid = $false + [bool]$GeoIPFromCommandLine = $Global:Hawk.EnableGeoIPLocation + Out-LogFile "GeoIPFromCommandLine -> $GeoIPFromCommandLine" -Information } process { @@ -36,11 +39,27 @@ function Get-IPStackAPIKey { } } - # If EnableGeoIPLocation is set to true and key exists on disk (supports Hawk automation) - # If the key comes back invalid, just run the program without lookuping up GeoIP data + # NON-INTERACTIVE MODE: If EnableGeoIPLocation is set to true and key exists on disk (supports Hawk automation) + # If the key comes back invalid, continue to run the program without lookuping up GeoIP data + if ($GeoIPFromCommandLine) { + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { + Out-LogFile "GeoIP API key provided via command line: $AccessKeyFromFile" -Information + $AccessKeyValid = Test-GeoIPAPIKey -Key $AccessKeyFromFile + if ($AccessKeyValid) { + Out-LogFile "GeoIP API key found on disk is valid." -Information + return $AccessKeyFromFile + } else { + Out-LogFile "GeoIP API key found on disk is invalid." -isError + return $null + } + } else { + Out-LogFile "GeoIP API key not found on disk." -isError + return $null + } + } - # Check for existing access key on disk - if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)){ + # Check for existing access key on disk and prompt to use it if in interactive mode + if (-not [string]::IsNullOrEmpty($AccessKeyFromFile) -and (-not $GeoIPFromCommandLine)) { do { $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) Out-LogFile "Found existing API key ending in: $maskedKey" -Information @@ -70,7 +89,8 @@ function Get-IPStackAPIKey { } # If no existing access key is found on disk, prompt for a new one - if ([string]::IsNullOrEmpty($AccessKeyFromFile)) { + # Check if the user is running in interactive mode + if ([string]::IsNullOrEmpty($AccessKeyFromFile) -and (-not $GeoIPFromCommandLine)) { # Display informational messages once before looping Out-LogFile "IpStack.com requires an API access key to gather GeoIP information." -Information Out-LogFile "Get your free API key at: https://ipstack.com/" -Information diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index bcda27ff..4f501b36 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -565,6 +565,7 @@ if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { $Hawk.EnableGeoIPLocation = $EnableGeoIPLocation + Out-LogFile "INSIDE [INITALIZE HAWKGLOBALOBJECT]::NON-INTERACTIVE MODE - GEOIP: $EnableGeoIPLocation." -Information } From 4c0ce3dda3dd70b3cf8a3181f98bded401dcbf78 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 20:57:24 -0500 Subject: [PATCH 46/53] valided the log telemetry still get proceeded in non-interactive mode w/o a valid REST API Access Key from ipstack.com --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 1 + Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 9933b672..3dc54be5 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -54,6 +54,7 @@ function Get-IPStackAPIKey { } } else { Out-LogFile "GeoIP API key not found on disk." -isError + Out-LogFile "Continuing to process logs without GeoIP lookup information." -Information return $null } } diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index 4f501b36..bcda27ff 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -565,7 +565,6 @@ if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { $Hawk.EnableGeoIPLocation = $EnableGeoIPLocation - Out-LogFile "INSIDE [INITALIZE HAWKGLOBALOBJECT]::NON-INTERACTIVE MODE - GEOIP: $EnableGeoIPLocation." -Information } From b3dc3a1e287c3529fcce0fd458623b7e9c873c70 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 21:27:52 -0500 Subject: [PATCH 47/53] added GeoIPNonInteractive variable to Hawk Global Object to help determine code flow between interactive and non-interactive. --- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 5 ++--- .../functions/Initialize-HawkGlobalObject.ps1 | 14 ++++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 3dc54be5..6bf202db 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -19,9 +19,8 @@ function Get-IPStackAPIKey { [string]$newKey = $null [string]$AccessKeyFromFile = $null [string]$saveChoice = $null - [bool]$AccessKeyValid = $false - [bool]$GeoIPFromCommandLine = $Global:Hawk.EnableGeoIPLocation - Out-LogFile "GeoIPFromCommandLine -> $GeoIPFromCommandLine" -Information + [bool]$GeoIPFromCommandLine = $Global:Hawk.GeoIPNonInteractive + Out-LogFile "IPSTACKIPKEY::GeoIPFromCommandLine: $GeoIPFromCommandLine" -Information } process { diff --git a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 index bcda27ff..ca59db6b 100644 --- a/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 +++ b/Hawk/internal/functions/Initialize-HawkGlobalObject.ps1 @@ -238,13 +238,14 @@ # Create the global $Hawk variable immediately with minimal properties $Global:Hawk = [PSCustomObject]@{ - FilePath = $null # Will be set shortly - DaysToLookBack = $null - StartDate = $null - EndDate = $null - WhenCreated = $null + FilePath = $null # Will be set shortly + DaysToLookBack = $null + StartDate = $null + EndDate = $null + WhenCreated = $null EnableGeoIPLocation = $null - TenantName = $null + TenantName = $null + GeoIPNonInteractive = $false } # Set up the file path first, before any other operations @@ -565,6 +566,7 @@ if ($PSBoundParameters.ContainsKey('EnableGeoIPLocation')) { $Hawk.EnableGeoIPLocation = $EnableGeoIPLocation + $Hawk.GeoIPNonInteractive = $true } From 191561a99a89f0acb0d53f8c091ba30e00e5fd11 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Sun, 2 Mar 2025 22:01:14 -0500 Subject: [PATCH 48/53] Logic for non-interacive use case added to Hawk ISO version 4.0.1 --- Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 | 5 +++-- Hawk/internal/functions/Get-IPStackAPIKey.ps1 | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index ab217038..80b0285e 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -104,11 +104,12 @@ # Add Geo IP location information to the data if ($PSBoundParameters.ContainsKey('ResolveIPLocations')) { - Out-LogFile "Resolving IP Locations" -Action + Out-LogFile "Attemping to resolve IP Locations" -Information # Setup our counter $i = 0 # Conduct IPStack API Key validation (once) so the API Key can be passed to Get-IPGeolocation + # Get-IPStackAPIKey either returns a valid API key or null $AccessKey = Get-IPStackAPIKey # Loop thru each connection and get the Geo IP location @@ -125,7 +126,7 @@ $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey } else { - $Location = "Valid REST API Key was not provided or IP address was not found" + $Location = "Lack valid REST API key or IP address was not found" } # Combine the connection object and the location object so that we have a single output ready diff --git a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 index 6bf202db..6290e87f 100644 --- a/Hawk/internal/functions/Get-IPStackAPIKey.ps1 +++ b/Hawk/internal/functions/Get-IPStackAPIKey.ps1 @@ -20,7 +20,6 @@ function Get-IPStackAPIKey { [string]$AccessKeyFromFile = $null [string]$saveChoice = $null [bool]$GeoIPFromCommandLine = $Global:Hawk.GeoIPNonInteractive - Out-LogFile "IPSTACKIPKEY::GeoIPFromCommandLine: $GeoIPFromCommandLine" -Information } process { @@ -42,7 +41,8 @@ function Get-IPStackAPIKey { # If the key comes back invalid, continue to run the program without lookuping up GeoIP data if ($GeoIPFromCommandLine) { if (-not [string]::IsNullOrEmpty($AccessKeyFromFile)) { - Out-LogFile "GeoIP API key provided via command line: $AccessKeyFromFile" -Information + $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) + Out-LogFile "GeoIP API key provided via command line: $maskedKey" -Information $AccessKeyValid = Test-GeoIPAPIKey -Key $AccessKeyFromFile if ($AccessKeyValid) { Out-LogFile "GeoIP API key found on disk is valid." -Information @@ -59,6 +59,7 @@ function Get-IPStackAPIKey { } # Check for existing access key on disk and prompt to use it if in interactive mode + # GeoIPFromCommandLine is set to true when running in non-interactive mode if (-not [string]::IsNullOrEmpty($AccessKeyFromFile) -and (-not $GeoIPFromCommandLine)) { do { $maskedKey = "**************************" + $AccessKeyFromFile.Substring($AccessKeyFromFile.Length - 6) From 0ac07c4556ee503e36e733518b5dbb428190851c Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 3 Mar 2025 02:05:16 -0500 Subject: [PATCH 49/53] Changed Get-IPGeolocation to Get-IPGeoLocation --- Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 | 2 +- Hawk/internal/functions/Get-IPGeolocation.ps1 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 index 80b0285e..a07431dd 100644 --- a/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 +++ b/Hawk/functions/User/Get-HawkUserUALSignInLog.ps1 @@ -123,7 +123,7 @@ # Get the location information for this IP address # Need to perform access key value only once instead of for each IP address if($ExpandedUserLogonLogs.item($i).clientip -and ([string]::IsNullOrEmpty($AccessKey) -eq $false)) { - $Location = Get-IPGeolocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey + $Location = Get-IPGeoLocation -IPAddress $ExpandedUserLogonLogs.item($i).clientip -AccessKey $AccessKey } else { $Location = "Lack valid REST API key or IP address was not found" diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 index 943a1bd7..6eeb7726 100644 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeolocation.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Get-IPGeolocation is called by Get-HawkUserUALSignInLog to resolve IP addresses to geolocation data. + Get-IPGeoLocation is called by Get-HawkUserUALSignInLog to resolve IP addresses to geolocation data. An IP address and IP Stack API Key is passed to the function, as it returns a PSCustomObject with the geolocation data. .DESCRIPTION @@ -10,12 +10,12 @@ .PARAMETER AccessKey Access key for ipstack.com's REST API .EXAMPLE - Get-IPGeolocation -IPAddress 8.8.8.8 -AccessKey e904134b5cbb91f752a79f3ba9cbe59a - Gets all IP Geolocation data of IPs that recieved + Get-IPGeoLocation -IPAddress 8.8.8.8 -AccessKey e904134b5cbb91f752a79f3ba9cbe59a + Gets all IP GeoLocation data of IPs that recieved .NOTES General notes #> -function Get-IPGeolocation { +function Get-IPGeoLocation { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] From e27ffa1c2d9a7f237dafd0c5d93158cca36bb236 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 3 Mar 2025 02:14:46 -0500 Subject: [PATCH 50/53] renaming files --- Hawk/internal/functions/Get-IPGeolocation.ps1 | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 Hawk/internal/functions/Get-IPGeolocation.ps1 diff --git a/Hawk/internal/functions/Get-IPGeolocation.ps1 b/Hawk/internal/functions/Get-IPGeolocation.ps1 deleted file mode 100644 index 6eeb7726..00000000 --- a/Hawk/internal/functions/Get-IPGeolocation.ps1 +++ /dev/null @@ -1,88 +0,0 @@ -<# -.SYNOPSIS - Get-IPGeoLocation is called by Get-HawkUserUALSignInLog to resolve IP addresses to geolocation data. - An IP address and IP Stack API Key is passed to the function, as it returns a PSCustomObject with the geolocation data. - -.DESCRIPTION - Get the Geographic Location of an IP address using the ipstack.com REST API -.PARAMETER IPAddress - IP address to look up for its geographic location -.PARAMETER AccessKey - Access key for ipstack.com's REST API -.EXAMPLE - Get-IPGeoLocation -IPAddress 8.8.8.8 -AccessKey e904134b5cbb91f752a79f3ba9cbe59a - Gets all IP GeoLocation data of IPs that recieved -.NOTES - General notes -#> -function Get-IPGeoLocation { - [CmdletBinding()] - param ( - [Parameter(Mandatory = $true)] - [string]$IPAddress, - - [Parameter(Mandatory = $false)] - [string]$AccessKey - ) - - begin {} - - process { - try { - - if ($IPAddress -eq "") { - Write-Verbose "Null IP Provided: $IPAddress" - return [PSCustomObject]@{ - IP = $IPAddress - CountryName = "NULL IP" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" - KnownMicrosoftIP = "Unknown" - } - } - - # Check cache - if ($Global:IPLocationCache.ip -contains $IPAddress) { - Write-Verbose "IP Cache Hit: $IPAddress" - return ($Global:IPLocationCache | Where-Object { $_.ip -eq $IPAddress }) - } - - # Make API calls to IP Stack to look up IP addresses - $resource = "http://api.ipstack.com/$($IPAddress)?access_key=$AccessKey" - $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction Stop - $geoip | ConvertTo-Json -Depth 10 - - # Create result object - Write-Output "`n" - $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -Type $geoip.type - $result = [PSCustomObject]@{ - IP = $geoip.ip - CountryName = $geoip.country_name - ContinentName = $geoip.continent_name - RegionName = $geoip.region_name - RegionCode = $geoip.region_code - City = $geoip.city - KnownMicrosoftIP = $isMSFTIP - } - - # Update cache - [array]$Global:IPLocationCache += $result - - return $result - } - catch { - Out-LogFile "Failed to retrieve location for IP $IPAddress : $_" -isError - return [PSCustomObject]@{ - IP = $IPAddress - CountryName = "Failed to Resolve" - RegionName = "Unknown" - RegionCode = "Unknown" - ContinentName = "Unknown" - City = "Unknown" - KnownMicrosoftIP = "Unknown" - } - } - } -} \ No newline at end of file From 47a12396cf35af69045d37c949638a76ad716db4 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 3 Mar 2025 02:16:33 -0500 Subject: [PATCH 51/53] added Get-IPGeoLocation.ps1 --- Hawk/internal/functions/Get-IPGeoLocation.ps1 | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Hawk/internal/functions/Get-IPGeoLocation.ps1 diff --git a/Hawk/internal/functions/Get-IPGeoLocation.ps1 b/Hawk/internal/functions/Get-IPGeoLocation.ps1 new file mode 100644 index 00000000..6eeb7726 --- /dev/null +++ b/Hawk/internal/functions/Get-IPGeoLocation.ps1 @@ -0,0 +1,88 @@ +<# +.SYNOPSIS + Get-IPGeoLocation is called by Get-HawkUserUALSignInLog to resolve IP addresses to geolocation data. + An IP address and IP Stack API Key is passed to the function, as it returns a PSCustomObject with the geolocation data. + +.DESCRIPTION + Get the Geographic Location of an IP address using the ipstack.com REST API +.PARAMETER IPAddress + IP address to look up for its geographic location +.PARAMETER AccessKey + Access key for ipstack.com's REST API +.EXAMPLE + Get-IPGeoLocation -IPAddress 8.8.8.8 -AccessKey e904134b5cbb91f752a79f3ba9cbe59a + Gets all IP GeoLocation data of IPs that recieved +.NOTES + General notes +#> +function Get-IPGeoLocation { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$IPAddress, + + [Parameter(Mandatory = $false)] + [string]$AccessKey + ) + + begin {} + + process { + try { + + if ($IPAddress -eq "") { + Write-Verbose "Null IP Provided: $IPAddress" + return [PSCustomObject]@{ + IP = $IPAddress + CountryName = "NULL IP" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" + KnownMicrosoftIP = "Unknown" + } + } + + # Check cache + if ($Global:IPLocationCache.ip -contains $IPAddress) { + Write-Verbose "IP Cache Hit: $IPAddress" + return ($Global:IPLocationCache | Where-Object { $_.ip -eq $IPAddress }) + } + + # Make API calls to IP Stack to look up IP addresses + $resource = "http://api.ipstack.com/$($IPAddress)?access_key=$AccessKey" + $geoip = Invoke-RestMethod -Method Get -URI $resource -ErrorAction Stop + $geoip | ConvertTo-Json -Depth 10 + + # Create result object + Write-Output "`n" + $isMSFTIP = Test-MicrosoftIP -IPToTest $geoip.ip -Type $geoip.type + $result = [PSCustomObject]@{ + IP = $geoip.ip + CountryName = $geoip.country_name + ContinentName = $geoip.continent_name + RegionName = $geoip.region_name + RegionCode = $geoip.region_code + City = $geoip.city + KnownMicrosoftIP = $isMSFTIP + } + + # Update cache + [array]$Global:IPLocationCache += $result + + return $result + } + catch { + Out-LogFile "Failed to retrieve location for IP $IPAddress : $_" -isError + return [PSCustomObject]@{ + IP = $IPAddress + CountryName = "Failed to Resolve" + RegionName = "Unknown" + RegionCode = "Unknown" + ContinentName = "Unknown" + City = "Unknown" + KnownMicrosoftIP = "Unknown" + } + } + } +} \ No newline at end of file From a59ea1eba095f3c7480f94e36ff01cfe24a1cabf Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 3 Mar 2025 03:20:06 -0500 Subject: [PATCH 52/53] Modified changelog.md --- Hawk/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/changelog.md b/Hawk/changelog.md index af6049c1..6e97bab1 100644 --- a/Hawk/changelog.md +++ b/Hawk/changelog.md @@ -107,7 +107,7 @@ - Added log pull of user Send activity to the User Investigation (Get-HawkUserMailSendActivity) - Added log pull of user SharePoint Search activity to the User Investigation (Get-HawkUserSharePointSearchQuery) -## 4.0.1 (2025-3-02) +## 4.0.1 (2025-3-0X) - Fixed bug in Get-IPGeolocation where API Keys from ipstack.com were not validated - Fixed bug in Get-IPGeolocation where API Keys were not validated to meet basic sanity checks/requirements - Added commandline agrument '-EnableGeoIPLocation' to Start-HawkUserInvestigation providing the ability to skip interactive prompts when conducting investigations. \ No newline at end of file From 0d61d173384fc5a16d4a79cb2da24a6be027eb24 Mon Sep 17 00:00:00 2001 From: DCODEV1702 Date: Mon, 3 Mar 2025 03:23:33 -0500 Subject: [PATCH 53/53] Changed AccessKey Parameter from False to True --- Hawk/internal/functions/Get-IPGeoLocation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Hawk/internal/functions/Get-IPGeoLocation.ps1 b/Hawk/internal/functions/Get-IPGeoLocation.ps1 index 6eeb7726..7c7b53c3 100644 --- a/Hawk/internal/functions/Get-IPGeoLocation.ps1 +++ b/Hawk/internal/functions/Get-IPGeoLocation.ps1 @@ -21,7 +21,7 @@ function Get-IPGeoLocation { [Parameter(Mandatory = $true)] [string]$IPAddress, - [Parameter(Mandatory = $false)] + [Parameter(Mandatory = $true)] [string]$AccessKey )