PowerShell 技能连载 - 检查坏(不安全的)密码(第 2 部分)

在前一个技能中我们解释了如何用 Web Service 来安全地检测密码并且查明它们是否已被泄漏。

信息安全有关的代码有时经过压缩后看起来是否“有趣”,所以在第一步分钟我们分享了优美的而且可读的代码。而以下是考虑“信息安全”的变体,它展示 PowerShell 代码可以被压缩到什么程度并且可以自动混淆。这段代码返回一个指定的密码被暴露了多少次(如果未曾发现被暴露过,返回 null)。

1
$p = 'P@ssw0rd'[Net.ServicePointManager]::SecurityProtocol = 'Tls12'$a,$b = (Get-FileHash -A 'SHA1' -I ([IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($p)))).Hash -split '(?<=^.{5})'(((irm "https://api.pwnedpasswords.com/range/$a" -UseB) -split '\r\n' -like "$b*") -split ':')[-1

PowerShell 技能连载 - 检查坏(不安全的)密码(第 1 部分)

复杂的密码不一定安全。例如,”P@ssw0rd” 是一个非常复杂的密码,但是非常不安全。这是为什么安全社区开始建议用更相关的测试取代复杂性标准,并防止使用以前黑客入侵中使用过的密码。这些密码——虽然它们可能很复杂——是字典攻击的一个常规部分并且非常不安全。

如何知道某个密码是否已被泄露?您可以使用类似 haveibeenpwnd.com 的网站或者它们的 API。这是它的工作原理:

  1. 您根据密码创建哈希值,这样就不会泄漏您的密码。
  2. 将哈希的头五个字节发送到 API,这样就不会泄漏哈希值。
  3. 您可以获得所有以这五个字节开头的哈希值。
  4. 检查返回的哈希中是否有您密码的哈希值。

以下是用 PowerShell 检查密码的方法:

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
# enable all SSL protocols
[Net.ServicePointManager]::SecurityProtocol = 'Ssl3,Tls, Tls11, Tls12'

# get password hash
$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($Password))
$hash = Get-FileHash -InputStream $stream -Algorithm SHA1
$stream.Close()
$stream.Dispose()

# find first five and subsequent hash characters
$prefix, $suffix = $hash.Hash -split '(?<=^.{5})'

# ask for matching passwords with the same first 5 hash digits
$url = "https://api.pwnedpasswords.com/range/$prefix"
$response = Invoke-RestMethod -Uri $url -UseBasicParsing

# find the exact match
$lines = $response -split '\r\n'
$seen = foreach ($line in $lines)
{
if ($line.StartsWith($suffix)) {
[int]($line -split ':')[-1]
break
}
}

"$Password has been seen {0:n0} times." -f $seen

试着改变 $Password 中的密码来测试这段代码。您会很惊讶地发现许多密码已经泄漏:

Sunshine has been seen 13.524 times.

PowerShell 技能连载 - 用聪明的方法指定位标志

在前一个技能中我们学习了如何在 PowerShell 中启用所有的 SSL 安全协议来连接到 Web Service 和 网站:

1
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

有趣的是,可以用一行更短的代码来代替:

1
[Net.ServicePointManager]::SecurityProtocol = 'Ssl3, Tls, Tls11, Tls12'

以下是它的原因:

由于 SecurityProtocolNet.SecurityProtocolType 类型的,所以当您传入字符串数据,它可以自动转换:

1
2
PS> [Net.ServicePointManager]::SecurityProtocol.GetType().FullName
System.Net.SecurityProtocolType

与其用 SecurityProtocolType 枚举并且用 -bor 操作符来连接,您还可以用比特标志位的名称组成的逗号分隔的字符串。两者是相同的:

1
2
3
4
5
6
7
$a = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
$b = [Net.SecurityProtocolType]'Ssl3, Tls, Tls11, Tls12'



PS> $a -eq $b
True

PowerShell 技能连载 - 在 PowerShell 中使用 SSL/HTTPS

根据您的 PowerShell、.NET Framework 的版本和升级,WEB 连接的缺省安全协议可能仍然是 SSL3。您可以方便地查明它:

1
[Net.ServicePointManager]::SecurityProtocol

如果返回的协议不包含 Tls12,那么您可能无法用 PowerShell 连接到安全的 Web Service 和网站。我们只需要这样操作就可以启用更多的服务:

1
2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
[Net.ServicePointManager]::SecurityProtocol

