Powershell 高级使用技巧
Powershell 几乎可以用于安全相关流程的各个方面,本文介绍powershell命令或脚本的汇总,最大程度提升安全运维的效率。
使用 PowerShell 创建安全工具
安全字符串
有时候我们可能希望将密码保存到文件中,后面从该文件中读取。但是,如果以纯文本形式存储文件,任何人都可以读取它。因此我们可以使用 PowerShell 的 `ConvertFrom-SecureString` 和 `ConvertTo-SecureString` cmdlet 将 SecureString 保存到文件中。
用 PowerShell 代码创建一个 SecureString,对其进行加密并将其保存到文件中:
$securePassword = Read-Host -Prompt "Enter your password" -AsSecureString $securePassword | ConvertFrom-SecureString | Set-Content mysecurestring.txt
当我们运行此脚本并被要求输入密码时, 这里输出p@ssw0rd!! 让我们输入它的值并查看“mysecurestring.txt”文件的内容:
现在,无需在任何文件、脚本或命令行中使用明文密码,我们可以在必要时通过引用加密文件来使用密码。
# Read SecureString from Saved file $securePassword = Get-Content mysecurestring.txt | ConvertTo-SecureString # Convert SecureString to PSCredential $credential = New-Object System.Management.Automation.PSCredential ("username", $securePassword) # Use Credential for web request $webRequest = Invoke-WebRequest -Uri $url -Credential $credential
在此示例中,我们使用Get-Content cmdlet 读取文件,并使用ConvertTo-SecureString回收加密字符串 。然后,我们将此SecureString转换为 PSCredential 对象并将该对象作为身份验证信息发送到 Web 服务。
追踪重要的Windows事件ID
- 事件 ID 4625 当帐户登录尝试失败时会触发此事件。这可能表明攻击者正在试图接管该帐户。
- 事件 ID 4648 表示用户已为另一个帐户执行了显式身份验证(特别是针对服务或任务)。这可能是正常的,但也可能表明攻击者已接管帐户并试图使用它来获得更多访问权限。
- 事件 ID 4703 表示任务的安全设置已更改。这可能表明攻击者正在试图利用某些漏洞。
- 事件 ID 1102 表示日志已被删除。这可能表明攻击者正在试图掩盖其踪迹。
- 事件 ID 7045 表示已安装新服务。这可能表明攻击者正试图向系统添加恶意服务。
我们可以编写一个脚本,每小时扫描事件日志中的这些事件,并在触发任何事件时通过电子邮件通知我们:
# SMTP Server Settings $smtpServer = "smtp.zgao.top" $smtpUser = "zgao" $smtpPassword = "**********" # Sender and Recipient Mail Addresses $from = "alerter@zgao.top" $to = "admin@zgao.top" # Determine Start Time $lastHour = (Get-Date).AddHours(-1) #EventIDList $eventIDs = @(4625, 4648, 4703, 1102, 7045) #ControlEvents foreach ($id in $eventIDs) { $events = Get-WinEvent -FilterHashtable @{Logname='Security'; ID=$id; StartTime=$lastHour} if ($events) { $subject = "Alert: Detected Event ID $id" $body = "Detected event ID $id on the following system: $($events[0].MachineName) at $($events[0].TimeCreated)" # Create Credential Object for SMTP $smtpCred = New-Object System.Management.Automation.PSCredential -ArgumentList $smtpUser, $($smtpPassword | ConvertTo-SecureString -AsPlainText -Force) # Send Email Notification Send-MailMessage -SmtpServer $smtpServer -From $from -To $to -Subject $subject -Body $body -Credential $smtpCred -UseSsl } }
此脚本假定 SMTP 服务器支持 SSL,并需要用户名和密码进行身份验证。这些值应根据自己的 SMTP 服务器的要求进行设置。我们只需要保存为ps1脚本后将其添加为每小时运行一次的计划任务,它将检查运行时间前 60 分钟发生的事件,如果发生了我们在 $eventIDs 字符串中指定的事件之一,它将向我们发送电子邮件。
我们创建的这个脚本通知中的误报率可能很高。例如,即使用户意外输入了一次错误密码,也会触发 4625(登录失败)事件。那么,如果我们为每个事件 ID 设置一个阈值,超过才触发告警,应该如何实现呢?
# 只查找过去1小时的 $lastHour = (Get-Date).AddHours(-1) # EventID and Threshold List $eventLimits = @{4625=10; 4648=5; 4703=3; 1102=1; 7045=2} #ControlEvents foreach ($entry in $eventLimits.GetEnumerator()) { $id = $entry.Key $limit = $entry.Value $events = Get-WinEvent -FilterHashtable @{Logname='Security'; ID=$id; StartTime=$lastHour} -ErrorAction SilentlyContinue if ($events -and $events.Count -gt $limit) { $subject = "Alert: Detected Event ID $id" $body = "Detected $($events.Count) instances of event ID $id on the following system: $($events[0].MachineName) at $($events[0].TimeCreated)" Write-Output $body } else { Write-Output "No events found for ID $id in the last hour." } }
因为我们实际没有配置发送邮箱,所以这里改为直接输出到控制台。
将威胁情报与 PowerShell 结合使用
威胁情报源是提供诸如哈希、域、IP 地址等项目的情报信息的来源。例如,通过查询我们系统上运行的应用程序的哈希,我们可以质疑此应用程序是否是恶意应用程序,或者连接到我们系统的 IP 地址是否有恶意活动的记录。
IP 信誉检查
我们列出最近一小时内成功登录我们系统的用户的用户名和 IP 地址,并检查这些 IP 地址中是否有具有恶意记录的 IP 地址。
# Event ID 4624 indicates a successful user login attempt. $events = Get-WinEvent -FilterHashtable @{Logname='Security';ID=4624;StartTime=(Get-Date).AddHours(-1)} # We loop through the events we obtain foreach ($event in $events) { # We get the XML version of the event $eventXML = [xml]$event.ToXml() # We get relevant information from XML. $ipAddress = $eventXML.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'} | Select-Object -ExpandProperty '#text' $userName = $eventXML.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'} | Select-Object -ExpandProperty '#text' # We print the username and IP address on the screen Write-Output "User $userName has logged in from IP address $ipAddress" }
function IpCheck($ip){ $body = @{'fields' = 'status,proxy'} $ipInfo = Invoke-RestMethod -Uri "http://ip-api.com/json/$ip/" -Body $body if ($ipInfo.proxy -eq 1) { Write-Output "IP address $ipAddress has been marked as malicious!" } else { Write-Output "IP address $ipAddress has been marked as clean!" } } $events = Get-WinEvent -FilterHashtable @{Logname='Security';ID=4624;StartTime=(Get-Date).AddHours(-1)} foreach ($event in $events) { $eventXML = [xml]$event.ToXml() $ipAddress = $eventXML.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'} | Select-Object -ExpandProperty '#text' $userName = $eventXML.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetUserName'} | Select-Object -ExpandProperty '#text' try { $ipBytes = [System.Net.IPAddress]::Parse($ipAddress).GetAddressBytes() if ($ipBytes[0] -eq 10) { # This is Local Block, Do Nothing continue } elseif ($ipBytes[0] -eq 172 -and $ipBytes[1] -ge 16 -and $ipBytes[1] -le 31) { # This is Local Block, Do Nothing continue } elseif ($ipBytes[0] -eq 192 -and $ipBytes[1] -eq 168) { # This is Local Block, Do Nothing continue } else { # This is Real IP Write-Output "User $userName has logged in from external IP address $ipAddress" ipCheck ($ipAddress) } } catch { # This is Local Login Success continue } }
可以加上在线接口检测ip的信誉度。
哈希信誉检查
我们可以使用不同的算法来计算文件系统中所有文件的哈希值。此哈希信息就像文件当前状态的快照,即使文件中的单个位发生变化,哈希值也会发生变化。
Get-FileHash -Path C:\Windows\System32\winlogon.exe -Algorithm SHA256
例如,当我们使用 Get-FileHash SHA256参数计算系统上运行的“winlogon.exe”文件的哈希值时,我们会得到以下输出:
那么我们如何利用 Powershell 扫描 TI 源?下面的脚本会提取正在运行的进程的可执行文件(.exe 等)的哈希值。我们使用这些哈希值检查恶意软件是否在我们的系统上运行:
# Get running process list $processes = Get-Process # Start Foreach loop for process list $results = foreach ($process in $processes) { # Find current process path $filePath = $process.MainModule.FileName if ($filePath) { # Calculate Hash Value with SHA256 Algorithm $hash = Get-FileHash -Path $filePath -Algorithm SHA256 # Add FileName,Path and Hash values into PSObject [PSCustomObject]@{ 'Process Name' = $process.Name 'File Path' = $filePath 'SHA256 Hash' = $hash.Hash } } } # Groups array by SHA256 hash value and returns only unique values $results | Group-Object -Property 'SHA256 Hash' | ForEach-Object { $_.Group | Select-Object -First 1 }
在上面的脚本中,我们首先获取系统中正在运行的进程列表。然后,我们在此列表中找到每个进程的路径,并通过将此路径提供给 Get-FileHash cmdlet 来计算其哈希值。我们将找到的值保存到 PSOBject 中,而不是直接列出它们。这样做的原因是为了可以运行同一可执行文件的多个副本。由于这会导致不必要的拥挤和混乱,我们首先将获得的值累积到 PSObject 中,最后使用 Group-Object cmdlet,通过仅取 1 个具有相同哈希值的值来使我们的输出独一无二。
让我们进一步开发我们的脚本并查询我们在 Virus Total (需要api key)上获得的哈希值进行研判。
# VirusTotal API key values $virusTotalApiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # Get running process list $processes = Get-Process # Start Foreach loop for process list $results = foreach ($process in $processes) { # Find current process path $filePath = $process.MainModule.FileName if ($filePath) { # Calculate Hash Value with SHA256 Algorithm $hash = Get-FileHash -Path $filePath -Algorithm SHA256 # Add FileName,Path and Hash values into PSObject [PSCustomObject]@{ 'Process Name' = $process.Name 'File Path' = $filePath 'SHA256 Hash' = $hash.Hash } } } # Groups array by SHA256 hash value and returns only unique values $uniqueResults = $results | Group-Object -Property 'SHA256 Hash' | ForEach-Object { $_.Group | Select-Object -First 1 } foreach ($result in $uniqueResults) { $hash = $result.'SHA256 Hash' # VirusTotal Hash Control try{ $vtResponse = Invoke-RestMethod -Method 'Get' -Uri "https://www.virustotal.com/api/v3/files/$hash" -Headers @{"x-apikey" = $virusTotalApiKey} #Write-Output $vtResponse if ($vtResponse.data.attributes.last_analysis_stats.malicious -gt 0) { # If mailicus found Write-Output "Suspicius Process Found! Process Name: $($result.'Process Name'), Executable Path: $($result.'File Path'), SHA256 Hash: $hash" }else{ Write-Output "Process Name: $($result.'Process Name'), Executable Path: $($result.'File Path') is clean!" } }catch{ Write-Output "VT API Error! Process Name: $($result.'Process Name'), Executable Path: $($result.'File Path'), SHA256 Hash: $hash" } }
我们筛选输出中可疑的sha1进一步研判。由于VT的api有请求频率限制,我们可以收集更多的api作为池子进行批量请求从而减少接口报错。
我们可以在需要的时候运行这个脚本,也可以在特定的时间段以计划任务的形式运行它。
检查更新
使用 PowerShell,我们可以检查系统上是否需要应用任何更新并报告结果。
$updateSession = New-Object -ComObject Microsoft.Update.Session $updateSearcher = $updateSession.CreateUpdateSearcher() $updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software'") if ($searchResult.Updates.Count -eq 0) { Write-Output "No updates to install." } else { Write-Output "$($searchResult.Updates.Count) updates to install." $searchResult.Updates | ForEach-Object { Write-Output ("Title: " + $_.Title) Write-Output ("Description: " + $_.Description) Write-Output ("IsDownloaded: " + $_.IsDownloaded) Write-Output ("------------------") } }
创建多平台脚本
PowerShell 是 Microsoft 设计和开发的命令行 shell,支持 Windows、MacOS 和 Linux 操作系统。这意味着可以编写单个 PowerShell 脚本并在不同平台上运行。但是,在创建跨平台脚本时需要考虑一些重要因素。
检查 PowerShell 版本
PowerShell 的版本会影响可用的功能和命令。虽然 PowerShell 5.1 是作为 Windows 的一部分发布的,但 PowerShell 7(以前称为 PowerShell Core)是 Microsoft 的开源版本,可在多种平台上运行。执行某些任务的命令在不同的 Powershell 版本中可能以不同的名称命名。某些命令可能并非在每个 Powershell 版本中都可用。另一个与版本相关的问题是我们在命令中使用的参数,某些参数可能在不同的 Powershell 版本中不被接受。
这可能需要确保使用特定的 PowerShell 版本,例如:
# Check Powershell Version if ($PSVersionTable.PSVersion.Major -lt 7) { Write-Output "This script requires PowerShell 7 or Greater" }
检查特定于平台的命令
某些 PowerShell 命令或功能仅适用于特定操作系统。在这种情况下,检查操作系统并相应地创建脚本非常重要。
#Check OS if ($IsWindows) { # Windows specific code } elseif ($IsLinux) { # Linux specific code } elseif ($IsMacOS) { # macOS specific code }
文件路径和分隔符
不同的操作系统在文件路径分隔符和文件结构方面可能有所不同。PowerShell 在这方面提供了一些跨平台兼容性功能。例如, Join-Path cmdlet 允许您在不同平台上使用正确的文件路径分隔符。
$filePath = Join-Path -Path $env:USERPROFILE -ChildPath 'Documents\file.txt'
Join-Path cmdlet在创建跨平台脚本时非常有用,因为它可以理解操作系统的文件路径格式。这意味着它可以避免跨平台不兼容问题,尤其是手动合并文件路径时可能遇到的问题。
例如,在 Linux 中,文件路径分隔符为“ / ”,而在 Windows 中则为“ \ ”。Join -Path cmdlet 根据其运行的操作系统使用正确的分隔符。
假设需要生成某个文件的完整路径。我们知道这个文件位于“C:”盘的“users”文件夹中的“Admin”子文件夹中,文件名为“document.txt”。
$drive = "C:" $folder = "Users\Admin" $file = "document.txt" $path = Join-Path -Path (Join-Path -Path $drive -ChildPath $folder) -ChildPath $file Write-Output $path
该脚本将“$path”变量设置为“C:\Users\Admin\document.txt”。Join-Path cmdlet通常与其他操作文件路径的 PowerShell cmdlet 结合使用,并用于创建复杂的文件路径层次结构。这些 cmdlet 包括 Get-Content 、 Set-Content` 、 Copy-Item 、 Remove-Item 等等。当您使用 `Join-Path` 使用这些 cmdlet 时,以更简洁且更不容易出错的方式创建文件路径。
赞赏微信赞赏支付宝赞赏
发表评论