├── AutoSpamEmailScan.ps1 ├── ESA_Spam_Block.ps1 ├── EwsManagedApi.msi ├── LICENSE ├── MineMeld_Indicator.ps1 ├── README.md ├── RedirectURL.py ├── Submit_FILE_Virustotal.py ├── Work Flow.jpg ├── init.conf ├── module.xml ├── pdf2url.py ├── plug-in.jpg ├── procedure.jpg ├── secureX.ps1 └── selenium_simulator.py /AutoSpamEmailScan.ps1: -------------------------------------------------------------------------------- 1 | 2 | <#PSScriptInfo 3 | 4 | .VERSION 5.2.0 5 | 6 | .GUID 134de175-8fd8-4938-9812-053ba39eed83 7 | 8 | .AUTHOR HAO BAN/banhao@gmail.com 9 | 10 | .COMPANYNAME 11 | 12 | .COPYRIGHT 13 | 14 | .TAGS 15 | 16 | .LICENSEURI https://github.com/banhao/AutoSpamEmailScan/blob/master/LICENSE 17 | 18 | .PROJECTURI 19 | 20 | .ICONURI 21 | 22 | .EXTERNALMODULEDEPENDENCIES Before you run the script Install the Exchange Web Services Managed API 2.2. https://www.microsoft.com/en-us/download/details.aspx?id=42951 23 | 24 | .REQUIREDSCRIPTS 25 | 26 | .EXTERNALSCRIPTDEPENDENCIES 27 | 28 | .RELEASENOTES 29 | Creation Date: <11/08/2022> 30 | Purpose/Change: Remove module "EncodedHTML". Add BANNER. Add module check. 31 | 32 | Creation Date: <09/21/2022> 33 | Purpose/Change: Add Hash Value into MineMeld when the attachment scan result is positive 34 | 35 | Creation Date: <09/09/2022> 36 | Purpose/Change: Add module "EncodedHTML" 37 | 38 | Creation Date: <08/17/2022> 39 | Purpose/Change: Add module "extract_hyperlink_from_Excel" 40 | 41 | Creation Date: <06/10/2022> 42 | Purpose/Change: optimize some outputs format. 43 | 44 | Creation Date: <05/31/2022> 45 | Purpose/Change: Rename some variables. 46 | 47 | Creation Date: <05/30/2022> 48 | Purpose/Change: Optimize the module "CheckRedirectedURL", skip scan the URL if the URL contain file types in variable "$EXTENSIONARRAY" 49 | 50 | Creation Date: <05/26/2022> 51 | Purpose/Change: Add "RedirectURL.py" to replace the powershell script. 52 | Add "pdf2url.py" to replace the "Bytescout.PDF2HTML.dll" 53 | Add "Submit_FILE_Virustotal.py" to replace the "Submit-FILE-Virustotal" and call VirusTotal V3 API 54 | 55 | Creation Date: <05/09/2022> 56 | Purpose/Change: Add "selenium_simulator.py" to open HTML file on local and get screenshot. 57 | 58 | Creation Date: <05/03/2022> 59 | Purpose/Change: optimize the method to extract email address from the mail body. 60 | 61 | Creation Date: <04/28/2022> 62 | Purpose/Change: Instead the "Cisco SecureX Investigation Module" with the "secureX.ps1" 63 | Add "MineMeld_Indicator.ps1" 64 | Add ESA_Spam_Block.ps1 65 | Remove "checkphish.ai" module 66 | 67 | Creation Date: <09/20/2021> 68 | Purpose/Change: Fixed Function "ESASpamQuarantine" a small bug. 69 | 70 | Creation Date: <07/08/2021> 71 | Purpose/Change: add Cisco SecureX Investigation Module 72 | 73 | Creation Date: <05/26/2021> 74 | Purpose/Change: Fixed some bugs 75 | 76 | Creation Date: <05/19/2021> 77 | Purpose/Change: Add "BlockedMailSelfRelease" function. If you donnot have ESA/SMA or donott want to use ESA/SMA API to block SPAM sender or release blocked emails from quarantine then please setup "ENABLEESASPAMBL" and "ENABLESELFRELEASE" as "False" in init.conf 78 | 79 | Creation Date: <05/13/2021> 80 | Purpose/Change: Add "slblconfig EXPORT" after update the Cisco Email Security Appliance Spam Quarantine Blacklist.(related to Cisco Bug CSCvx12488) 81 | ssh PRIVATE KEY must be save in "c:\users\\.ssh\" folder. ".ssh" folder must disable "inheritance" and manually grant "local\SYSTEM" group, "local\Administrators" group "full control" privilege, and current user "read only" privilege. 82 | 83 | Creation Date: <04/05/2021> 84 | Purpose/Change: Optimize function CheckRedirectedURL{} 85 | 86 | Creation Date: <03/25/2021> 87 | Purpose/Change: Update function CheckRedirectedURL{} 88 | 89 | Creation Date: <03/19/2021> 90 | Purpose/Change: Add a new module for Cisco Email Security Appliance Spam Quarantine Blacklist. 91 | 92 | Creation Date: <11/10/2020> 93 | Purpose/Change: Move emails to sub-folder when after the checking. 94 | 95 | Creation Date: <04/03/2020> 96 | Purpose/Change: Optimize the parameters setting. 97 | 98 | Creation Date: <04/02/2020> 99 | Purpose/Change: Add new feature to let the use input the credential just chose "N" when prompt "salt is empty". Add SystemException, fix the broken of the system error. 100 | 101 | Creation Date: <03/10/2020> 102 | Purpose/Change: Add a new Function CheckRedirectedURL, this feature is used to detect URLs that try to escape the scan. 103 | Change "function Submit-URL-Virustotal" to use the VirusTotal API V3 104 | 105 | Creation Date: <02/11/2020> 106 | Purpose/Change: Add checkphish.ai API limit error 107 | 108 | Creation Date: <01/22/2020> 109 | Purpose/Change: Add a new Function checkphish.ai 110 | 111 | Creation Date: <10/21/2019> 112 | Purpose/Change: One funcation name was changed but calls the old name in the program. Update the Bytescout.PDF2HTML.dll to version 10.6.0.3667. It's still a trial version and will expire after 90 days. If you see this error: 113 | -------------------------------------------------------------------------------------- 114 | "new-object : Exception calling ".ctor" with "0" argument(s): "Trial period expired." 115 | + $extractor = new-object Bytescout.PDF2HTML.HTMLExtractor 116 | -------------------------------------------------------------------------------------- 117 | That means the DLL file has been expired. 118 | 119 | .PRIVATEDATA 120 | 121 | .DESCRIPTION AutoSpamEmailScan.ps1 is used to monitor a specific mailbox that in enterprise users can forward suspicious spam emails to a specific mailbox. 122 | This PowerShell script can monitor the mailbox for any unread emails, grab the URLs and attachments from the emails and submit to virustotal.com, urlscan.io, Google safe browsing and OPSWAT. Script also can extract URLs from a pdf file. 123 | After the scan finished, script can generate HTML format scan report and auto reply to the senders. 124 | Script can be run once or loop interval, if in the init.conf is 0 means script will only run one time else the number is the loop interval seconds. 125 | 126 | Visit https://github.com/banhao/AutoSpamEmailScan to get the init.conf and Bytescout.PDF2HTML.dll, this dll file is used to convert PDF to HTML. 127 | 128 | Please check the License before you download this script, if you don't agree with the License please don't download and use this script. https://github.com/banhao/AutoSpamEmailScan/blob/master/LICENSE 129 | 130 | The Password is base64 encoded and saved in init.conf, following is the example about how to genertae the encoded password: 131 | "JkPgsiG9Zh0XCvk" is the password. 132 | "yp9P7" is the salt. make sure salt is the unique string that can't have the same pattern in the password. 133 | Insert the salt into password where ever you want: 134 | yp9P7JkPgsiG9Zh0XCvk, JkPgsiG9Zh0XCvkyp9P7, JkPgyp9P7siG9Zh0XCvk, JkPgsiG9Zh0XCyp9P7vk, ...... all these are legitimate. 135 | 136 | Generate the base64 encoded string: 137 | [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("yp9P7JkPgsiG9Zh0XCvk")) 138 | eQBwADkAUAA3AEoAawBQAGcAcwBpAEcAOQBaAGgAMABYAEMAdgBrAA== 139 | [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("JkPgsiG9Zh0XCvkyp9P7")) 140 | SgBrAFAAZwBzAGkARwA5AFoAaAAwAFgAQwB2AGsAeQBwADkAUAA3AA== 141 | [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("JkPgyp9P7siG9Zh0XCvk")) 142 | SgBrAFAAZwB5AHAAOQBQADcAcwBpAEcAOQBaAGgAMABYAEMAdgBrAA== 143 | [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("JkPgsiG9Zh0XCyp9P7vk")) 144 | SgBrAFAAZwBzAGkARwA5AFoAaAAwAFgAQwB5AHAAOQBQADcAdgBrAA== 145 | 146 | Save the encoded string in the init.conf file. 147 | 148 | Decode the encoded string: 149 | [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("eQBwADkAUAA3AEoAawBQAGcAcwBpAEcAOQBaAGgAMABYAEMAdgBrAA==")) 150 | yp9P7JkPgsiG9Zh0XCvk 151 | [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SgBrAFAAZwBzAGkARwA5AFoAaAAwAFgAQwB2AGsAeQBwADkAUAA3AA==")) 152 | JkPgsiG9Zh0XCvkyp9P7 153 | [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SgBrAFAAZwB5AHAAOQBQADcAcwBpAEcAOQBaAGgAMABYAEMAdgBrAA==")) 154 | JkPgyp9P7siG9Zh0XCvk 155 | [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SgBrAFAAZwBzAGkARwA5AFoAaAAwAFgAQwB5AHAAOQBQADcAdgBrAA==")) 156 | JkPgsiG9Zh0XCyp9P7vk 157 | 158 | Even someone can get the encoded string from the init.conf and use base64 to decode it, but they don't know the salt, so they still can't get the password directly. 159 | 160 | This PowerShell passed the test in PowerShell version 5.1.16299.1146. Can not run on Powershell version 4 and below. 161 | PS H:\>host 162 | Check the PowerShell version. 163 | 164 | #> 165 | 166 | 167 | #------------------------------------------------------------------------------------------------------------------------------------------------------- 168 | #variables 169 | param ($CREDENTIAL,$SALT) 170 | cls 171 | $BANNER = @" 172 | 173 | # ##### ####### ##### 174 | # # # # ##### #### # # ##### ## # # # # # ## # # # # #### ## # # 175 | # # # # # # # # # # # # ## ## # ## ## # # # # # # # # # ## # 176 | # # # # # # # ##### # # # # # ## # ##### # ## # # # # # ##### # # # # # # 177 | ####### # # # # # # ##### ###### # # # # # ###### # # # # ###### # # # 178 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # ## 179 | # # #### # #### ##### # # # # # ####### # # # # # ###### ##### #### # # # # 180 | 181 | Copyright © Hao Ban - 2022 182 | Issue Date: November 07, 2022 183 | Version: 5.2.0 184 | Author: Hao Ban 185 | "@ 186 | Write-Host $BANNER `r`n 187 | 188 | $ALLPYTHONMODULEOFF = $false 189 | $PYTHONVERSION = &{python -V} 2>&1 190 | 191 | if($PYTHONVERSION -is [System.Management.Automation.ErrorRecord]) { 192 | $PYTHONVERSION.Exception.Message 193 | Write-Host "***** CAN NOT FIND PYTHON ENVIRONMENT. *****" -ForegroundColor red `r`n 194 | Write-Host "***** All THE PYTHON MODULES WILL BE DISABLED. *****" -ForegroundColor red `r`n 195 | $ALLPYTHONMODULEOFF = $true 196 | }else { 197 | if ([System.Version]$PYTHONVERSION.split("")[1] -lt [System.Version]"3.6.0") { 198 | Write-Host "***** THE PYTHON VERSION DOES NOT MATCH THE MINIMUM REQUIREMENT. *****" -ForegroundColor red `r`n 199 | Write-Host "***** All THE PYTHON MODULES WILL BE DISABLED. *****" -ForegroundColor red `r`n 200 | $ALLPYTHONMODULEOFF = $true 201 | } 202 | } 203 | 204 | $global:MODULE_LIST = New-Object System.Collections.Generic.Dictionary"[String,String]" 205 | $MODULES = $(Select-Xml -Path .\module.xml -XPath "//SETTINGS/MODULE" | foreach {$_.node}).'#text' 206 | $xmlSETTINGS = [xml](Get-Content -Path .\module.xml) 207 | foreach ($module in $MODULES){ 208 | if ( $module.trim() -like "*.py" -and $ALLPYTHONMODULEOFF ) { 209 | $global:MODULE_LIST.add($($module.trim()), "OFF") 210 | Write-Host "##### [$($module.trim())] is turned" -NoNewline 211 | Write-Host " [OFF] " -ForegroundColor red -NoNewline 212 | Write-Host "#####" `r 213 | $COMMENT = $($xmlSETTINGS.SelectNodes("//SETTINGS/MODULE[contains(.,'$module')]/COMMENT").'#text').Trim() 214 | Write-Host " |-$COMMENT" `r`n 215 | 216 | }else { 217 | if ( Test-Path -path ".\$($module.trim())" ){ 218 | $ONOFF = $($xmlSETTINGS.SelectNodes("//SETTINGS/MODULE[contains(.,'$module')]/ENABLE").'#text').Trim() 219 | $global:MODULE_LIST.add($($module.trim()), $ONOFF) 220 | if ( $ONOFF -eq "OFF" ) { 221 | Write-Host "##### [$($module.trim())] is in the current folder and is turned" -NoNewline 222 | Write-Host " [$ONOFF] " -ForegroundColor red -NoNewline 223 | Write-Host "#####" `r 224 | $COMMENT = $($xmlSETTINGS.SelectNodes("//SETTINGS/MODULE[contains(.,'$module')]/COMMENT").'#text').Trim() 225 | Write-Host " |-$COMMENT" `r`n 226 | }else{ 227 | Write-Host "##### [$($module.trim())] is in the current folder and is turned [$ONOFF] #####" `r`n -ForegroundColor green 228 | } 229 | }else{ 230 | $global:MODULE_LIST.add($($module.trim()), "OFF") 231 | Write-Host "***** CANNOT FIND [$($module.trim())] IN THE CURRENT FOLDER, SO THE FEATURE WILL BE DISABLED. *****" -ForegroundColor red `r 232 | $COMMENT = $($xmlSETTINGS.SelectNodes("//SETTINGS/MODULE[contains(.,'$module')]/COMMENT").'#text').Trim() 233 | Write-Host " |-$COMMENT" `r`n 234 | } 235 | } 236 | } 237 | 238 | if ( [string]::IsNullOrEmpty($CREDENTIAL) -and [string]::IsNullOrEmpty($SALT) ){ 239 | $YorN = Read-Host "Do you want to input the Credential? [ y/n ] (Default is y)" 240 | if ( $YorN -match "[yY]" -or ([string]::IsNullOrEmpty($YorN))){ 241 | $USERNAME = Get-Content .\init.conf | findstr ADUSERNAME | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 242 | $CREDENTIAL = Get-Credential -credential $USERNAME 243 | $PASSWORD = $CREDENTIAL.Password 244 | }else{ 245 | $ENCODEDPASSWORD = Get-Content .\init.conf | findstr ADPASSWORD | %{ $_.Split(':')[1]; } | foreach{ $_.ToString().Trim() } 246 | $USERNAME = Get-Content .\init.conf | findstr ADUSERNAME | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 247 | if ( [string]::IsNullOrEmpty($SALT) ){ 248 | $YorN = Read-Host "The salt is empty. Do you want to input the salt to decrypt the password? [ y/n ] (Default is y)" 249 | if ( $YorN -match "[yY]" -or ([string]::IsNullOrEmpty($YorN))){ 250 | $SALT = Read-Host -assecurestring "Please input the salt" 251 | $PASSWORD = ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD))).Replace($([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SALT))),"") 252 | }else{ $PASSWORD = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD)) } 253 | }else { $PASSWORD = ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD))).Replace($SALT,"") } 254 | } 255 | }else{ 256 | $ENCODEDPASSWORD = Get-Content .\init.conf | findstr ADPASSWORD | %{ $_.Split(':')[1]; } | foreach{ $_.ToString().Trim() } 257 | $USERNAME = Get-Content .\init.conf | findstr ADUSERNAME | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 258 | if ( [string]::IsNullOrEmpty($SALT) ){ 259 | $YorN = Read-Host "The salt is empty. Do you want to input the salt to decrypt the password? [ y/n ] (Default is y)" 260 | if ( $YorN -match "[yY]" -or ([string]::IsNullOrEmpty($YorN))){ 261 | $SALT = Read-Host -assecurestring "Please input the salt" 262 | $PASSWORD = ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD))).Replace($([System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SALT))),"") 263 | }else{ $PASSWORD = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD)) } 264 | }else { $PASSWORD = ([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODEDPASSWORD))).Replace($SALT,"") } 265 | } 266 | 267 | $DOMAIN = Get-Content .\init.conf | findstr DOMAIN | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 268 | $EMAILADDRESS = Get-Content .\init.conf | findstr EMAILADDRESS | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 269 | $EWSDLLPATH = Get-Content .\init.conf | findstr EWSDLLPATH | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 270 | $DOWNLOADDIRECTORY = Get-Content .\init.conf | findstr DOWNLOADDIRECTORY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 271 | $REPORTSDIRECTORY = Get-Content .\init.conf | findstr REPORTSDIRECTORY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 272 | $EXTENSIONARRAY = Get-Content .\init.conf | findstr EXTENSIONARRAY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 273 | $EXEMPTURL = (Get-Content .\init.conf | findstr EXEMPTURL | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() }).split(",") 274 | $SUBFOLDER = Get-Content .\init.conf | findstr SUBFOLDER | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 275 | $INTERVAL = [int]$(Get-Content .\init.conf | findstr INTERVAL | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() }) 276 | $VIRUSTOTAL_API_KEY = Get-Content .\init.conf | findstr VIRUSTOTAL_API_KEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 277 | $URLSCAN_API_KEY = Get-Content .\init.conf | findstr URLSCAN_API_KEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 278 | $GOOGLE_API_KEY = Get-Content .\init.conf | findstr GOOGLE_API_KEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 279 | $OPSWAT_API_KEY = Get-Content .\init.conf | findstr OPSWAT_API_KEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 280 | $CHECKPHISH_API_KEY = Get-Content .\init.conf | findstr CHECKPHISH_API_KEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 281 | 282 | $global:enable_SecureX = $false 283 | 284 | function Google-Safe-Browsing { 285 | Write-OutPut "Google Safe Browsing Scan Report: " >> $LOGFILE 286 | $BODY = @() 287 | $BODY +=[pscustomobject]@{"client" = @{"clientId" = "eHealth Saskatche"; "clientVersion" = "1.0"}; "threatInfo" = @{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING"; "platformTypes" = "WINDOWS"; "threatEntryTypes" = "URL"; "threatEntries" = @{"url" = "$URL"}}} 288 | $HEADERS = @{ 'Content-Type' = "application/json" } 289 | $JSONBODY = $BODY | ConvertTo-Json 290 | $Uri = 'https://safebrowsing.googleapis.com/v4/threatMatches:find?key='+ $GOOGLE_API_KEY 291 | $Results = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS 292 | if ( ([string]::IsNullOrEmpty($Results)) ) { 293 | Write-OutPut "Can not find the result in Google Safe Browsing Scan." >> $LOGFILE 294 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 295 | }else{ 296 | $ThreatType = $Results | select -expand matches | select threatType 297 | if ( ($ThreatType.threatType -eq "SOCIAL_ENGINEERING") -or ($ThreatType.threatType -eq "MALWARE") -or ($ThreatType.threatType -eq "POTENTIALLY_HARMFUL_APPLICATION") ) { $global:enable_SecureX = $true } 298 | Write-OutPut "Google Safe Browsing Scan Results: ",$($ThreatType) >> $LOGFILE 299 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 300 | } 301 | } 302 | 303 | function Submit-URLSCAN { 304 | Write-OutPut "URLscan Scan Report: " >> $LOGFILE 305 | $BODY = @{ 'url' = "$URL"; 'public' = 'on' } 306 | $HEADERS = @{ 'API-Key' = "$URLSCAN_API_KEY" } 307 | Try { $SCANRESPONSE = Invoke-RestMethod -Method 'POST' -Uri 'https://urlscan.io/api/v1/scan/' -Headers $HEADERS -Body $BODY } Catch { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) } 308 | if ( [string]::IsNullOrEmpty($SCANRESPONSE) ) { 309 | $reader.BaseStream.Position = 0 310 | $reader.DiscardBufferedData() 311 | $Exception = $reader.ReadToEnd() | ConvertFrom-Json 312 | } 313 | if ( -not ([string]::IsNullOrEmpty($Exception)) ) { 314 | Write-Output "Exception Error:" $Exception.description >> $LOGFILE 315 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 316 | }else{ 317 | $RESPONSEAPI = $SCANRESPONSE.api 318 | Do { 319 | Start-Sleep -s 30 320 | $RESPONSE = try { $SCANRESULT = Invoke-RestMethod -Method 'GET' -Uri $RESPONSEAPI } catch { $_.Exception.Response.StatusCode.Value__} 321 | }Until($RESPONSE -ne 404) 322 | $ReportURL = $SCANRESULT.task.reportURL 323 | $ScreenShot = $SCANRESULT.task.screenshotURL 324 | 325 | Write-OutPut "ScanReportURL: ",$($ReportURL) >> $LOGFILE 326 | Write-OutPut "ScreenShotURL: ",$($ScreenShot) >> $LOGFILE 327 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 328 | Start-Sleep -s 3 329 | } 330 | } 331 | 332 | function Submit-URL-Virustotal { 333 | $BODY = @{ "url" = "$URL" } 334 | $HEADERS = @{ "x-apikey" = "$VIRUSTOTAL_API_KEY" } 335 | $SCAN = Invoke-RestMethod -Method 'POST' -Uri "https://www.virustotal.com/api/v3/urls" -Headers $HEADERS -Body $BODY 336 | $RESULTID = $SCAN.data.id | %{ $_.Split('-')[1]; } | foreach{ $_.ToString().Trim() } 337 | $PERMALINK = "https://virustotal.com/gui/url/"+$RESULTID+"/detection" 338 | Start-Sleep -s 30 339 | $SCANRESULTS = Invoke-RestMethod -Method 'GET' -Uri "https://www.virustotal.com/api/v3/urls/$RESULTID" -Headers $HEADERS 340 | Write-OutPut "VirusTotal URL Scan Report: " >> $LOGFILE 341 | Write-OutPut $PERMALINK >> $LOGFILE 342 | Write-OutPut "VirusTotal URL Scan Stats: " >> $LOGFILE 343 | Write-OutPut $SCANRESULTS.data.attributes.last_analysis_stats >> $LOGFILE 344 | if ( ($SCANRESULTS.data.attributes.last_analysis_stats.malicious -gt 0) -or ($SCANRESULTS.data.attributes.last_analysis_stats.suspicious -gt 0) ) { $global:enable_SecureX = $true } 345 | Write-OutPut "VirusTotal URL COMMUNITY VOTES : " >> $LOGFILE 346 | Write-OutPut $SCANRESULTS.data.attributes.total_votes >> $LOGFILE 347 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 348 | } 349 | 350 | function Submit-FILE-OPSWAT { 351 | $URI = 'https://api.metadefender.com/v4/hash/'+$HASH 352 | $HEADERS = @{} 353 | $HEADERS.Add('apikey', $OPSWAT_API_KEY) 354 | $RESPONSE = try { $SCANRESULT = Invoke-RestMethod -Method 'GET' -Uri $URI -Headers $HEADERS } catch { $_.Exception.Response.StatusCode.Value__ } 355 | if ( $RESPONSE -eq 404){ 356 | $FILENAME = Split-Path $FILEPATH -leaf 357 | $URI = 'https://api.metadefender.com/v4/file' 358 | $HEADERS = @{} 359 | $HEADERS.Add('apikey', $OPSWAT_API_KEY) 360 | $HEADERS.Add('filename', $FILENAME) 361 | $SCANRESULT = Invoke-RestMethod -Method 'Post' -Uri $URI -Headers $HEADERS -Body $FILEPATH -ContentType 'application/octet-stream' 362 | $HASH = $SCANRESULT.sha256 363 | $DATA_ID = $SCANRESULT.data_id 364 | $URI = 'https://api.metadefender.com/v4/file/'+$DATA_ID 365 | $HEADERS = @{} 366 | $HEADERS.Add('apikey', $OPSWAT_API_KEY) 367 | Do { 368 | Start-Sleep -s 5 369 | $RESPONSE = try { $SCANRESULT = Invoke-RestMethod -Method 'GET' -Uri $URI -Headers $HEADERS } catch { $_.Exception.Response.StatusCode.Value__} 370 | } Until ($RESPONSE -ne 404) 371 | Write-OutPut "OPSWAT MetaDefender Cloud File Scan Report: " >> $LOGFILE 372 | $RESULTLINK = 'https://metadefender.opswat.com/results#!/file/'+$HASH+'/hash/overview' 373 | Write-OutPut $RESULTLINK >> $LOGFILE 374 | Write-OutPut "POSITIVES | TOTAL" >> $LOGFILE 375 | Write-OutPut "$($SCANRESULT.scan_results.total_detected_avs) | $($SCANRESULT.scan_results.total_avs)" >> $LOGFILE 376 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 377 | }else { 378 | Write-OutPut "OPSWAT MetaDefender Cloud File Scan Report: " >> $LOGFILE 379 | $RESULTLINK = 'https://metadefender.opswat.com/results#!/file/'+$HASH+'/hash/overview' 380 | Write-OutPut $RESULTLINK >> $LOGFILE 381 | Write-OutPut "POSITIVES | TOTAL" >> $LOGFILE 382 | Write-OutPut "$($SCANRESULT.scan_results.total_detected_avs) | $($SCANRESULT.scan_results.total_avs)" >> $LOGFILE 383 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 384 | } 385 | } 386 | 387 | function FromEmailAttachment { 388 | $EMLData = Get-Content $Args[0] 389 | $AdoDbStream = New-Object -ComObject ADODB.Stream 390 | $AdoDbStream.Open() 391 | $AdoDbStream.LoadFromFile($Args[0]) 392 | $global:CdoMessage = New-Object -ComObject CDO.Message 393 | $global:CdoMessage.DataSource.OpenObject($AdoDbStream,"_Stream") 394 | Write-OutPut "===From: $($global:CdoMessage.From)" >> $LOGFILE 395 | Write-OutPut "===To: $($global:CdoMessage.To)" >> $LOGFILE 396 | Write-OutPut "===Subject: $($global:CdoMessage.Subject)" >> $LOGFILE 397 | Write-OutPut "===DateTimeReceived: $($global:CdoMessage.SentOn)" >> $LOGFILE 398 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 399 | $TextBody = $global:CdoMessage.Fields.Item("urn:schemas:httpmail:textdescription").Value 400 | $HTMLBody = $global:CdoMessage.Fields.Item("urn:schemas:httpmail:htmldescription").Value 401 | $EmailBODY = $TextBody + $HTMLBody + $EMLData 402 | $URLLIST = $EmailBODY | select-string -pattern $URLRegEx -AllMatches | %{ $_.Matches } | %{ $_.Value } | Sort-Object | ? {$EXEMPTURL -notcontains $_} | Get-Unique 403 | $EXPLIST = $EXEMPTURL | foreach-object { $URLLIST -match $_ } 404 | $URLARRAY = @() 405 | foreach ($URL in $URLLIST){ if ( $URL -notin $EXPLIST ){$URLARRAY = $URLARRAY += $URL }} 406 | if ( -not ([string]::IsNullOrEmpty($URLARRAY)) ){ 407 | foreach($URL in $URLARRAY){ 408 | Write-OutPut "URL: ",$URL >> $LOGFILE 409 | CheckRedirectedURL 410 | } 411 | } 412 | $BOUNDARY = $global:CdoMessage.Fields.Item("urn:schemas:mailheader:content-type").Value | %{ $_.Split(';')[1]; } | %{ $_.Split('"')[1]; } 413 | for ($i=1;$i -le $global:CdoMessage.Attachments.count;$i++){ 414 | $ContentMediaType = $global:CdoMessage.Attachments.Item($i).ContentMediaType 415 | $FILENAME = $global:CdoMessage.Attachments.Item($i).FileName 416 | $AttachmentPATTERN = """$FILENAME""(.*?) --$BOUNDARY" 417 | $ATTACHDATA = [regex]::match($EMLData, $AttachmentPATTERN).Groups[1].Value 418 | if ( -not [string]::IsNullOrEmpty($FILENAME) ){ 419 | $TRIMNUM = $ATTACHDATA.LastIndexOf(" ")+2 420 | $ATTACHMENTDATA = $ATTACHDATA.Remove(0,$TRIMNUM) 421 | $ATTFILENAME = ($DOWNLOADDIRECTORY + $FILENAME.split('.')[0].trim() + "_" + $RANDOMID + "." + $FILENAME.split('.')[-1].trim()) 422 | Try{ $bytes = [Convert]::FromBase64String($ATTACHMENTDATA) } catch { $Exception = $_.Exception } 423 | if ( -not ([string]::IsNullOrEmpty($Exception)) ) { 424 | Write-Output "Exception Error:" $Exception.description >> $LOGFILE 425 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 426 | } 427 | if ( -not ([string]::IsNullOrEmpty($bytes)) ) { 428 | [IO.File]::WriteAllBytes($ATTFILENAME, $bytes) 429 | Write-OutPut "Downloaded Attachment : Original File $($FILENAME) Saved As $($ATTFILENAME) " >> $LOGFILE 430 | Try { $ALGORITHM = (Get-FileHash ($ATTFILENAME)).Algorithm } 431 | Catch [System.SystemException] { $ExceptionError = $_.Exception.Message } 432 | if ( [string]::IsNullOrEmpty($ExceptionError) ) { 433 | $HASH = (Get-FileHash ($ATTFILENAME)).Hash.ToLower() 434 | $FILEPATH = (Get-FileHash ($ATTFILENAME)).Path 435 | Write-OutPut "Attachment $ALGORITHM Hash : " $HASH >> $LOGFILE 436 | $EXTENSION = [System.IO.Path]::GetExtension($ATTFILENAME) 437 | if ( ($EXTENSION -eq ".pdf") -or ($EXTENSION -eq ".htm") -or ($EXTENSION -eq ".html") -or ($EXTENSION -eq ".shtml") ){ 438 | if ( $global:MODULE_LIST.'Submit_FILE_Virustotal.py' -eq "ON" ) { 439 | Write-OutPut "=====================Submit File to VirusTotal and OPSWAT=====================" >> $LOGFILE 440 | python Submit_FILE_Virustotal.py $FILEPATH >> $LOGFILE 441 | $scan_result = Get-Content $LOGFILE -Tail 1 | ConvertFrom-Json 442 | if ( ($scan_result.'malicious' -ne 0) -or ($scan_result.'suspicious' -ne 0) ) { 443 | if ( $global:MODULE_LIST.'MineMeld_Indicator.ps1' -eq "ON" ) { 444 | .\MineMeld_Indicator.ps1 $HASH $ALGORITHM.ToLower() -comment "AutoSpamEmailScan Script Detected and Blocked" >> $LOGFILE 445 | Write-OutPut "$($HASH) has been added into MineMeld." >> $LOGFILE 446 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 447 | } 448 | } 449 | } 450 | Submit-FILE-OPSWAT 451 | Write-OutPut "=====================Extract URLs from the PDF/HTML file=====================" >> $LOGFILE 452 | ExtractURLFromPDFHTML 453 | if ( $global:MODULE_LIST.'selenium_simulator.py' -eq "ON" ) { 454 | Write-OutPut "=====================Selenimu Simulator=====================" >> $LOGFILE 455 | python selenium_simulator.py $ATTFILENAME $LOGFILE >> $LOGFILE 456 | } 457 | if ( ($EXTENSION -eq ".htm") -or ($EXTENSION -eq ".html") -or ($EXTENSION -eq ".shtml") ){ 458 | $HTMLCONTENT = Get-Content($FILEPATH) 459 | if ( ($HTMLCONTENT -like "*atob('*')*") -or ($HTMLCONTENT -like "*document.write(unescape('*')*") -or ($HTMLCONTENT -like "*base64*") -or ($HTMLCONTENT -like "*\u00*") ){ 460 | Write-OutPut "********************************************************************" >> $LOGFILE 461 | Write-OutPut "* *" >> $LOGFILE 462 | Write-OutPut "* HTML FILE IS ENCODED AND IT IS HIGHLY SUSPICIOUS *" >> $LOGFILE 463 | Write-OutPut "* *" >> $LOGFILE 464 | Write-OutPut "********************************************************************" >> $LOGFILE 465 | } 466 | } 467 | }else { 468 | if ( -not ([string]::IsNullOrEmpty($FILEPATH)) ){ 469 | if ( $global:MODULE_LIST.'Submit_FILE_Virustotal.py' -eq "ON" ) { 470 | Write-OutPut "=====================Submit File to VirusTotal and OPSWAT=====================" >> $LOGFILE 471 | python Submit_FILE_Virustotal.py $FILEPATH >> $LOGFILE 472 | $scan_result = Get-Content $LOGFILE -Tail 1 | ConvertFrom-Json 473 | if ( ($scan_result.'malicious' -ne 0) -or ($scan_result.'suspicious' -ne 0) ) { 474 | if ( $global:MODULE_LIST.'MineMeld_Indicator.ps1' -eq "ON" ) { 475 | .\MineMeld_Indicator.ps1 $HASH $ALGORITHM.ToLower() -comment "AutoSpamEmailScan Script Detected and Blocked" >> $LOGFILE 476 | Write-OutPut "$($HASH) has been added into MineMeld." >> $LOGFILE 477 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 478 | } 479 | } 480 | } 481 | Submit-FILE-OPSWAT 482 | } 483 | } 484 | }else { 485 | Write-OutPut "********************************************************************" >> $LOGFILE 486 | Write-Output "Exception Error:" $ExceptionError >> $LOGFILE 487 | Write-OutPut "********************************************************************" >> $LOGFILE 488 | } 489 | } 490 | } 491 | } 492 | } 493 | 494 | function ConvertLogToHTML { 495 | $File = Get-Content $LOGFILE 496 | $FileLine = @() 497 | Foreach ($Line in $File) { 498 | $MyObject = New-Object -TypeName PSObject 499 | if ( ($Line -match "virustotal.com") -or ($Line -match "urlscan.io") -or ($Line -match "opswat.com") -or ($Line -match "checkphish.ai") -or ($Line -match "googleapis.com") ){ 500 | if ($Line -match ".png"){ 501 | Add-Member -InputObject $MyObject -Type NoteProperty -Name "Security Scan Report" -Value "$Line" 502 | }else{ Add-Member -InputObject $MyObject -Type NoteProperty -Name "Security Scan Report" -Value "$Line" } 503 | }else{ Add-Member -InputObject $MyObject -Type NoteProperty -Name "Security Scan Report" -Value $Line } 504 | $FileLine += $MyObject 505 | } 506 | $($FileLine | ConvertTo-Html -Title "Security Scan Report" -Property "Security Scan Report" ) -replace '>','>' -replace '<','<' -replace ''',"'" | Out-File $HTMLREPFILE 507 | } 508 | 509 | function ExtractURLFromPDFHTML { 510 | if ( $EXTENSION -eq ".pdf" ){ 511 | if ( $global:MODULE_LIST.'pdf2url.py' -eq "ON" ) { 512 | $URLArrayFromPDF = & python pdf2url.py $ATTFILENAME 513 | } 514 | }else{ 515 | $HTMLFILE = $ATTFILENAME 516 | $URLArrayFromHTML = Get-Content $HTMLFILE | select-string -pattern $URLRegEx -AllMatches | %{ $_.Matches } | %{ $_.Value } | Sort-Object | Get-Unique 517 | } 518 | if ( ![string]::IsNullOrEmpty($URLArrayFromPDF) -and ![string]::IsNullOrEmpty($URLArrayFromHTML) ) { 519 | $URLLIST = $URLArrayFromHTML + $URLArrayFromPDF | Sort-Object | Get-Unique 520 | }elseif( [string]::IsNullOrEmpty($URLArrayFromPDF) -and ![string]::IsNullOrEmpty($URLArrayFromHTML) ) { 521 | $URLLIST = $URLArrayFromHTML | Sort-Object | Get-Unique 522 | }elseif( ![string]::IsNullOrEmpty($URLArrayFromPDF) -and [string]::IsNullOrEmpty($URLArrayFromHTML) ) { 523 | $URLLIST = $URLArrayFromPDF | Sort-Object | Get-Unique 524 | } 525 | $EXPLIST = $EXEMPTURL | foreach-object { $URLLIST -match $_ } 526 | $URLARRAY = @() 527 | foreach ($URL in $URLLIST){ if ( $URL -notin $EXPLIST ){$URLARRAY = $URLARRAY += $URL }} 528 | # URL is not null or empty do check the URL 529 | if ( -not ([string]::IsNullOrEmpty($URLARRAY)) ){ 530 | foreach($URL in $URLARRAY){ 531 | Write-OutPut "URL: ",$URL >> $LOGFILE 532 | CheckRedirectedURL 533 | } 534 | }else{ Write-OutPut "=====================No URL in the PDF/HTML file needs to scan=====================" >> $LOGFILE } 535 | } 536 | 537 | function CheckRedirectedURL { 538 | if ( $URL -like '*safelinks.protection.outlook.com*' ) { $URL = [System.Web.HttpUtility]::ParseQueryString($(New-Object -TypeName System.Uri -ArgumentList $URL).Query)["url"] } 539 | if ($global:MODULE_LIST.'RedirectURL.py' -eq "ON") { 540 | $URLAccessible = & python RedirectURL.py $URL 541 | }else{ 542 | $URLAccessible = $URL 543 | } 544 | if ($URLAccessible -match "is not accessible.") { 545 | Write-OutPut "$($URL) is not accessible." >> $LOGFILE 546 | }else{ 547 | Write-OutPut " |" >> $LOGFILE 548 | Write-Output " |--> The Redirected URL is: $($URLAccessible)" >> $LOGFILE 549 | if ( (! $([System.Uri]$URL).AbsolutePath.EndsWith('/')) -and ($EXTENSIONARRAY.contains($(([System.Uri]$URL).Segments)[-1].split(".")[-1])) ) { 550 | Write-OutPut "$($(([System.Uri]$URL).Segments)[-1]) no needs to scan" >> $LOGFILE 551 | }else{ 552 | Submit-URL-Virustotal 553 | Submit-URLSCAN 554 | Google-Safe-Browsing 555 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 556 | if ( $global:enable_SecureX ) { 557 | if ( $global:MODULE_LIST.'MineMeld_Indicator.ps1' -eq "ON" ) { 558 | .\MineMeld_Indicator.ps1 $URL URL -comment "AutoSpamEmailScan Script Detected and Blocked" >> $LOGFILE 559 | Write-OutPut "$($URL) has been added into MineMeld." >> $LOGFILE 560 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 561 | } 562 | if ( $global:MODULE_LIST.'secureX.ps1' -eq "ON" ) { 563 | .\secureX.ps1 $URL >> $LOGFILE 564 | Write-OutPut "secureX and MDATP investigation is done." >> $LOGFILE 565 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 566 | } 567 | if ( $global:MODULE_LIST.'ESA_Spam_Block.ps1' -eq "ON" ) { 568 | if ( ![string]::IsNullOrEmpty($global:CdoMessage) ) { 569 | $regex = [regex]"\<(.*)\>" 570 | $Blocklist_Sender = $($regex.match($($global:CdoMessage.From)).Groups[1].value).ToLower() 571 | }else{ 572 | $regex = "From:.*?(?<=[\[\<]).+?(?=[\]\>])" 573 | $regex_eml = '([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)' 574 | if ( @([regex]::Matches($EMAIL.Body.Text, $regex).value).length -gt 1 ) { $Blocklist_Sender = [regex]::Matches($([regex]::Matches($EMAIL.Body.Text, $regex).Value[-1]), $regex_eml).value[-1] }else{ $Blocklist_Sender = [regex]::Matches($([regex]::Matches($EMAIL.Body.Text, $regex).Value), $regex_eml).value } 575 | } 576 | if ( ![string]::IsNullOrEmpty($Blocklist_Sender) ) { 577 | .\ESA_Spam_Block.ps1 $Blocklist_Sender ALL >> $LOGFILE 578 | Write-OutPut "SPAM Sender $($Blocklist_Sender) has been blacklisted." >> $LOGFILE 579 | } 580 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 581 | } 582 | $global:enable_SecureX = $false 583 | } 584 | } 585 | } 586 | Write-OutPut "====================================================================" >> $LOGFILE 587 | } 588 | 589 | function extract_hyperlink_from_Excel { 590 | $URLLIST = @() 591 | $excel = New-Object -ComObject excel.application 592 | $excel.visible = $False 593 | $workbook = $excel.Workbooks.Open($ATTFILENAME) 594 | $workbook.saveas($DOWNLOADDIRECTORY+'temp',44) 595 | #Looping the Workbook sheet to check all the sheets in the Excel 596 | for ($loop_index = 1; $loop_index -le $workbook.Sheets.Count; $loop_index++) 597 | { 598 | #Assigining the hyperlinks to the Variable from each sheet 599 | $Sheet = $workbook.Worksheets($loop_index).Hyperlinks 600 | #looping the Sheet one by one and store it in collection 601 | $Sheet | ForEach-Object{ 602 | $Link = $_.Address 603 | $URLLIST = $URLLIST += $Link 604 | } 605 | } 606 | #Kill the excel 607 | $workbook.close() 608 | Stop-Process -name excel 609 | Remove-Item -Path "$($DOWNLOADDIRECTORY)temp.*" 610 | Remove-Item -Force -Recurse -Path "$($DOWNLOADDIRECTORY)temp_files" 611 | $URLARRAY = @() 612 | foreach ($URL in $URLLIST){ if ( $URL -notin $EXPLIST ){$URLARRAY = $URLARRAY += $URL }} 613 | # URL is not null or empty do check the URL 614 | if ( -not ([string]::IsNullOrEmpty($URLARRAY)) ){ 615 | foreach($URL in $URLARRAY){ 616 | Write-OutPut "URL: ",$URL >> $LOGFILE 617 | CheckRedirectedURL 618 | } 619 | } 620 | } 621 | 622 | function MAIN { 623 | date 624 | If(!(test-path $DOWNLOADDIRECTORY)){ New-Item -ItemType directory -Path $DOWNLOADDIRECTORY } 625 | If(!(test-path $REPORTSDIRECTORY)){ New-Item -ItemType directory -Path $REPORTSDIRECTORY } 626 | Import-Module $EWSDLLPATH 627 | $SERVICE = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2) 628 | $SERVICE.Credentials = New-Object Net.NetworkCredential($USERNAME, $PASSWORD, $DOMAIN) 629 | $SERVICE.AutodiscoverUrl($EMAILADDRESS) 630 | $INBOX = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($SERVICE,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox) 631 | $FOLDERROOT = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($SERVICE,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::msgFolderRoot) 632 | $SUBFOLDERID = ($FOLDERROOT.FindFolders([Microsoft.Exchange.WebServices.Data.FolderView]100) | where { $_.DisplayName -eq $SUBFOLDER }).Id 633 | $PROPERTYSET = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties) 634 | $PROPERTYSET.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text 635 | [System.Net.ServicePointManager]::SecurityProtocol = @("Tls12","Tls11","Tls","Ssl3") 636 | # [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 637 | # Use .Net Object to ignore self-signed certificate 638 | if ("TrustAllCertsPolicy" -as [type]) {} 639 | else { 640 | Add-Type @" 641 | using System.Net; 642 | using System.Security.Cryptography.X509Certificates; 643 | public class TrustAllCertsPolicy : ICertificatePolicy { 644 | public bool CheckValidationResult( 645 | ServicePoint srvPoint, X509Certificate certificate, 646 | WebRequest request, int certificateProblem) { 647 | return true; 648 | } 649 | } 650 | "@ 651 | [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 652 | } 653 | if ( $INBOX.TotalCount -ne 0 ){ 654 | $ITEMS = $INBOX.FindItems($INBOX.TotalCount) 655 | foreach ( $EMAIL in $ITEMS.Items ){ 656 | # only get unread emails 657 | if( $EMAIL.isread -eq $false ){ 658 | if( ($EMAIL.Subject -ne "Thank you for contacting the eHealth Saskatchewan Service Desk") -or ($EMAIL.Subject -ne "Service Desk Auto Reply for emailsecurity@ehealthsask.ca") ){ 659 | # load the property set to get to the body 660 | $EMAIL.load($PROPERTYSET) 661 | $RANDOMID = -join ((48..57) + (97..122) | Get-Random -Count 20 | % {[char]$_}) 662 | $LOGFILE = $REPORTSDIRECTORY+"security-scan-report_"+$RANDOMID+".log" 663 | $HTMLREPFILE = $REPORTSDIRECTORY+"security-scan-report_"+$RANDOMID+".html" 664 | $SCREENSHOTFILE = $REPORTSDIRECTORY+"screenshot_"+$RANDOMID+".jpg" 665 | # output the results - first of all the From, Subject and Date Time Received 666 | Write-OutPut "====================================================================" > $LOGFILE 667 | Write-OutPut "From: $($EMAIL.From)" >> $LOGFILE 668 | Write-OutPut "To: $($EMAIL.ToRecipients)" >> $LOGFILE 669 | Write-OutPut "Subject: $($EMAIL.Subject)" >> $LOGFILE 670 | Write-OutPut "DateTimeReceived: $($EMAIL.DateTimeReceived)" >> $LOGFILE 671 | Write-OutPut "====================================================================" >> $LOGFILE 672 | $URLRegEx = '\b(?:(?:https?|ftp|file)://|www\.|ftp\.)(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\([-A-Z0-9+&@#/%=~_|$?!:,.]*\)|[A-Z0-9+&@#/%=~_|$])' 673 | $URLLIST = $($EMAIL.Body.Text) | select-string -pattern $URLRegEx -AllMatches | %{ $_.Matches } | %{ $_.Value } | Sort-Object | Get-Unique 674 | $EXPLIST = $EXEMPTURL | foreach-object { $URLLIST -match $_ } 675 | $URLARRAY = @() 676 | foreach ($URL in $URLLIST){ if ( $URL -notin $EXPLIST ){$URLARRAY = $URLARRAY += $URL }} 677 | # URL is not null or empty do check the URL 678 | if ( -not ([string]::IsNullOrEmpty($URLARRAY)) ){ 679 | foreach($URL in $URLARRAY){ 680 | Write-OutPut "URL: ",$URL >> $LOGFILE 681 | CheckRedirectedURL 682 | } 683 | } 684 | foreach($ATTACH in $EMAIL.Attachments){ 685 | if ( ![string]::IsNullOrEmpty($ATTACH.Name)){ 686 | $EXTENSION = [System.IO.Path]::GetExtension($ATTACH.Name.ToString().ToLower()) 687 | }else{ $EXTENSION ="" } 688 | # only save the file that extension is not in the extension list 689 | if ( !$EXTENSIONARRAY.contains($EXTENSION) -or [string]::IsNullOrEmpty($EXTENSION) ){ 690 | if ( ($ATTACH.ContentType -eq "message/rfc822") -or ([string]::IsNullOrEmpty($ATTACH.ContentType)) -and ($ATTACH.PSobject.Properties.name -match "Item") ){ 691 | Write-OutPut "========================The attachment is an email=========================" >> $LOGFILE 692 | $MIMEPROPERTYSET = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.ItemSchema]::MimeContent) 693 | $ATTACH.Load($MIMEPROPERTYSET) 694 | $AttachmentData = $ATTACH.Item.MimeContent.Content 695 | $ATTFILENAME = ($DOWNLOADDIRECTORY + [GUID]::NewGuid().ToString() + "_MSG.eml") 696 | $EXTENSION = ".eml" 697 | }else{ 698 | $ATTACH.Load() 699 | $AttachmentData = $ATTACH.Content 700 | $ATTFILENAME = ($DOWNLOADDIRECTORY + $ATTACH.Name.ToString().Trim("")) 701 | } 702 | Try { $ATTFILE = new-object System.IO.FileStream(($ATTFILENAME), [System.IO.FileMode]::Create) } 703 | Catch [System.SystemException] { $ExceptionError = $_.Exception.Message } 704 | if ( [string]::IsNullOrEmpty($ExceptionError) ) { 705 | $ATTFILE.Write($AttachmentData, 0, $AttachmentData.Length) 706 | $ATTFILE.Close() 707 | Write-OutPut "Downloaded Attachment : $($ATTFILENAME)" >> $LOGFILE 708 | Try { $ALGORITHM = (Get-FileHash ($ATTFILENAME)).Algorithm } 709 | Catch [System.SystemException] { $ExceptionError = $_.Exception.Message } 710 | if ( [string]::IsNullOrEmpty($ExceptionError) ) { 711 | $HASH = (Get-FileHash ($ATTFILENAME)).Hash.ToLower() 712 | $FILEPATH = (Get-FileHash ($ATTFILENAME)).Path 713 | Write-OutPut "Attachment $ALGORITHM Hash : $($HASH)" >> $LOGFILE 714 | if ( ($EXTENSION -eq ".eml") -or ($EXTENSION -eq ".raw") ){ 715 | FromEmailAttachment $ATTFILENAME 716 | } else{ 717 | if ( ($EXTENSION -eq ".xlsx") -or ($EXTENSION -eq ".xlsm") -or ($EXTENSION -eq ".xls") -or ($EXTENSION -eq ".xlsb") -or ($EXTENSION -eq ".xltx") -or ($EXTENSION -eq ".xltm") -or ($EXTENSION -eq ".xlt") -or ($EXTENSION -eq ".xla") -or ($EXTENSION -eq ".xlam") -or ($EXTENSION -eq ".xlsm") ){ 718 | extract_hyperlink_from_Excel 719 | } 720 | if ( ($EXTENSION -eq ".pdf") -or ($EXTENSION -eq ".htm") -or ($EXTENSION -eq ".html") -or ($EXTENSION -eq ".shtml") ){ 721 | if ( $global:MODULE_LIST.'Submit_FILE_Virustotal.py' -eq "ON" ) { 722 | Write-OutPut "=====================Submit File to VirusTotal and OPSWAT=====================" >> $LOGFILE 723 | python Submit_FILE_Virustotal.py $FILEPATH >> $LOGFILE 724 | $scan_result = Get-Content $LOGFILE -Tail 1 | ConvertFrom-Json 725 | if ( ($scan_result.'malicious' -ne 0) -or ($scan_result.'suspicious' -ne 0) ) { 726 | if ( $global:MODULE_LIST.'MineMeld_Indicator.ps1' -eq "ON" ) { 727 | .\MineMeld_Indicator.ps1 $HASH $ALGORITHM.ToLower() -comment "AutoSpamEmailScan Script Detected and Blocked" >> $LOGFILE 728 | Write-OutPut "$($HASH) has been added into MineMeld." >> $LOGFILE 729 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 730 | } 731 | } 732 | } 733 | Submit-FILE-OPSWAT 734 | Write-OutPut "=====================Extract URLs from the PDF/HTML file=====================" >> $LOGFILE 735 | ExtractURLFromPDFHTML 736 | if ( $global:MODULE_LIST.'selenium_simulator.py' -eq "ON" ) { 737 | Write-OutPut "=====================Selenimu Simulator=====================" >> $LOGFILE 738 | python selenium_simulator.py $ATTFILENAME $LOGFILE >> $LOGFILE 739 | } 740 | if ( ($EXTENSION -eq ".htm") -or ($EXTENSION -eq ".html") -or ($EXTENSION -eq ".shtml") ){ 741 | $HTMLCONTENT = Get-Content($FILEPATH) 742 | if ( ($HTMLCONTENT -like "*atob('*')*") -or ($HTMLCONTENT -like "*document.write(unescape('*')*") -or ($HTMLCONTENT -like "*base64*") -or ($HTMLCONTENT -like "*\u00*") ){ 743 | Write-OutPut "********************************************************************" >> $LOGFILE 744 | Write-OutPut "* *" >> $LOGFILE 745 | Write-OutPut "* HTML FILE IS ENCODED AND IT IS HIGHLY SUSPICIOUS *" >> $LOGFILE 746 | Write-OutPut "* *" >> $LOGFILE 747 | Write-OutPut "********************************************************************" >> $LOGFILE 748 | } 749 | } 750 | }else { 751 | if ( -not ([string]::IsNullOrEmpty($FILEPATH)) ){ 752 | if ( $global:MODULE_LIST.'Submit_FILE_Virustotal.py' -eq "ON" ) { 753 | Write-OutPut "=====================Submit File to VirusTotal and OPSWAT=====================" >> $LOGFILE 754 | python Submit_FILE_Virustotal.py $FILEPATH >> $LOGFILE 755 | $scan_result = Get-Content $LOGFILE -Tail 1 | ConvertFrom-Json 756 | if ( ($scan_result.'malicious' -ne 0) -or ($scan_result.'suspicious' -ne 0) ) { 757 | if ( $global:MODULE_LIST.'MineMeld_Indicator.ps1' -eq "ON" ) { 758 | .\MineMeld_Indicator.ps1 $HASH $ALGORITHM.ToLower() -comment "AutoSpamEmailScan Script Detected and Blocked" >> $LOGFILE 759 | Write-OutPut "$($HASH) has been added into MineMeld." >> $LOGFILE 760 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" >> $LOGFILE 761 | } 762 | } 763 | } 764 | Submit-FILE-OPSWAT 765 | } 766 | } 767 | } 768 | } else { 769 | Write-OutPut "********************************************************************" >> $LOGFILE 770 | Write-Output "Exception Error:" $ExceptionError >> $LOGFILE 771 | Write-OutPut "********************************************************************" >> $LOGFILE 772 | } 773 | } else { 774 | Write-OutPut "********************************************************************" >> $LOGFILE 775 | Write-Output "Exception Error:" $ExceptionError >> $LOGFILE 776 | Write-OutPut "********************************************************************" >> $LOGFILE 777 | } 778 | } 779 | } 780 | Write-OutPut "================================END=================================" >> $LOGFILE 781 | ConvertLogToHTML 782 | $REPLYSUBJECT = "AUTO-REPLY/Security Scan Report-- "+$($EMAIL.Subject) 783 | $SMTPSERVER = Get-Content .\init.conf | findstr SMTPSERVER | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 784 | $REPLYTO = Get-Content .\init.conf | findstr REPLYTO | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 785 | $REPLYCC = Get-Content .\init.conf | findstr REPLYCC | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 786 | $EMAIBODY = '%CUSTOMER_EMAIL=' + $($EMAIL.From.Address) + "`r`n" + '%CUSTOMER=' + $($EMAIL.From.Name) + "`r`n" + '%SUMMARY=Security Scan Report--' + $($EMAIL.Subject) 787 | if ( Test-Path -Path $SCREENSHOTFILE ) { 788 | $ATTACHMENTS = @($HTMLREPFILE, $SCREENSHOTFILE) 789 | Send-MailMessage -SmtpServer $SMTPSERVER -To $REPLYTO -From $EMAILADDRESS -Cc $REPLYCC -Subject $REPLYSUBJECT -Body $EMAIBODY -Attachments $ATTACHMENTS 790 | }else { 791 | Send-MailMessage -SmtpServer $SMTPSERVER -To $REPLYTO -From $EMAILADDRESS -Cc $REPLYCC -Subject $REPLYSUBJECT -Body $EMAIBODY -Attachments $HTMLREPFILE 792 | } 793 | } 794 | } 795 | $EMAIL.isRead = $true 796 | $EMAIL.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AutoResolve) 797 | $EMAIL.Move($SUBFOLDERID) 798 | } 799 | }else{ Write-OutPut "==============There is no email in the inbox==================" } 800 | } 801 | 802 | # Main Procedure 803 | if ( $INTERVAL -eq 0 ){ 804 | MAIN 805 | }else{ 806 | while($true){ 807 | MAIN 808 | Write-Host -NoNewline "==============After"$INTERVAL" seconds will check again==============" 809 | "" 810 | Start-Sleep -s $INTERVAL 811 | } 812 | } 813 | -------------------------------------------------------------------------------- /ESA_Spam_Block.ps1: -------------------------------------------------------------------------------- 1 | 2 | <#PSScriptInfo 3 | 4 | .VERSION 1.2 5 | 6 | .GUID 134de175-8fd8-4938-9812-053ba39eed83 7 | 8 | .AUTHOR HAO BAN/banhao@gmail.com 9 | 10 | .COMPANYNAME 11 | 12 | .COPYRIGHT 13 | 14 | .TAGS 15 | 16 | .LICENSEURI https://github.com/banhao/ESA_SPAM_QUARANTINE_BLOCKLIST/blob/main/LICENSE 17 | 18 | .PROJECTURI 19 | 20 | .ICONURI 21 | 22 | .EXTERNALMODULEDEPENDENCIES 23 | 24 | .REQUIREDSCRIPTS 25 | 26 | .EXTERNALSCRIPTDEPENDENCIES 27 | 28 | .RELEASENOTES 29 | Creation Date: <01/24/2022> 30 | 31 | .PRIVATEDATA 32 | 33 | .SYNOPSIS 34 | 35 | .EXAMPLE 36 | 37 | .DESCRIPTION ESA_SPAM_QUARANTINE_BLOCKLIST.ps1 is used to add "email address" or "domain name" into ESA SPAM QUARANTINE BLOCKLIST by calling the ESA API. 38 | ESA_SPAM_QUARANTINE_BLOCKLIST.ps1 [-Sender] [[-Recipient] ] 39 | 40 | #> 41 | 42 | #------------------------------------------------------------------------------------------------------------------------------------------------------- 43 | 44 | [CmdletBinding(DefaultParameterSetName = "Indicator")] 45 | Param( 46 | [Parameter(ParameterSetName="Indicator", Mandatory=$true, Position=0, HelpMessage="---`"Sender`" is mandatory, please input an email address or a domain name(e.g.: user@domain.com, server.domain.com, domain.com)---")] 47 | [ValidateNotNullOrEmpty()] 48 | [string]$Sender, 49 | 50 | [Parameter(ParameterSetName="Indicator", Mandatory=$false, Position=1, HelpMessage="---Please input `"Recipient`" which can be an email address or a domain name---")] 51 | [string]$Recipient 52 | ) 53 | 54 | 55 | function ValidateEmailorDomain($arg) { 56 | ($arg -match "^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$") -or ($arg -match "^\w+([-+.']\w+)*\w+([-.]\w+)*\.\w+([-.]\w+)*$") 57 | } 58 | 59 | function ValidateEmail($arg) { 60 | $arg -match "^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$" 61 | } 62 | 63 | function ValidateDomain($arg) { 64 | $arg -match "^\w+([-+.']\w+)*\w+([-.]\w+)*\.\w+([-.]\w+)*$" 65 | } 66 | 67 | function ESASpamQuarantine($Block_Sender, $Block_Recipient) { 68 | if ( $(ValidateDomain($Block_Recipient)) ) { 69 | $Recipient_domain = '.*@'+$($Block_Recipient.split('@')[1]) 70 | }else{ 71 | $Recipient_domain = $Block_Recipient 72 | } 73 | 74 | $HEADERS = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 75 | $HEADERS.Add("Authorization", "Basic $ESACREDENTIAL") 76 | $HEADERS.Add("Content-Type", "text/plain") 77 | $SenderList = $(Invoke-RestMethod -Method 'GET' -Uri "$ESAURL1/esa/api/v2.0/quarantine/blocklist?action=view&quarantineType=spam&viewBy=recipient&search=$Block_Recipient" -Headers $HEADERS).data.senderList 78 | $SenderList_domain = $(Invoke-RestMethod -Method 'GET' -Uri "$ESAURL1/esa/api/v2.0/quarantine/blocklist?action=view&quarantineType=spam&viewBy=recipient&search=$Recipient_domain" -Headers $HEADERS).data.senderList 79 | if ( ([string]::IsNullOrEmpty($SenderList)) -and ([string]::IsNullOrEmpty($SenderList_domain)) ){ 80 | $BODY = "{ `n`"action`": `"add`", `n`"quarantineType`": `"spam`", `n`"viewBy`": `"recipient`", `n`"senderList`": [`"$Block_Sender`"], `n`"recipientAddresses`": [`"$Block_Recipient`"] `n}" 81 | $Response_1 = Invoke-RestMethod -Method 'POST' -Uri "$ESAURL1/esa/api/v2.0/quarantine/blocklist" -Headers $HEADERS -Body $BODY 82 | $Response_2 = Invoke-RestMethod -Method 'POST' -Uri "$ESAURL2/esa/api/v2.0/quarantine/blocklist" -Headers $HEADERS -Body $BODY 83 | Write-OutPut "********************************************************************" 84 | Write-Output $Response_1 | ConvertTo-Json 85 | Write-Output $Response_2 | ConvertTo-Json 86 | Write-OutPut "********************************************************************" 87 | }else{ 88 | if ( ($Block_Sender -in $SenderList) -or ($Block_Sender -in $SenderList_domain) ){ 89 | Write-OutPut "********************************************************************" 90 | Write-OutPut "$Block_Sender was already blocked in $Block_Recipient Blocklist." 91 | Write-OutPut "********************************************************************" 92 | }else{ 93 | $BODY = "{ `n`"action`": `"append`", `n`"quarantineType`": `"spam`", `n`"viewBy`": `"sender`", `n`"senderAddresses`": [`"$Block_Sender`"], `n`"recipientList`": [`"$Block_Recipient`"] }" 94 | $Response_1 = Invoke-RestMethod -Method 'POST' -Uri "$ESAURL1/esa/api/v2.0/quarantine/blocklist" -Headers $HEADERS -Body $BODY 95 | $Response_2 = Invoke-RestMethod -Method 'POST' -Uri "$ESAURL2/esa/api/v2.0/quarantine/blocklist" -Headers $HEADERS -Body $BODY 96 | Write-OutPut "********************************************************************" 97 | Write-OutPut "*************************Block the Sender***************************" 98 | Write-OutPut "********************************************************************" 99 | Write-Output $Response_1 | ConvertTo-Json 100 | Write-Output $Response_2 | ConvertTo-Json 101 | Write-OutPut "********************************************************************" 102 | } 103 | } 104 | } 105 | 106 | $ESAUSER = Get-Content .\init.conf | findstr ESAUSER | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 107 | $ESACREDENTIAL = Get-Content .\init.conf | findstr ESACREDENTIAL | %{ $_.Split(':')[1]; } | foreach{ $_.ToString().Trim() } 108 | $ESAURL1 = Get-Content .\init.conf | findstr ESAURL1 | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 109 | $ESAURL2 = Get-Content .\init.conf | findstr ESAURL2 | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 110 | $HOST1 = $([System.Uri]$ESAURL1).Host 111 | $HOST2 = $([System.Uri]$ESAURL2).Host 112 | $PRIVATEKEY = Get-Content .\init.conf | findstr PRIVATEKEY | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 113 | 114 | if ( (-not [string]::IsNullOrEmpty($Sender)) -and (ValidateEmailorDomain($Sender)) ){ 115 | $regex = [regex]".*\..*" 116 | $RAT_DomainList = $(ssh -i ~/.ssh/id_rsa_esa $ESAUSER@$HOST1 "clustermode cluster; listenerconfig EDIT InboundMail RCPTACCESS PRINT" | %{ $_.Split(' ')[0];} | %{ $regex.match($_) }).value | Where-Object {$_} 117 | 118 | if ( [string]::IsNullOrEmpty($Recipient)){ 119 | $i = 1 120 | $menu = @{} 121 | Write-Host "0. ALL" 122 | $menu.Add(0, "ALL") 123 | foreach($line in $RAT_DomainList) { 124 | Write-Host "$i. $line" 125 | $menu.Add($i, ($line)) 126 | $i++ 127 | } 128 | [int]$ans = Read-Host "Please select the Domain from the `"Recipient Access Table`" that you want to block for [ 0 - $($i-1) ]" 129 | $selection = $menu.Item($ans) 130 | if ( ([string]::IsNullOrEmpty($selection)) ){ 131 | write-output "------------------------------------------------------------------------------------------------------" 132 | Write-Output "Selection Wrong, Please correct it and try again." 133 | }else { 134 | if ($selection -eq "ALL"){ 135 | foreach($line in $RAT_DomainList) { 136 | ESASpamQuarantine $Sender.tolower() $('.*@'+$line).tolower() 137 | } 138 | }else { 139 | ESASpamQuarantine $Sender.tolower() $('.*@'+$selection).tolower() 140 | } 141 | } 142 | }else{ 143 | if ($Recipient -eq "ALL"){ 144 | foreach($line in $RAT_DomainList) { 145 | ESASpamQuarantine $Sender.tolower() $('.*@'+$line).tolower() 146 | } 147 | }else { 148 | if ( ValidateEmail($Recipient) ){ 149 | ESASpamQuarantine $Sender.tolower() $Recipient.tolower() 150 | }else{ 151 | if ( (ValidateDomain($Recipient)) -and ($Recipient -in $RAT_DomainList) ){ 152 | ESASpamQuarantine $Sender.tolower() $('.*@'+$Recipient).tolower() 153 | }else{ Write-OutPut( "$Recipient is not valid, it only can be an email address or a domain name (e.g.: user@domain.com, server.domain.com, domain.com)") } 154 | } 155 | } 156 | } 157 | ssh -i ~\.ssh\$PRIVATEKEY $ESAUSER@$HOST1 "slblconfig EXPORT" 158 | ssh -i ~\.ssh\$PRIVATEKEY $ESAUSER@$HOST2 "slblconfig EXPORT" 159 | }else{ Write-OutPut( "$Sender is not valid, it only can be an email address or a domain name (e.g.: user@domain.com, server.domain.com, domain.com)") } 160 | -------------------------------------------------------------------------------- /EwsManagedApi.msi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banhao/AutoSpamEmailScan/5f76ef1b2f9521bdbe71b626d8f658958aa1582a/EwsManagedApi.msi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 banhao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MineMeld_Indicator.ps1: -------------------------------------------------------------------------------- 1 | 2 | <#PSScriptInfo 3 | 4 | .VERSION 1.13 5 | 6 | .GUID 134de175-8fd8-4938-9812-053ba39eed83 7 | 8 | .AUTHOR HAO BAN/banhao@gmail.com 9 | 10 | .COMPANYNAME 11 | 12 | .COPYRIGHT 13 | 14 | .TAGS 15 | 16 | .LICENSEURI 17 | 18 | .PROJECTURI 19 | 20 | .ICONURI 21 | 22 | .EXTERNALMODULEDEPENDENCIES 23 | 24 | .REQUIREDSCRIPTS 25 | 26 | .EXTERNALSCRIPTDEPENDENCIES 27 | 28 | .RELEASENOTES 29 | Creation Date: <05/30/2022> 30 | 31 | .PRIVATEDATA 32 | 33 | .SYNOPSIS 34 | 35 | .EXAMPLE 36 | 37 | .DESCRIPTION MineMeld_Indicator.ps1 38 | 39 | #> 40 | 41 | #------------------------------------------------------------------------------------------------------------------------------------------------------- 42 | 43 | [CmdletBinding(DefaultParameterSetName = "Indicator")] 44 | Param( 45 | [Parameter(ParameterSetName="Indicator", Mandatory=$true, Position=0, HelpMessage="---Please input `"Indicator`" which support URL/domain/sha256/sha1/md5/IPv4/IPv6/email address---")] 46 | [ValidateNotNullOrEmpty()] 47 | [string]$Indicator, 48 | 49 | [Parameter(ParameterSetName="Indicator", Mandatory=$true, Position=1, HelpMessage="---Please input Indicator `"Type`", only can be `"URL | domain | sha256 | sha1 | md5 | IPv4 | IPv6 | email-addr`"---")] 50 | [ValidateNotNullOrEmpty()] 51 | [ValidateSet("URL", "domain", "sha256", "sha1", "md5", "IPv4", "IPv6", "email-addr")] 52 | [string]$Type, 53 | 54 | [Parameter(ParameterSetName="Indicator", Mandatory=$false, Position=2, HelpMessage="---Please input `"Expire`" Seconds---")] 55 | [AllowEmptyCollection()] 56 | [int[]]$Expire, 57 | 58 | [Parameter(ParameterSetName="Indicator", Mandatory=$true, Position=3, HelpMessage="---Please input `"Comment`", if includes space please use double quote---")] 59 | [AllowEmptyString()] 60 | [string]$Comment 61 | ) 62 | 63 | If( $Indicator:paramMissing -or $Type:paramMissing ){ 64 | throw "---USAGE: MineMeld_Indicator.ps1 [Expire]---" 65 | } 66 | 67 | $MineMeldServer = Get-Content .\init.conf | findstr MineMeldServer | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 68 | $MineMeldNode = Get-Content .\init.conf | findstr MineMeldNode | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 69 | $MINEMELDCREDENTIAL = Get-Content .\init.conf | findstr MINEMELDCREDENTIAL | %{ $_.Split(':')[1]; } | foreach{ $_.ToString().Trim() } 70 | $MineMeldApiUrl = $MineMeldServer + "/config/data/" + $MineMeldNode + "_indicators/append?h=" + $MineMeldNode + "&t=localdb" 71 | $HEADERS = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 72 | $HEADERS.Add("Authorization", "Basic $MINEMELDCREDENTIAL") 73 | $HEADERS.Add("Content-Type", "application/json") 74 | if ( [string]::IsNullOrEmpty($Expire) ){ 75 | [string]$Expire = "disabled" 76 | $BODY = "{ `n`"indicator`": `"$Indicator`", `n`"type`": `"$Type`", `n`"comment`": `"$Comment`", `n`"share_level`": `"green`", `n`"confidence`": 100, `n`"ttl`": `"$Expire`" `n}" 77 | }else{ 78 | $BODY = "{ `n`"indicator`": `"$Indicator`", `n`"type`": `"$Type`", `n`"comment`": `"$Comment`", `n`"share_level`": `"green`", `n`"confidence`": 100, `n`"ttl`": $Expire `n}" 79 | } 80 | $BODY 81 | Try { $MineMeldResponse = Invoke-RestMethod -Method 'POST' -Uri $MineMeldApiUrl -Headers $HEADERS -Body $BODY } Catch { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) } 82 | if ( [string]::IsNullOrEmpty($MineMeldResponse) ) { 83 | $reader.BaseStream.Position = 0 84 | $reader.DiscardBufferedData() 85 | if ( -not ([string]::IsNullOrEmpty($reader.ReadToEnd())) ) { 86 | Write-Output "Exception Error:" $reader.ReadToEnd() 87 | } 88 | }else{ $MineMeldResponse | ConvertTo-Json } 89 | 90 | 91 | if ( ($Type -eq "URL") -or ($Type -eq "domain") ){ 92 | if ($Type -eq "URL"){ 93 | $DstDomain = $([URI]$Indicator).host 94 | $DstURL = $Indicator 95 | 96 | }else{ 97 | $DstDomain = $Indicator 98 | $DstURL = $Indicator 99 | } 100 | $Umbrella_API_Key = Get-Content .\init.conf | findstr Umbrella_API_Key | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 101 | $URL = "https://s-platform.api.opendns.com/1.0/events?customerKey=" + $Umbrella_API_Key 102 | $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 103 | $headers.Add("Content-Type", "application/json") 104 | $DeviceID = ((get-itemproperty -path HKLM:\SOFTWARE\Microsoft\SQMClient -Name MachineID).MachineId).Trim("{","}") 105 | $DeviceVersion = "Windows"+$([environment]::OSVersion.Version.Major)+"/"+$([environment]::OSVersion.Version.Build) 106 | $ProviderName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name -replace "\\", "-" 107 | $AlertTime = Get-Date -UFormat "%Y-%m-%dT%T%Z" 108 | if ( [string]::IsNullOrEmpty($Comment)){ 109 | $ProtocolVersion = "1.0" 110 | }else{ $ProtocolVersion = $Comment } 111 | $body = "{`n `"alertTime`": `"$AlertTime`",`n `"deviceId`": `"$DeviceID`",`n `"deviceVersion`": `"$DeviceVersion`",`n `"dstDomain`": `"$DstDomain`",`n `"dstUrl`": `"$DstURL`",`n `"eventTime`": `"$AlertTime`",`n `"protocolVersion`": `"$ProtocolVersion`",`n `"providerName`": `"$ProviderName`"`n}" 112 | $response = Invoke-RestMethod $URL -Method 'POST' -Headers $headers -Body $body 113 | $response | ConvertTo-Json 114 | 115 | $URL = "https://s-platform.api.opendns.com/1.0/domains?customerKey=" + $Umbrella_API_Key 116 | $response = Invoke-RestMethod $URL -Method 'GET' -Headers $headers 117 | $domain_list = @($response.data.name) 118 | while($true){ 119 | if ($response.meta.next) { 120 | $URL = $response.meta.next 121 | $response = Invoke-RestMethod $URL -Method 'GET' -Headers $headers 122 | $domain_list += @($response.data.name) 123 | }else{ break } 124 | } 125 | foreach ($domain in $domain_list) { 126 | if ($domain -eq $DstDomain) { 127 | Write-OutPut "$($domain) is added into OpenDNS Block List successfully." 128 | break 129 | } 130 | } 131 | } 132 | 133 | 134 | <# 135 | 136 | #Delete 137 | $body = "" 138 | $URL = "https://s-platform.api.opendns.com/1.0/domains/" + $Indicator + "?customerKey=" + $Umbrella_API_Key 139 | $response = Invoke-RestMethod $URL -Method 'DELETE' -Headers $headers -Body $body 140 | $response | ConvertTo-Json 141 | 142 | #> 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Spam Email Scan 2 | [![Minimum Supported PowerShell Version](https://img.shields.io/badge/PowerShell-5.1+-purple.svg)](https://github.com/PowerShell/PowerShell) ![Cross Platform](https://img.shields.io/badge/platform-windows-lightgrey) 3 | [![PowerShell Gallery Version](https://img.shields.io/powershellgallery/v/AutoSpamEmailScan)](https://www.powershellgallery.com/packages/AutoSpamEmailScan) [![PowerShell Gallery](https://img.shields.io/powershellgallery/dt/AutoSpamEmailScan)](https://www.powershellgallery.com/packages/AutoSpamEmailScan) 4 | 5 | UPDATE: Version 5.2.0 November 08, 2022 6 | 7 | - Remove module "EncodedHTML". Add BANNER. Add module check. 8 | 9 | 10 | UPDATE: Version 5.1.6 September 21, 2022 11 | 12 | - Add Hash Value into MineMeld when the attachment scan result is positive 13 | 14 | 15 | UPDATE: Version 5.1.5 September 09, 2022 16 | 17 | - Add module "Base64Decode" 18 | 19 | 20 | UPDATE: Version 5.1.4 August 17, 2022 21 | 22 | - Add module "extract_hyperlink_from_Excel" 23 | 24 | 25 | UPDATE: Version 5.1.3 June 10, 2022 26 | 27 | - optimize some outputs format 28 | 29 | 30 | UPDATE: Version 5.1.2 May 31, 2022 31 | 32 | - Rename some variables. Update "RedirectURL.py" 33 | 34 | 35 | UPDATE: Version 5.1.1 May 30, 2022 36 | 37 | - Optimize the module "CheckRedirectedURL", skip scan the URL if the URL contain file types in variable "$EXTENSIONARRAY" 38 | 39 | 40 | UPDATE: Version 5.1.0 May 26, 2022 41 | 42 | - Add "RedirectURL.py" to replace the powershell script. 43 | - Add "pdf2url.py" to replace the "Bytescout.PDF2HTML.dll" 44 | - Add "Submit_FILE_Virustotal.py" to replace the "Submit-FILE-Virustotal" and call VirusTotal V3 API 45 | 46 | 47 | UPDATE: Version 5.0.1 May 03, 2022 48 | 49 | - optimize the method to extract email address from the mail body. 50 | 51 | 52 | UPDATE: Version 5.0.0 April 29, 2022 53 | 54 | - This version I removed some modules and replaced them by using independ powershell scripts. So I want this one can be more flexible if you want to use this script as a main frame and bulid your own work flow. 55 | 56 | 57 | 58 | 59 | 60 | Notice: "Cisco Email Reporting Plug-in" or "NotifySecurity" Plug-in are not necessary. It's just optional for the end users to have the one-click ability. Without these plug-in the end users still can forward the spam emails to a specific mail box for the investigation. 61 | 62 | But I still recommend to use "Cisco Email Reporting Plug-in" because after custom the buttons you can do lots of things. 63 | 64 | Imagine the following scenario: You have an email security poolicy in your company that all encrypted email will be quarantined by Cisco ESA because the attackers will use encrypted emails to bypass the security inspection. The end users will receive an notification email and asked them to confirm that they known the sender and it's a business related email and they are expecting this email. Then the end users forward the notification email to the Service Desk or Security Team to ask releasing the email from the quarantine. 65 | 66 | So you can involve this "Cisco Email Reporting Plug-in" and make it automatically. You can custom one button and display as "Release this email", when the end users click this button on the notification email, it will be encapsulated as a raw-data and forward to "releaseemail@yourconpany.com". You can run a script to monitor "releaseemail@yourconpany.com" and grab the information from the email and call the ESA API to release the emails from the quarantine. 67 | 68 | “AutoSpamEmailScan.ps1” is using the same manner to call the ESA API to build a blocklist for the endusers. 69 | 70 | 71 | 72 | There are 2 ways to intergrate the Plug-in on Office Outlook. "Cisco Email Reporting Plug-in" and "NotifySecurity" which I forked "https://github.com/banhao/NotifySecurity" 73 | 74 | I prefer to use "Cisco Email Reporting Plug-in" which you can custom the button. 75 | 76 | 77 | 78 | And also can custom the action when end users click the different buttons. 79 | 80 | ``` 81 | 82 | 83 | 84 | false 85 | true 86 | true 87 | 88 | plain 89 | Cisco Email Reporting Plug-in Report ${reportType} 90 | 91 | 1005000 92 | orig_mas.raw 93 | true 94 | true 95 | true 96 | true 97 | true 98 | 99 | 100 |
spam@company.com
101 | 102 | spam 103 | false 104 | true 105 | true 106 | 107 |
108 | 109 |
ham@company.com
110 | 111 | ham 112 | false 113 | false 114 | true 115 | 116 |
117 | 118 |
virus@company.com
119 | 120 | virus 121 | true 122 | false 123 | false 124 | 125 |
126 | 127 |
phish@company.com
128 | 129 | phish 130 | true 131 | false 132 | true 133 | 134 |
135 | 136 |
marketing@company.com
137 | 138 | marketing 139 | false 140 | true 141 | true 142 | 143 |
144 |
145 |
146 |
147 | ``` 148 | 149 | # AutoSpamEmailScan 150 | “AutoSpamEmailScan.ps1” is used to monitor a specific mailbox that enterprise users can forward suspicious spam emails to a specific mailbox. 151 | This PowerShell script can monitor the mailbox for any unread emails, grab the URLs and attachments from the emails and submit to virustotal.com, urlscan.io, Google safe browsing and OPSWAT. Script also can extract URLs from a pdf file. 152 | After the scan finished, script can generate HTML format scan report and auto reply to the senders. 153 | Script can be run once or loop interval, if $INTERVAL in the init.conf is 0 means script will only run one time else the number is the loop interval seconds. 154 | 155 | Before you run the script Install the Exchange Web Services Managed API 2.2. 156 | https://www.microsoft.com/en-us/download/details.aspx?id=42951 157 | 158 | You also can find this script in powershellgallery.com 159 | https://www.powershellgallery.com/packages/AutoSpamEmailScan/ 160 | -------------------------------------------------------------------------------- /RedirectURL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Author: hao.ban@ehealthsask.ca//banhao@gmail.com 5 | # Version: 6 | # Issue Date: June 02, 2022 7 | # Release Note: 8 | 9 | import sys, time, datetime, urllib.request 10 | from selenium import webdriver 11 | from selenium.webdriver.edge.options import Options 12 | #from msedge.selenium_tools import EdgeOptions 13 | #from msedge.selenium_tools import Edge 14 | 15 | url = sys.argv[1] 16 | edge_options = Options() 17 | edge_options.use_chromium = True 18 | edge_options.add_experimental_option('w3c', False) 19 | edge_options.binary_location = r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" 20 | #driver = Edge(executable_path=r'.\msedgedriver.exe', options=edge_options) 21 | driver = webdriver.Edge(options = edge_options) 22 | time.sleep(1) 23 | try: 24 | driver.get(url) 25 | time.sleep(10) 26 | print(driver.current_url) 27 | except Exception: 28 | MSG = sys.exc_info()[1] 29 | if "ERR_NAME_NOT_RESOLVED" in MSG.msg: 30 | print(url, "is not accessible.") 31 | driver.quit() -------------------------------------------------------------------------------- /Submit_FILE_Virustotal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Author: hao.ban@ehealthsask.ca//banhao@gmail.com 5 | # Version: 6 | # Issue Date: July 04, 2022 7 | # Release Note: 8 | 9 | 10 | import requests, sys, time 11 | 12 | for line in open("./init.conf",'r'): 13 | if line.startswith('VIRUSTOTAL_API_KEY'): 14 | VIRUSTOTAL_API_KEY = line.split("=")[1].strip() 15 | file = sys.argv[1] 16 | url = "https://www.virustotal.com/api/v3/files" 17 | files={ "file": open(file,'rb') } 18 | headers = {"x-apikey": VIRUSTOTAL_API_KEY} 19 | response = requests.request("POST", url, headers=headers, files=files) 20 | time.sleep(180) 21 | ID = response.json()['data']['id'] 22 | url = "https://www.virustotal.com/api/v3/analyses/"+ID 23 | headers = { "Accept": "application/json","x-apikey": VIRUSTOTAL_API_KEY } 24 | response = requests.request("GET", url, headers=headers) 25 | url = "https://www.virustotal.com/api/v3/files/"+response.json()['meta']['file_info']['sha256'] 26 | response = requests.request("GET", url, headers=headers) 27 | print("VirusTotal File Scan Report: ") 28 | print("https://www.virustotal.com/gui/file/"+response.json()['data']['id']+"/detection") 29 | print("VirusTotal File Scan Stats: ") 30 | print(response.json()['data']['attributes']['last_analysis_stats']) 31 | 32 | -------------------------------------------------------------------------------- /Work Flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banhao/AutoSpamEmailScan/5f76ef1b2f9521bdbe71b626d8f658958aa1582a/Work Flow.jpg -------------------------------------------------------------------------------- /init.conf: -------------------------------------------------------------------------------- 1 | [MAILBOX] 2 | ADUSERNAME = 3 | ADPASSWORD : 4 | DOMAIN = 5 | EMAILADDRESS = 6 | SMTPSERVER = 7 | EWSDLLPATH = C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll 8 | DOWNLOADDIRECTORY = D:\AutoSpamEmailScan\downloads\ 9 | REPORTSDIRECTORY = D:\AutoSpamEmailScan\reports\ 10 | EXTENSIONARRAY = .jpg,.jpeg,.jpe,.png,.gif,.bmp,.tif,.pngx 11 | EXEMPTURL = microsoft.com,oracle.com,cisco.com,entrust.net,github.com,twitter.com,facebook.com,youtube.com,google.com,google.ca,openxmlformats.org,xmlsoap.org,w3.org 12 | REPLYCC = 13 | REPLYTO = 14 | SUBFOLDER = Processed 15 | INTERVAL = 1800 16 | 17 | [VIRUSTOTAL] 18 | VIRUSTOTAL_API_KEY = 19 | 20 | [URLSCAN] 21 | URLSCAN_API_KEY = 22 | 23 | [GOOGLE] 24 | GOOGLE_API_KEY = 25 | 26 | [OPSWAT] 27 | OPSWAT_API_KEY = 28 | 29 | [MINEMELD] 30 | MineMeldServer = 31 | MineMeldNode = 32 | MINEMELDCREDENTIAL : 33 | 34 | [UMBRELLA] 35 | Umbrella_API_Key = 36 | 37 | [SECUREX] 38 | SECUREX_CLIENT_ID = 39 | SECUREX_CLIENT_PASSWORD = 40 | 41 | [MDATP] 42 | TenantId = 43 | appId = 44 | appSecret = 45 | 46 | [ESA] 47 | ESAUSER = 48 | ESACREDENTIAL : 49 | ESAURL1 = 50 | ESAURL2 = 51 | PRIVATEKEY = 52 | -------------------------------------------------------------------------------- /module.xml: -------------------------------------------------------------------------------- 1 | 2 | RedirectURL.py 3 | ON 4 | without this feature the script cannot check the redirection URL. 5 | 6 | 7 | pdf2url.py 8 | ON 9 | without this feature the script cannot conver a pdf to HTML and cannot extract the URL from a PDF file. 10 | 11 | 12 | selenium_simulator.py 13 | ON 14 | without this feature the script cannot simulate as a browser to open an URL or a HTML file. 15 | 16 | 17 | Submit_FILE_Virustotal.py 18 | ON 19 | without this feature the script cannot submit the files to Virustotal. 20 | 21 | 22 | MineMeld_Indicator.ps1 23 | ON 24 | without this feature the script cannot call the MineMeld API to add the IOCs into the Indicator List. 25 | 26 | 27 | ESA_Spam_Block.ps1 28 | ON 29 | without this feature the script cannot call the Cisco ESA's API to add the spam email address into the spam quarantine blocklist. 30 | 31 | 32 | secureX.ps1 33 | ON 34 | without this feature the script cannot call the Cisco SecureX API and Microsoft 365 Defender API to get the related email information and the end user's activities. 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /pdf2url.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Author: hao.ban@ehealthsask.ca//banhao@gmail.com 5 | # Version: 6 | # Issue Date: May 25, 2022 7 | # Release Note: 8 | 9 | import sys 10 | from PyPDF2 import PdfReader 11 | 12 | pdffile = sys.argv[1] 13 | 14 | reader = PdfReader(pdffile) 15 | for page in reader.pages: 16 | if "/Annots" in page: 17 | for annot in page["/Annots"]: 18 | if annot.get_object()["/Subtype"] == '/Link' : 19 | print(annot.get_object()["/A"]["/URI"]) 20 | -------------------------------------------------------------------------------- /plug-in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banhao/AutoSpamEmailScan/5f76ef1b2f9521bdbe71b626d8f658958aa1582a/plug-in.jpg -------------------------------------------------------------------------------- /procedure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/banhao/AutoSpamEmailScan/5f76ef1b2f9521bdbe71b626d8f658958aa1582a/procedure.jpg -------------------------------------------------------------------------------- /secureX.ps1: -------------------------------------------------------------------------------- 1 | 2 | <#PSScriptInfo 3 | 4 | .VERSION 1.4.2 5 | 6 | .GUID 134de175-8fd8-4938-9812-053ba39eed83 7 | 8 | .AUTHOR HAO BAN/banhao@gmail.com 9 | 10 | .COMPANYNAME 11 | 12 | .COPYRIGHT 13 | 14 | .TAGS 15 | 16 | .LICENSEURI 17 | 18 | .PROJECTURI 19 | 20 | .ICONURI 21 | 22 | .EXTERNALMODULEDEPENDENCIES 23 | 24 | .REQUIREDSCRIPTS 25 | 26 | .EXTERNALSCRIPTDEPENDENCIES 27 | 28 | .RELEASENOTES 29 | Creation Date: <11/08/2022> 30 | Purpose/Change: 31 | 32 | .PRIVATEDATA 33 | 34 | .SYNOPSIS 35 | 36 | .EXAMPLE 37 | 38 | .DESCRIPTION secureX.ps1 39 | 40 | #> 41 | 42 | $SECUREX_CLIENT_ID = Get-Content .\init.conf | findstr SECUREX_CLIENT_ID | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 43 | $SECUREX_CLIENT_PASSWORD = Get-Content .\init.conf | findstr SECUREX_CLIENT_PASSWORD | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 44 | 45 | $TenantId = Get-Content .\init.conf | findstr TenantId | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 46 | $appId = Get-Content .\init.conf | findstr appId | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 47 | $appSecret = Get-Content .\init.conf | findstr appSecret | %{ $_.Split('=')[1]; } | foreach{ $_.ToString().Trim() } 48 | 49 | function Threat_Response_authentication { 50 | $oAuthUri = "https://visibility.amp.cisco.com/iroh/oauth2/token" 51 | 52 | $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]" 53 | $headers.Add("Authorization", "Basic $([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($SECUREX_CLIENT_ID + ":" + $SECUREX_CLIENT_PASSWORD)))") 54 | $headers.Add("Content-Type", "application/x-www-form-urlencoded") 55 | $headers.Add("Accept", "application/json") 56 | 57 | $authBody = @{ 58 | grant_type = 'client_credentials' 59 | } 60 | 61 | $authResponse = Invoke-RestMethod -Method Post -Uri $oAuthUri -Headers $headers -Body $authBody -ErrorAction Stop 62 | $global:Threat_Response_token = $authResponse.access_token 63 | $global:Threat_Response_tokenexpire = $authResponse.expires_in 64 | } 65 | 66 | function MDATP_authentication { 67 | $resourceAppIdUri = 'https://api.securitycenter.microsoft.com' 68 | $oAuthUri = "https://login.microsoftonline.com/$TenantId/oauth2/token" 69 | $authBody = [Ordered] @{ 70 | resource = "$resourceAppIdUri" 71 | client_id = "$appId" 72 | client_secret = "$appSecret" 73 | grant_type = 'client_credentials' 74 | } 75 | $authResponse = Invoke-RestMethod -Method Post -Uri $oAuthUri -Body $authBody -ErrorAction Stop 76 | $global:MDATP_token = $authResponse.access_token 77 | $global:MDATP_tokenexpire = $authResponse.expires_on 78 | } 79 | 80 | function SecureX-Investigation { 81 | Threat_Response_authentication 82 | Write-OutPut "SecureX Investigation: " 83 | $headers = @{ 84 | 'Content-Type' = 'application/json' 85 | Accept = 'application/json' 86 | Authorization = "Bearer $Threat_Response_token" 87 | } 88 | $body = ConvertTo-Json -InputObject @{ 'content' = $CONTENT } 89 | $inspect_response = Invoke-WebRequest -Method Post -Uri "https://visibility.amp.cisco.com/iroh/iroh-inspect/inspect" -Headers $headers -Body $body -ErrorAction Stop 90 | $headers = @{ 91 | 'Content-Type' = 'application/json' 92 | Accept = 'application/json' 93 | Authorization = "Bearer $Threat_Response_token" 94 | } 95 | $body = $inspect_response.Content 96 | $response = Invoke-WebRequest -Method Post -Uri "https://visibility.amp.cisco.com/iroh/iroh-enrich/observe/observables" -Headers $headers -Body $body -ErrorAction Stop 97 | $results = $response.Content | ConvertFrom-Json 98 | for($i=0;$i -le $results.data.length;$i++){ 99 | $module = $results.data[$i].module 100 | if ( $module -eq "Talos Intelligence" ) { 101 | Write-OutPut "*********************************************" 102 | Write-OutPut "* Talos Intelligence Investigation Results: " 103 | Write-OutPut "*********************************************" 104 | foreach ( $talos_results in $results.data[$i].data.verdicts.docs ){ 105 | $ta_result = $talos_results.observable.value+" , "+$talos_results.disposition_name 106 | if ( ($talos_results.disposition_name -eq "Malicious") -or ($talos_results.disposition_name -eq "Suspicious") ) { $enable_alert = $true } 107 | Write-OutPut $ta_result 108 | } 109 | Write-OutPut "" 110 | Write-OutPut "" 111 | } 112 | if ( ($module -eq "Umbrella") -and (![string]::IsNullOrEmpty($results.data[$i].data.sightings)) ) { 113 | $title = "* Umbrella Investigation Results, " + $($results.data[$i].data.sightings.docs[0].description -split 'by', 0)[0] + "by:" 114 | Write-OutPut "*********************************************" 115 | Write-OutPut $title 116 | Write-OutPut "*********************************************" 117 | $_endpoint_list = @() 118 | $endpoint_list = @() 119 | foreach ($umbrella_results in $results.data[$i].data.sightings.docs){ 120 | $_endpoint = $($umbrella_results.description -split 'by', 0)[1] 121 | $_endpoint_list += $_endpoint 122 | } 123 | $_endpoint_list = $_endpoint_list | sort -u 124 | Write-OutPut $_endpoint_list 125 | foreach ( $endpoint in $_endpoint_list ) { 126 | if ( $endpoint -like '*(AD Users)' ) { 127 | $regex = [regex]"\(\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\)" 128 | $fRegion_EmailAddress = $($regex.match($endpoint)).Value.trimstart("(").trimend(")") 129 | $fRegion_DomainName = $($fRegion_EmailAddress -split '@',0)[1] 130 | $GivenName = $($($($endpoint -split "\(" , 0)[0] -split "," , 0)[1] -split ' ', 0)[1] 131 | $Surname = $($($endpoint -split "\(" , 0)[0] -split "," , 0)[0].Trim().trimstart("'") 132 | # Write-OutPut "$($GivenName) $($Surname), $($fRegion_DomainName)" 133 | $ADUser_Properties = Get-ADUser -Filter 'GivenName -eq $GivenName -and Surname -eq $Surname' -Server $(Get-ADDomainController -DomainName $fRegion_DomainName -Discover -NextClosestSite).Name -properties * 134 | $SamAccountName = $ADUser_Properties.SamAccountName.ToLower() 135 | $Recipient = $ADUser_Properties.DisplayName 136 | $url = "https://api.securitycenter.microsoft.com/api/advancedqueries/run" 137 | MDATP_authentication 138 | $headers = @{ 139 | 'Content-Type' = 'application/json' 140 | Accept = 'application/json' 141 | Authorization = "Bearer $MDATP_token" 142 | } 143 | $query = @" 144 | DeviceNetworkEvents 145 | | where RemoteUrl contains "$CONTENT" and InitiatingProcessAccountName == "$SamAccountName" 146 | "@ 147 | $body = ConvertTo-Json -InputObject @{ 'Query' = $query } 148 | $response = Invoke-WebRequest -Method POST -Uri $url -Headers $headers -Body $body -ErrorAction Stop 149 | if ( ![string]::IsNullOrEmpty($(($response | ConvertFrom-Json).Results)) ) { 150 | Write-OutPut "MDATP found the User $($Recipient) tried to access the URL" 151 | $InitiatingProcessFileName = ($response | ConvertFrom-Json).Results.InitiatingProcessVersionInfoFileDescription 152 | $Timestamp = ($response | ConvertFrom-Json).Results.Timestamp 153 | $EmailBody = @" 154 | Hello $Recipient, 155 | Our security system detected you used $InitiatingProcessFileName to access [$CONTENT] at $Timestamp which is a Malicious/Phishing site. Please contact the Service Desk and change you AD account password immediately. 156 | If you have any questions, please contact ehssecurity@ehealthsask.ca 157 | 158 | Thanks, 159 | Enterprise Security Services 160 | eHealth Saskatechewan 161 | "@ 162 | if ( $enable_alert -eq $true) { 163 | if ( ![string]::IsNullOrEmpty($ADUser_Properties.EmailAddress) ) { 164 | Send-MailMessage -SmtpServer relay-partner.ehealthsask.ca -To $ADUser_Properties.EmailAddress -From "EMAILADDRESS" -Cc "EMAILADDRESS" -Subject "Security Alert" -Body $EmailBody 165 | Write-OutPut "Alert Message has been sent to $($Recipient)" 166 | }else{ Write-OutPut "$($Recipient) Email Address is not exist." } 167 | } 168 | } else { Write-OutPut "No related result was found in MDATP for $($Recipient)" } 169 | } 170 | if ( $endpoint -like '*(Anyconnect Roaming Client)' ) { 171 | $endpoint_list += $($endpoint -split ' ', 0)[1].trimstart("'").trimend("'") 172 | $HOSTNAME = $($endpoint -split ' ', 0)[1].trimstart("'").trimend("'") 173 | $url = "https://api.securitycenter.microsoft.com/api/advancedqueries/run" 174 | MDATP_authentication 175 | $headers = @{ 176 | 'Content-Type' = 'application/json' 177 | Accept = 'application/json' 178 | Authorization = "Bearer $MDATP_token" 179 | } 180 | $query = @" 181 | DeviceInfo 182 | | where DeviceName startswith "$HOSTNAME" 183 | | summarize count() by DeviceId 184 | "@ 185 | $body = ConvertTo-Json -InputObject @{ 'Query' = $query } 186 | $response = Invoke-WebRequest -Method POST -Uri $url -Headers $headers -Body $body -ErrorAction Stop 187 | $DeviceId = ($response | ConvertFrom-Json).Results.DeviceId 188 | $query = @" 189 | DeviceNetworkEvents 190 | | where DeviceId == "$DeviceId" and RemoteUrl contains "$CONTENT" 191 | | where Timestamp > ago(30d) 192 | "@ 193 | $body = ConvertTo-Json -InputObject @{ 'Query' = $query } 194 | $response = Invoke-WebRequest -Method POST -Uri $url -Headers $headers -Body $body -ErrorAction Stop 195 | if ( ![string]::IsNullOrEmpty($(($response | ConvertFrom-Json).Results)) ) { 196 | Write-OutPut "MDATP found the Endpoint $($HOSTNAME) tried to access the URL" 197 | $UserPrincipalName = ($response | ConvertFrom-Json).Results.InitiatingProcessAccountUpn 198 | if ( !([string]::IsNullOrEmpty($UserPrincipalName) -or [string]::IsNullOrWhiteSpace($UserPrincipalName)) ) { 199 | $UserPrincipalName = ($response | ConvertFrom-Json).Results.InitiatingProcessAccountName 200 | } 201 | $InitiatingProcessFileName = ($response | ConvertFrom-Json).Results.InitiatingProcessVersionInfoFileDescription 202 | $Timestamp = ($response | ConvertFrom-Json).Results.Timestamp 203 | $computerDnsName = ($response | ConvertFrom-Json).Results.DeviceName 204 | $DomainName = $($UserPrincipalName -split '@',0)[1] 205 | $Identity = $($UserPrincipalName -split '@',0)[0] 206 | $ADUser_Properties = Get-ADUser -Identity $Identity -Server $(Get-ADDomainController -DomainName $DomainName -Discover -NextClosestSite).Name -properties * 207 | $Recipient = $ADUser_Properties.DisplayName 208 | $EmailBody = @" 209 | Hello $Recipient, 210 | Our security system detected you used $InitiatingProcessFileName to access [$CONTENT] at $Timestamp which is a Malicious/Phishing site. Please contact the Service Desk and change you AD account password immediately. 211 | If you have any questions, please contact ehssecurity@ehealthsask.ca 212 | 213 | Thanks, 214 | Enterprise Security Services 215 | eHealth Saskatechewan 216 | "@ 217 | if ( $enable_alert -eq $true) { 218 | if ( ![string]::IsNullOrEmpty($ADUser_Properties.EmailAddress) ) { 219 | Send-MailMessage -SmtpServer relay-partner.ehealthsask.ca -To $ADUser_Properties.EmailAddress -From "EMAILADDRESS" -Cc "EMAILADDRESS" -Subject "Security Alert" -Body $EmailBody 220 | Write-OutPut "Alert Message has been sent to $($HOSTNAME)" 221 | }else{ Write-OutPut "$($Recipient) Email Address is not exist." } 222 | } 223 | } else { Write-OutPut "No related result was found in MDATP for HOST $($HOSTNAME)" } 224 | } 225 | } 226 | Write-OutPut "" 227 | Write-OutPut "" 228 | 229 | } 230 | if ( $module -eq "SMA Email" ) { 231 | Write-OutPut "*********************************************" 232 | Write-OutPut "* SMA Email Investigation Results, Following e-mail address were related to the URLs/Domains:" 233 | Write-OutPut "*********************************************" 234 | $Outgoing_list = @() 235 | $Incoming_list = @() 236 | for($j=0;$j -le $results.data[$i].data.sightings.docs.length;$j++){ 237 | if ($results.data[$i].data.sightings.docs[$j].description -match "Outgoing"){ 238 | $email_mid = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "cisco_mid"})){$key.value} 239 | $email_subject = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "email_subject"})){$key.value} 240 | $email_address = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "email"})){$key.value} 241 | $outgoing_array = $($email_address | Get-Unique), $($($email_mid -split '-')[0] | Get-Unique), $($email_subject | Get-Unique) 242 | $Outgoing_list += ,$outgoing_array 243 | } 244 | if ($results.data[$i].data.sightings.docs[$j].description -match "Incoming"){ 245 | $email_mid = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "cisco_mid"})){$key.value} 246 | $email_subject = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "email_subject"})){$key.value} 247 | $email_address = foreach($key in $($results.data[$i].data.sightings.docs[$j].relations.related | where-Object {$_.type -eq "email"})){$key.value} 248 | $incoming_array = $($email_address | Get-Unique), $($($email_mid -split '-')[0] | Get-Unique), $($email_subject | Get-Unique) 249 | $Incoming_list += ,$incoming_array 250 | } 251 | } 252 | Write-OutPut "Incoming Email List:" 253 | Write-OutPut $Incoming_list | % { $_ -join ','} 254 | Write-OutPut "--------------------------------------------------------------------" 255 | Write-OutPut "Outgoing Email List:" 256 | Write-OutPut $Outgoing_list | % { $_ -join ','} 257 | Write-OutPut "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" 258 | } 259 | } 260 | } 261 | 262 | 263 | $CONTENT = $Args[0] 264 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12 265 | SecureX-Investigation 266 | -------------------------------------------------------------------------------- /selenium_simulator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # Author: hao.ban@ehealthsask.ca//banhao@gmail.com 5 | # Version: 6 | # Issue Date: June 02, 2022 7 | # Release Note: 8 | 9 | import sys, time, datetime 10 | from selenium import webdriver 11 | 12 | url = sys.argv[1] 13 | log_file = sys.argv[2] 14 | #driver = webdriver.Firefox() 15 | #driver = webdriver.Chrome() 16 | driver = webdriver.Edge() 17 | #driver = webdriver.Edge(executable_path=r'.\msedgedriver.exe') 18 | time.sleep(2) 19 | driver.get(url) 20 | time.sleep(10) 21 | screenshot_file = r".\reports\screenshot_" + log_file.split("_")[1].split(".")[0] + ".jpg" 22 | driver.get_screenshot_as_file(screenshot_file) 23 | message = "This HTML file is trying to open as " + str(driver.title) + ". It's highly suspicious Phising or Spam email." 24 | print(message) 25 | driver.quit() 26 | --------------------------------------------------------------------------------