PowerShell 技能连载 - 以固定宽度分割文本

假设您需要以固定宽度分割一段文本。例如,如果您需要一段文本的前 5 个字符,以及剩余的部分,如何实现它?

大多数 PowerShell 用户可能会用类似这样的方法:

1
$text = 'ID12:Here is the text'$prefix = $text.Substring(0,5)$suffix = $text.Substring(5)$prefix$suffix

当然,如果用分割字符,例如 “:”,可以这样操作:

1
$prefix, $suffix = 'ID12:Here is the text' -split ':'$prefix$suffix

然而,这将会吃掉分割字符,并且它会导致超过两个部分。这不是我们要的目标:用固定宽度分割一段文本。而您仍然可以使用 -split 操作符:

1
$prefix, $suffix = 'ID12:Here is the text' -split '(?<=^.{5})'$prefix$suffix

正则表达式结构 “(?<=XXX)“ 称为“向后引用”。”^“ 代表文本的开始,而 “.“ 代表任何字符。如您猜测的那样,”{5}“ 限定该占位符出现的次数,所以基本上这个正则表达式从剩下的文本中分割出前 5 个字符并且返回两部分(假设文本至少 6 个以上字符长度)。

PowerShell 技能连载 - 获取文本的哈希值

在 PowerShell 5(以及 Get-FileHash 之前),要计算字符串和文件的哈希值,您需要借助原生的 .NET 方法。以下是一段为一个字符串创建 MD5 哈希的示例代码:

1
2
3
4
5
6
7
8
9
10
11
$Text = 'this is the text that you want to convert into a hash'

$Provider = New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider
$Encodiner = New-Object -TypeName Text.UTF8Encoding

$Bytes = $Encodiner.GetBytes($Text)
$hashBytes = $Provider.ComputeHash($Bytes)
$hash = [System.BitConverter]::ToString($hashBytes)

# remove dashes if needed
$hash -replace '-'

如果您需要计算一个文件内容的哈希值,要么使用 Get-Content 来读取文件,或者使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
$Path = "C:\somefile.txt"

# use your current PowerShell host as a sample
$Path = Get-Process -Id $Pid | Select-Object -ExpandProperty Path

$Provider = New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider
$FileContent = [System.IO.File]::ReadAllBytes($Path)
$hashBytes = $Provider.ComputeHash($FileContent)
$hash = [System.BitConverter]::ToString($HashBytes)

$hash -replace '-'

PowerShell 技能连载 - 从文本创建哈希

哈希是一种唯一确定一段文本而不用暴露原始文本的棒法。哈希被用来确定文本、查找重复的文件内容,以及验证密码。PowerShell 5 以及更高版本甚至提供了一个 cmdlet 来计算文件的哈希值:Get-FileHash

然而,Get-FileHash 不能计算字符串的哈希。没有必要只是为了计算哈希值而将字符串保存到文件。您可以使用所谓的内存流来代替。以下是一段从任何字符串计算哈希值的代码片段:

1
2
3
4
5
6
7
8
$Text = 'this is the text that you want to convert into a hash'

$stream = [IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($Text))
$hash = Get-FileHash -InputStream $stream -Algorithm SHA1
$stream.Close()
$stream.Dispose()

$hash

使用完成后别忘了关闭并释放内存流,防止内存泄漏并释放所有资源。

PowerShell 技能连载 - 美化 Out-GridView 对话框

当您用管道将对象输出到 Out-GridView,该 cmdlet 显示缺省的属性,所以当您用一个网格视图窗口当作选择框时,您可以控制用户可见的内容。以下代码将读取前 10 个 AD 用户输出到网格视图窗口,并且用户可以选择要返回的项。然而,网格视图窗口中显示的数据看起来很丑:

1
2
3
Get-ADUser -ResultSetSize 10 -Filter * |
Out-GridView -Title 'Select-User' -OutputMode Single |
Select-Object -Property *

如果您没有使用 AD 或没有安装 RSAT 工具,以下是使用进程的类似的例子:

1
2
3
4
Get-Process |
Where-Object MainWindowTitle |
Out-GridView -Title 'Which process do you want to kill?' -OutputMode Single |
Stop-Process -WhatIf

如果您使用 Select-Object 来限制显示的属性,这将改变对象的类型,所以当您继续用管道将改变过的对象传给下一级 cmdlet,它们将无法处理返回的对象。

