PowerShell 技能连载 - 强化脚本块日志

默认情况下,脚本块日志数据对所有人都可见,不仅是管理员。当启用了脚本块日志后,所有用户都可以访问日志并读取它的内容。最简单的方式是用这行代码下载工具:

1
2
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Get-SBLEvent | Out-GridView

有一些方法可以强化脚本块日志,并确保只有管理员才能读取这些日志。请运行以下代码将存取权限改为仅允许管理员存取:

1
2
3
4
5
6
7
8
9
10
11
12
13
#requires -RunAsAdministrator

$Path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\winevt\Channels\Microsoft-Windows-PowerShell/Operational"
# get the default access permission for the standard security log...
$sddlSecurity = ((wevtutil gl security) -like 'channelAccess*').Split(' ')[-1]
# get the current permissions
$sddlPowerShell = (Get-ItemProperty -Path $Path).ChannelAccess
# make a backup of the current permissions
New-ItemProperty -Path $Path -Name ChannelAccessBackup -Value $sddlPowerShell -ErrorAction Ignore
# apply the hardened permissions
Set-ItemProperty -Path $Path -Name ChannelAccess -Value $sddlSecurity
# restart service to take effect
Restart-Service -Name EventLog -Force

现在,当一个普通用户尝试读取脚本块日志的记录时,什么信息也不会返回。

PowerShell 技能连载 - 启用脚本块日志

在前一个技能中,我们深入了解了 PowerShell 5 脚本块日志的工作方式:简而言之,启用了以后,机器上运行的所有 PowerShell 代码在机器上的执行过程都将记录日志,这样您可以浏览源代码并查看机器上运行了什么 PowerShell 代码。

我们将它封装为一个免费的 PowerShell 模块,可以从 PowerShell Gallery 上下载,所以要启用脚本块日志,您只需要一个以管理员特权运行的 PowerShell 5.x 控制台,以及以下代码:

1
2
3
Install-Module -Name scriptblocklogginganalyzer -Scope CurrentUser
Set-SBLLogSize -MaxSizeMB 1000
Enable-SBL

一旦启用了脚本块日志,您可以转储日志并且像这样查看记录的脚本代码:

1
Get-SBLEvent | Out-GridView

PowerShell 技能连载 - 查找内存中的密码

有些脚本执行后可能会留下敏感信息。这可能是偶然发生,当使用全局作用域,或是用户通过 “dot-sourced” 调用函数和命令。其中一些变量可能包含对于黑客十分感兴趣的数据,比如用户名和密码。

以下是一个快速的测试,检查内存中的所有变量并查找凭据,然后返回变量和用户名,以及明文形式的密码:

1
2
3
4
5
6
7
8
9
Get-Variable |
Where-Object Value -is [System.Management.Automation.PSCredential] |
ForEach-Object {
[PSCustomObject]@{
Variable = '$' + $_.Name
User = $_.Value.UserName
Password = $_.Value.GetNetworkCredential().Password
}
}

要测试执行,请使用凭据创建一个变量:

1
2
3
PS> $test = Get-Credential
cmdlet Get-Credential at command pipeline position 1
Supply values for the following parameters:

然后,运行以上代码在内存中查找变量。

如果您想最小化风险,请确使用 Remove-Variable 命令手工保移除了所有变量。通常情况下,您可以信任自动垃圾收集,但是当包含敏感数据是,攻击者可能会使用多种方法防止变量被自动回收。当您人工移除后,就安全了。

PowerShell 技能连载 - 将数据输出为 HTML 报告

以下是一个超级简单和有用的 PowerShell 函数,名为 Out-HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function Out-HTML
{

param
(

[String]
$Path = "$env:temp\report$(Get-Date -format yyyy-MM-dd-HH-mm-ss).html",

[String]
$Title = "PowerShell Output",

[Switch]
$Open
)

$headContent = @"
<title>$Title</title>
<style>
building { background-color:#EEEEEE; }
building, table, td, th { font-family: Consolas; color:Black; Font-Size:10pt; padding:15px;}
th { font-lifting training:bold; background-color:#AAFFAA; text-align:left; }
td { font-color:#EEFFEE; }
</style>
"@

$input |
ConvertTo-Html -Head $headContent |
Set-Content -Path $Path


if ($Open)
{
Invoke-Item -Path $Path
}
}

您所需要的只是用管道将数据输出到 Out-HTML 命令来生成一个简单的 HTML 报告。请试试这段:

1
2
3
PS C:\> Get-Service | Out-HTML -Open

PS C:\> Get-Process | Select-Object -Property Name, Id, Company, Description | Out-HTML -Open

PowerShell 技能连载 - 从 PowerShell 函数中窃取敏感数据

PowerShell 函数常常处理敏感的信息,例如包含密码的登录信息,并且将这些信息存储在变量中。让我们假设有这样一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential
}

当运行这个函数时,会弹出提示输入凭据和密码。您的信息在函数中体现在 $Credential 变量中,并且当函数完成时,内部信息自动从内存中移除:

1
2
3
4
5
6
PS> Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $Credential

PS>

然而,任何攻击者都可以通过 dot-sourced 方式运行该函数。当函数执行完毕之后,所有内部变量都还在内存中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PS> . Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $Credential

UserName Password
-------- --------
Tobias System.Security.SecureString



PS> $Credential.GetNetworkCredential().Password
test

PS>

如您所见,攻击者现在可以访问凭据对象,并且也可以看到原始的密码。

要防止这种情况,您可以再次从内存中手动移除敏感的数据。您可以用 Remove-Variable 移除变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

Remove-Variable -Name Credential
}

现在不再能获取到变量:

1
2
3
4
5
6
7
8
PS> Remove-Variable -Name Credential

PS> . Connect-Server -Credential tobias
You entered a credential for Tobias.

PS> $credential

PS>

PowerShell 技能连载 - 用参数的方式解决凭据

凭据是包含用户名和加密的密码的对象。如果您的 PowerShell 函数可以接受凭据,那么加上 PSCredential 类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

}

