Powershell 高级使用技巧

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 时,以更简洁且更不容易出错的方式创建文件路径。

赞赏

微信赞赏支付宝赞赏

Zgao

愿有一日,安全圈的师傅们都能用上Zgao写的工具。

发表评论