解决方法是保持对象类型不变,而是改变缺省属性。以下是 AD 用户对象的解决方案,在选择对话框中只显示 Name 和 SID:

1
2
3
4
5
6
7
8
9
[string[]]$visible = 'Name', 'SID'
$type = 'DefaultDisplayPropertySet'
[Management.Automation.PSMemberInfo[]]$i =
New-Object System.Management.Automation.PSPropertySet($type,$visible)

Get-ADUser -LDAPFilter '(samaccountname=schul*)' |
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $i -Force -PassThru |
Out-GridView -Title 'Select-User' -OutputMode Single |
Select-Object -Property *

这是进程选择框的解决方案,显示进程的名称、公司、起始时间,和窗体标题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[string[]]$visible = 'Name', 'Company','StartTime','MainWindowTitle'
$type = 'DefaultDisplayPropertySet'
[Management.Automation.PSMemberInfo[]]$i =
New-Object System.Management.Automation.PSPropertySet($type,$visible)



Get-Process |
Where-Object MainWindowTitle |
Sort-Object -Property Name |
# important: object clone required
Select-Object -Property * |
Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $i -Force -PassThru |
Out-GridView -Title 'Which process do you want to kill?' -OutputMode Single |
Stop-Process -WhatIf

结果发现,进程对象不接受新的 DefaultDisplayPropertySet,所以在这个例子中需要一个完整的克隆,这样您可以用 Select-Object -Property * 将对象输出到管道。由于这不会改变对象类型,所以所有原始属性都被保留下来,下游管道命令能继续起作用,因为管道绑定仍然有效。

PowerShell 技能连载 - 将 PowerShell 输出重定向到 GridView

当在 PowerShell 中输出数据时,它会静默地通过管道输出到 Out-Default 并且最终以文本的方式输出到控制台。如果我们覆盖 Out-Default,就可以改变它的行为,例如将所有 PowerShell 的输出改到一个网格视图窗口。实际中,您甚至可以区别对待正常的输出和错误信息,并且将两者显示在不同的窗口里。

以下是两个函数:Enable-GridOutputDisable-GridOutput。当您运行 Enable-GridOutput 时,它会覆盖 Out-Default 并将常规的输出显示在 “Output” 网格视图窗口,并且将错误信息转换为有用的文本,并将它输出到一个独立的 “Error” 网格视图窗口。

当运行 Disable-GridOutput 后,会去掉覆盖的效果,并且回到缺省的行为:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function Enable-GridOutput
{
function global:Out-Default
{
param
(
[Parameter(ValueFromPipeline=$true)][Object]
$InputObject
)

begin
{
$cmd = $ExecutionContext.InvokeCommand.
GetCommand('Microsoft.PowerShell.Utility\Out-GridView',
[Management.Automation.CommandTypes]::Cmdlet)

$p1 = {& $cmd -Title 'Output' }.
GetSteppablePipeline($myInvocation.CommandOrigin)
$p2 = {& $cmd -Title 'Error' }.
GetSteppablePipeline($myInvocation.CommandOrigin)

$p1.Begin($PSCmdlet)
$p2.Begin($PSCmdlet)
}

process
{
if ($_ -is [Management.Automation.ErrorRecord])
{
$info = $_ | ForEach-Object { [PSCustomObject]@{
Exception = $_.Exception.Message
Reason = $_.CategoryInfo.Reason
Target = $_.CategoryInfo.TargetName
Script = $_.InvocationInfo.ScriptName
Line = $_.InvocationInfo.ScriptLineNumber
Column = $_.InvocationInfo.OffsetInLine
}
}
$p2.Process($info)
}
else
{
$p1.Process($_)
}
}

end
{
$p1.End()
$p2.End()
}
}
}

function Disable-GridOutput
{
Remove-Item -Path function:Out-Default -ErrorAction SilentlyContinue
}

PowerShell 技能连载 - 对比 AD 用户

您是否曾希望对比 ADUser 的属性?假设您安装了 RSAT 工具,您可以用 Get-ADUser 读取每个 AD 用户,但是对比它们的属性不那么容易。

除非使用以下函数:它基本上是将 AD 用户属性分割成独立的对象,这样便可以使用 Compare-Object

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
37
38
39
40
41
42
#requires -Version 3.0 -Modules ActiveDirectory