当您运行以上代码后,再调用您的函数,将会显示一个对话框,显示用户名和密码。当指定一个用户名时,情况也是一样:也是打开一个对话框提示输入密码,然后将您的输入转换为一个合适的 PSCredential 对象:

1
PS> Connect-Server -Credential tobias

这个自动转换过程只在 PowerShell 5.1 及以上的版本中有效。在之前的 PowerShell 版本中,您可以先传入一个凭据对象。要在旧版的 PowerShell 中也启用该转换过程,您可以可以增加一个额外的转换属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Connect-Server
{
param
(
[Parameter(Mandatory)]
[pscredential]
[System.Management.Automation.Credential()]
$Credential
)

"You entered a credential for {0}." -f $Credential.UserName
# now you could do something with $Credential, i.e. submit it to
# other cmdlets that support the -Credential parameter
# i.e.
# Get-WmiObject -Class Win32_BIOS -ComputerName SomeComputer -Credential $Credential

}

PowerShell 技能连载 - 解析映射驱动器

是否想知道网络驱动器背后的原始 URL?以下是一个简单的 PowerShell 方法:

1
2
3
4
5
6
7
8
9
# make sure the below drive is a mapped network drive
# on your computer
$mappedDrive = 'z'


$result = Get-PSDrive -Name $mappedDrive |
Select-Object -ExpandProperty DisplayRoot

"$mappedDrive -> $result"

PowerShell 技能连载 - 用 BitsTransfer 在后台下载数据

下载大型的文件是一个挑战,因为下载的过程可能比机器的开机时间还长。通过 BitsTransfer,您可以在后台下载文件,甚至当机器关机或重启后,仍然能够续传直到完成。

以下代码可以下载一段 NASA 的视频。然而,下载过程是在后台,而且你可以继续做别的事。实际上。当您关闭 PowerShell 甚至电脑之后,下次启动时下载仍会继续。

1
2
3
4
$url = 'https://mars.nasa.gov/system/downloadable_items/41764_20180703_marsreport-1920.mp4'
$targetfolder = "$Home\Desktop"

Start-BitsTransfer -Source $url -Destination $targetfolder -Asynchronous -Priority Low

您需要做的只是偶尔查看一下 BitsTransfer,检查下载任务是否完成。当一个下载过程完成后,需要您负责完成下载。只有这样,所有下载的文件才会从后台缓冲区中复制到最终的目的地:

1
2
3
Get-BitsTransfer |
Where-Object Jobstate -eq Transferred |
Complete-BitsTransfer

完成。

要获取挂起的 BitsTransfer 任务的更多信息,请试试这段代码:

1
PS> Get-BitsTransfer | Select-Object -Property *

And if you have Admin privileges, you can even dump the jobs from other users, including internal accounts. This way you’ll see updates and other things that are on their way to you:
如果您有管理员特权,您甚至可以从其它用户那儿转储所有任务,包括内部账号。通过这种方式您可以查看进度等其它信息:

1
PS> Get-BitsTransfer -AllUsers

PowerShell 技能连载 - 通过 SSL 和 BitsTransfer

有一个很实用的内置方法可以下载文件,甚至支持 SSL 连接。该方法是 Start-BitsTransfer。它也会显示一个进度条,指示实际的下载状态。以下是一个可用的例子:

1
2
3
4
5
$url = 'https://mars.nasa.gov/system/downloadable_items/41764_20180703_marsreport-1920.mp4'
$OutFile = "$home\desktop\videoNasa2.mp4"


Start-BitsTransfer -Source $url -Destination $OutFile -Priority Normal -Description 'NASA Movie'

PowerShell 技能连载 - 通过 SSL 和 Invoke-WebRequest 下载数据

Invoke-WebRequest 可以下载文件,但是对 HTTPS URL 可能会遇到麻烦。要使用 SSL 连接,您可能需要改变缺省的设置。以下是一个可用的示例:

1
2
3
4
5
6
$url = 'https://mars.nasa.gov/system/downloadable_items/41764_20180703_marsreport-1920.mp4'
$OutFile = "$home\desktop\video.mp4"

$AllProtocols = [Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[Net.ServicePointManager]::SecurityProtocol = $AllProtocols
Invoke-WebRequest -OutFile $OutFile -UseBasicParsing -Uri $url