function Compare-User
{
param
(
[Parameter(Mandatory)][String]
$User1,

[Parameter(Mandatory)][String]
$User2,

[String[]]
$Filter =$null
)


function ConvertTo-Object
{

process
{
$user = $_
$user.PropertyNames | ForEach-Object {
[PSCustomObject]@{
Name = $_
Value = $user.$_
Identity = $user.SamAccountName
}
}
}
}

$l1 = Get-ADUser -Identity $User1 -Properties * | ConvertTo-Object
$l2 = Get-ADUser -Identity $User2 -Properties * | ConvertTo-Object

Compare-Object -Ref $l1 -Dif $l2 -Property Name, Value |
Sort-Object -Property Name |
Where-Object {
$Filter -eq $null -or $_.Name -in $Filter
}
}

以下是输出可能看起来的样子:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
PS C:\> Compare-User -User1 student1 -User2 administrator

Name Value
---- -----
accountExpires 0
accountExpires 9223372036854775807
badPasswordTime 131977150131836679
badPasswordTime 131986685447368488
CanonicalName CCIE.LAN/Users/Administrator
CanonicalName CCIE.LAN/Users/student1
CN Administrator
CN student1
Created 08.03.2019 10:31:50
Created 02.04.2019 09:13:17
createTimeStamp 08.03.2019 10:31:50
createTimeStamp 02.04.2019 09:13:17
Description Built-in account for administering the computer/domain
Description
DistinguishedName CN=student1,CN=Users,DC=CCIE,DC=LAN
DistinguishedName CN=Administrator,CN=Users,DC=CCIE,DC=LAN
dSCorePropagationData ...2019 10:47:56, 08.03.2019 10:32:47, 01.01.1601 19:12:16}
dSCorePropagationData {02.04.2019 09:15:28, 01.01.1601 01:00:00}
isCriticalSystemObject True
LastBadPasswordAttempt 22.03.2019 08:56:53
LastBadPasswordAttempt 02.04.2019 10:49:04
lastLogon 131986622819726136
lastLogon 131986685566131171
LastLogonDate 02.04.2019 10:34:39
LastLogonDate 02.04.2019 09:04:41
lastLogonTimestamp 131986622819726136
lastLogonTimestamp 131986676794218709
logonCount 177
logonCount 4
logonHours {255, 255, 255, 255...}
MemberOf ...CIE,DC=LAN, CN=Schema Admins,CN=Users,DC=CCIE,DC=LAN...}
MemberOf ...C=CCIE,DC=LAN, CN=Domain Admins,CN=Users,DC=CCIE,DC=LAN}
Modified 03.04.2019 11:26:30
Modified 02.04.2019 09:04:41
modifyTimeStamp 03.04.2019 11:26:30
modifyTimeStamp 02.04.2019 09:04:41
msDS-User-Account-Control-Computed 8388608
msDS-User-Account-Control-Computed 0
Name Administrator
Name student1
ObjectGUID 6f5d7164-33cf-440a-af8c-3e973a1f381a
ObjectGUID ffe12d2d-cfdd-41f6-8268-41c493786f90
objectSid S-1-5-21-2389183542-1750168592-3050041687-500
objectSid S-1-5-21-2389183542-1750168592-3050041687-1128
PasswordExpired True
PasswordExpired False
PasswordLastSet
PasswordLastSet 08.03.2019 09:41:25
pwdLastSet 0
pwdLastSet 131965080857557947
SamAccountName student1
SamAccountName Administrator
SID S-1-5-21-2389183542-1750168592-3050041687-1128
SID S-1-5-21-2389183542-1750168592-3050041687-500
uSNChanged 25764
uSNChanged 24620
uSNCreated 24653
uSNCreated 8196
whenChanged 02.04.2019 09:04:41
whenChanged 03.04.2019 11:26:30
whenCreated 08.03.2019 10:31:50
whenCreated 02.04.2019 09:13:17

还可以只输出需要的属性:

1
2
3
4
5
6
7
8
9
10
PS C:\> Compare-User -User1 student1 -User2 administrator -Filter memberof, lastlogontime, logonCount, Name

Name Value
---- -----
logonCount 177
logonCount 4
MemberOf ...ise Admins,CN=Users,DC=CCIE,DC=LAN, CN=Schema Admins,CN=Users,DC=CCIE,DC=LAN...}
MemberOf ...LAN, CN=Test1,CN=Users,DC=CCIE,DC=LAN, CN=Domain Admins,CN=Users,DC=CCIE,DC=LAN}
Name Administrator
Name student1