概述:Windows 域认证

相关文章推荐:

md4 ntlm mimikatz 横向渗透

**在阅读之前你可能还需要补充的知识:

域认证

Visio-KerberosComms)

kerberos 域认证采用 Kerberos 协议的认证机制,有一个可信的第三方机构 KDC 的参与。

Kerberos

kerberos

  • kerbose 是一种网络认证协议,其设计目标是通过密钥系统为 客户机/服务器 应用提供强大的认证服务。
  • 该认证过程的视线不依赖于主机操作系统的认证,无需基于主机地址的信任,不要求网络上所有主机的物理安全,并假定网络上传送的数据包可以被任意的读取、修改和插入数据。
  • 在以上情况下,Kerberos 作为一种可信任的第三方认证服务,是通过传统的密码技术(如:共享密钥)执行认证服务的。

参与认证的三个角色:

  • Client
  • Server
  • KDC (Key Distribution Center) = DC (Domain Controller) = AD (Account Database) + AS (Authenication Service) + (TGS Ticket Granting Service) 从物理层面看,AD 与 AS,TGS,KDC 均为与控制器(Domain Controller)。

Kerberos 认证协议的基本概念

  • 票据(Ticket): 是网络对象互相访问的凭证
  • TGT (Ticket Granting Ticket): 用来生成 Ticket 的 Ticket
  • AD (Account Database): 存储域中所有用户的用户名和对应的 NTLM Hash,可以理解为域中的 SAM 数据库,KDC 可以从 AD 中提取域中所有用户的 NTLM Hash,这是 Kerberos 协议能够成功实现的基础。
  • KDC (Key Distribution Center): 密钥分发中心,负责管理票据、认证票据、分发票据,里面包含两个服务: ASTGS
  • AS (Authentication Server): 身份认证服务,为 Client 生成 TGT 的服务,也用来完成对 Client 的身份验证
  • TGS (Ticket Granting Server): 票据授予服务,为 Client 生成允许对某个服务访问的 Ticket ,就是 Client 从 AS 那里拿到 TGT 之后,来 TGS 这里再申请对某个特定服务或服务器访问的 Ticket,只有获取到这个 Ticket,Client 才有权限去访问对应的服务,该服务提供的票据也称为 TGS 或者叫 白银票据
  • TGT (Ticket Granting Ticket): 由身份认证服务授予的票据( 黄金票据 ),用于身份认证,存储在内存,默认有效期为 10 小时。

    注意:

    • Client 密钥、TGS 密钥和 Service 密钥均为对应用户的 NTLM Hash
    • TGS 密钥 `KDC Hash` krbtgt 用户的 NTLM Hash
    • ServerService 可以当做一个东西,就是 Client 想要访问的服务器或者服务
    • Client/(TGT/Server) SessionKey 可以看做客户端与 TGS 服务和尝试登录的 Server 之间会话用来加密的密钥,而 (Client/TGT/Service) 密钥(上面提到的三个实际为 NTLM Hash 的密钥)是用来加密会话密钥的密钥,为了保证会话密钥的传输安全,这些加密方式均为对称加密

    参与认证的三个角色的 NTLM Hash 是三个密钥,这三个 NTLM Hash 的唯一作用是确保会话密钥 SessionKey 的安全传输

黄金票据 (Golden Ticket)

如下所示为黄金票据和白银票据的生成过程 黄金票据生成过程)

黄金门票是伪造的 TGT。这意味着我们绕过下图 kerberos认证 的步骤 1-2,在那里我们向 DC 证明我们是谁  可以说有了金票就有了域内的最高权限

每个用户的 Ticket 都是由 krbtgt 的密码 Hash 来生成的,那么,我们如果拿到了 krbtgt 的密码 Hash,其实就可以伪造任意用户的 TICKET,

对于攻击者来说,实际上只要拿到了域控权限,就可以直接导出 krbtgt 的 Hash 值,,再通过 mimikatz 即可生成任意用户任何权限的 Ticket,也就是 Golden Ticket。

白银票据 (Siliver Ticket)

Silver Tickets(下面称银票)就是伪造的 ST(Service Ticket),因为在 TGT 已经在 PAC 里限定了给 Client 授权的服务(通过 SID 的值) 就是说计算机已经固定了,所以银票只能访问指定服务。

银票是伪造的 TGS 门票。所以现在,我们跳过了与 DC 上的 K DC 进行的所有通信(下图 kerberos认证中的步骤 1-4),只与我们希望直接访问的服务进行接口

白银票据认证流程)

kerberos 认证流程

Kerberos 认证示意图)

1. Authentication Service Exchange

。具体过程如下:

Client 向 KDC 的 Authentication Service 发送 Authentication Service Request(KRB_AS_REQ), 为了确保 KRB_AS_REQ 仅限于自己和 KDC 知道,Client 使用自己的 Master Key 对 KRB_AS_REQ 的主体部分进行加密(KDC 可以通过 Domain 的 Account Database 获得该 Client 的 Master Key)。KRB_AS_REQ 的大体包含以下的内容:

  • Pre-authentication data:包含用以证明自己身份的信息。说白了,就是证明自己知道自己声称的那个 account 的 Password。一般地,它的内容是一个被 Client 的 Master key 加密过的 Timestamp。
  • Client name & realm: 简单地说就是 Domain name\Client
  • Server Name:注意这里的 Server Name 并不是 Client 真正要访问的 Server 的名称,而我们也说了 TGT 是和 Server 无关的(Client 只能使用 Ticket,而不是 TGT 去访问 Server)。这里的 Server Name 实际上是KDC 的 Ticket Granting Service 的 Server Name

img)

img)

2. Ticket Granting Service Exchange

TGS(Ticket Granting ServiceExchange] 通过 Client 向 KDC 中的 TGS(Ticket Granting Service)发送 Ticket Granting Service Request(KRB_TGS_REQ)开始。KRB_TGS_REQ 大体包含以下的内容:

  • TGT:Client 通过 AS Exchange 获得的 Ticket Granting Ticket,TGT 被 KDC 的 Master Key 进行加密。
  • Authenticator:用以证明当初 TGT 的拥有者是否就是自己,所以它必须以 TGT 的颁发方(KDC)给自己(Client)的 Session Key(SKDC-Client:Logon Session Key)来进行加密。
  • Client name & realm: 简单地说就是 Domain name\Client。
  • Server name & realm: 简单地说就是 Domain name\Server,这回是 Client 试图访问的那个 Server。

img)

TGS 收到 KRB_TGS_REQ 在发给 Client 真正的 Ticket 之前,先得验证 Client 提供的那个 TGT 是否是 AS 颁发给它的。于是它需要验证 Client 提供的 Authenticator,但是 Authentication 是通过 Logon Session Key(SKDC-Client)进行加密的,而自己并没有保存这个 Session Key。所以 TGS 先得通过自己的 Master Key 对 Client 提供的 TGT 进行解密,从而获得这个 Logon Session Key(SKDC-Client),再通过这个 Logon Session Key(SKDC-Client)解密 Authenticator 进行验证。验证通过向对方发送 Ticket Granting Service Response(KRB_TGS_REP)。这个 KRB_TGS_REP 有两部分组成:使用 Logon Session Key(SKDC-Client)加密过用于 Client 和 Server 的 Session Key(SServer-Client)和使用 Server 的 Master Key 进行加密的 Ticket。该 Ticket 大体包含以下一些内容:

  • Session Key:SServer-Client。
  • Client name & realm: 简单地说就是 Domain name\Client。
  • PAC
  • End time: Ticket 的到期时间。

Client 收到 KRB_TGS_REP,使用 Logon Session Key(SKDC-Client)解密第一部分后获得 Session Key(SServer-Client)。有了 Session Key 和 Ticket,Client 就可以之间和 Server 进行交互,而无须在通过 KDC 作中间人了。

3. Client/Server Exchange

Client 通过 TGS Exchange 获得 Client 和 Server 的 Session Key(SServer-Client),随后创建用于证明自己就是 Ticket 的真正所有者的 Authenticator,并使用 Session Key(SServer-Client)进行加密。最后将这个被加密过的 Authenticator 和 Ticket 作为 APPLication Service Request(KRB_AP_REQ)发送给 Server。除了上述两项内容之外,KRB_AP_REQ 还包含一个 Flag 用于表示 Client 是否需要进行双向验证(Mutual Authentication)。

Server 接收到 KRB_AP_REQ 之后,通过自己的 Master Key 解密 Ticket,从而获得 Session Key(SServer-Client)。通过 Session Key(SServer-Client)解密 Authenticator,进而验证对方的身份,验证成功,让 Client 访问需要访问的资源,否则直接拒绝对方的请求。

获取域用户 hash

使用 mimikatz

可参mimikatz > 获取域用户]] 一节。

更详细的命令帮助手册可查看 [dcsync | The Hacker [【RPC】Tools|Tools

域用户 Hash

在 Windows 系统中,比较常见是从系统导出来的 NTLM hash,可以通过 Hashcat 能够破解出明文密码。

Hashcat 支持超过 200 种高度优化的 hash 算法,其中和 NTLM hash 相关的有 4 个,分别为 NetNTLMv1NetNTLMv1+ESSNetNTLMv2NTLM。关于 hashcat 的使用可以参考hashcat 用法]

域用户 Hash 获取

方案一 mimikatz

使用 mimikatz 是可以获取到与用户的相关信息的,命令手册 dcsync | The Hacker Tools

但是 mimikatz 是基于 DCSync 的,而 DCSync 又是很容易被 IDS/IPS 作为攻击标志的点,这就导致 mimikatz 的这个方案还是在使用场景上有缺陷。

mimikatz.exe "lsadump::dcsync /domain:ms.test.com /all /csv"

方案二 quarkspwdump

QuarkspwDump 说明peterdocter/quarkspwdump: Quarks PwDump is a native Win32 tool to extract credentials from Windows operating systems.

使用 quarkspwdump 获取域用户 hash。这个主要是从内存中提取用户的 Hash。与实际的有出入。

域用户 Hash Crash

域用户 Hash 和本地用户 hash 一致,都是使用 MD4 加密。

域缓存 hash

定义可以看域Hash缓存]

使用 mimikatz 提取

查询命令

privilege::debug
token::elevate
lsadump::cache

quarkspwdump

查询命令

quarkspwdump -dhdc

域缓存 hash Crash

相关参考文章

域缓存 hash 使用的加密方式略有不同:

  • Windows vista 以前使用的是 DCC,也就是 Domain Cached Credentials (DCC), MS Cache
  • Windows vista 之后都是用的 DCC2,也就是 Domain Cached Credentials 2 (DCC2), MS Cache 2

DCC Crash

工具

借助 hashcat 来探测

format

使用 hashcat 破解,破解 DCC 的形式如下所示:

1100Domain Cached Credentials (DCC), MS Cache4dd8965d1d476fa0d026722989a6b772:3060147285011

使用示例

{MsCache}:{uername}
 
hashcat -m 1100 -w 4 -a 3 4dd8965d1d476fa0d026722989a6b772:3060147285011

DCC2 Crash

工具

主要借助 hashcat 来探测

format

使用 hashcat 破解 DCC2 的形式如下所示:

2100Domain Cached Credentials 2 (DCC2), MS Cache 210240#tom#e4e938d12fe5974dc42a90120bd9c90f

使用示例

$DCC2$10240#{username}#{MsCacheV2}
 
hashcat -m 2100 -w 4 -a 3 $DCC2$10240#tom#e4e938d12fe5974dc42a90120bd9c90f

相关代码

实测可用

/*
 * dcc2_tst.c  A 'very' simple OpenSSL primative only test app, showing the
 * MSCASH2 format.
 * Written June 24, 2011, Jim Fougeron.  Placed in public domain
 * This is a very slow, but easier to understand mscash2 hash computation program
 * It has been written 100% with oSSL primative functions (SHA1 and MD4).  It does
 * not use any hmac primatives.    This pbkdf2 'has' been reduced some, since we
 * know a 'little' info.  1. the key will always be smaller than SHA_DIGEST_LENGTH
 * thus we can remove an initial sha reduction.  2. we only use lower 128 bites, so
 * only xor the first 4 words, not first 5
 *
 * If the program is run with no params, then user=admin and pass=password is used
 * arguments -p=PASSWORD  and -u=USER can be used to change the pass / user
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "openssl/sha.h"
#include "openssl/md4.h"
 
#pragma comment(lib, "libssl.lib")
#pragma comment(lib, "libcrypto.lib")
 
 /*
  * This function is derived from IEEE Std 802.11-2004, Clause H.4.
  * The main construction is from PKCS#5 v2.0.  It is tweaked a little
  * to remove some code not needed for our SHA1-128 output.
  */
void pbkdf2(unsigned char key[], size_t key_len,
	unsigned char salt[], size_t salt_len,
	unsigned int rounds,
	unsigned char digest[])
{
	SHA_CTX ctx1, ctx2, tmp_ctx1, tmp_ctx2;
	unsigned char ipad[SHA_CBLOCK + 1], opad[SHA_CBLOCK + 1], tmp_hash[SHA_DIGEST_LENGTH];
	unsigned i, j;
 
	memset(ipad, 0x36, sizeof(ipad));
	memset(opad, 0x5C, sizeof(opad));
 
	for (i = 0; i < key_len; i++) {
		ipad[i] ^= key[i];
		opad[i] ^= key[i];
	}
 
	SHA1_Init(&ctx1);
	SHA1_Init(&ctx2);
 
	SHA1_Update(&ctx1, ipad, SHA_CBLOCK);
	SHA1_Update(&ctx2, opad, SHA_CBLOCK);
 
	memcpy(&tmp_ctx1, &ctx1, sizeof(SHA_CTX));
	memcpy(&tmp_ctx2, &ctx2, sizeof(SHA_CTX));
 
	SHA1_Update(&ctx1, salt, salt_len);
	SHA1_Final(tmp_hash, &ctx1);
 
	SHA1_Update(&ctx2, tmp_hash, SHA_DIGEST_LENGTH);
	SHA1_Final(tmp_hash, &ctx2);
 
	memcpy(digest, tmp_hash, SHA_DIGEST_LENGTH);
 
	for (i = 1; i < rounds; i++)
	{
		memcpy(&ctx1, &tmp_ctx1, sizeof(SHA_CTX));
		memcpy(&ctx2, &tmp_ctx2, sizeof(SHA_CTX));
 
		SHA1_Update(&ctx1, tmp_hash, SHA_DIGEST_LENGTH);
		SHA1_Final(tmp_hash, &ctx1);
 
		SHA1_Update(&ctx2, tmp_hash, SHA_DIGEST_LENGTH);
		SHA1_Final(tmp_hash, &ctx2);
 
		for (j = 0; j < 4; j++)
			((unsigned int*)digest)[j] ^= ((unsigned int*)tmp_hash)[j];
	}
}
 
// simple 'to-unicode', adds null bytes. !WARNING! no overflow logic.
unsigned to_unicode(char* u16, char* a8) {
	unsigned cnt = strlen(a8);
	while (*a8) {
		*u16++ = *a8++;  *u16++ = 0;
	}
	return cnt << 1;
}
 
char hexdigit(int i) { // one hex digit
	if (i < 10) return i + '0';
	return (i - 10) + 'a';
}
char* to_hex(unsigned char* digest) {  // convert 16 byte digest to 32 byte hex
	static char buf[33];
	char* cp = buf;
	int i;
	for (i = 0; i < 16; ++i) {
		*cp++ = hexdigit(*digest >> 4); *cp++ = hexdigit(*digest++ & 0xF);
	}
	*cp = 0;
	return buf;
}
 
/*
 * usage:  dcc2_tst [-p=pass] [-u=username]
 */
int dcc2_tst(int argc, char** argv)
{
	char* username = (char*)"shimingming", * password = (char*)"Admin@123";
	unsigned char username_lc[22], salt[44], pass_unicode[128 + 2], md4hash[16], digest[20];
	unsigned salt_len, pass_len;
	MD4_CTX ctx;
	int i;
 
	// see if -p= or -u= was used.  If so, then use them.
	for (i = 1; i < argc; ++i) {
		if (!strncmp(argv[i], "-p=", 3)) password = &argv[i][3];
		if (!strncmp(argv[i], "-u=", 3)) username = &argv[i][3];
	}
 
	// low case user name (the salt), and convert to unicode.
	strncpy((char*)username_lc, username, 21);
	username_lc[21] = 0;
	if (strlen(username) != strlen((char*)username_lc)) return !!printf("Error, the user name is longer than 21 bytes.  Aborting\n");
	salt_len = to_unicode((char*)salt, strlwr((char*)username_lc));
 
	// pasword to unicode
	pass_len = to_unicode((char*)pass_unicode, password);
 
	// now get NTLM of the password (MD4 of unicode)
 
	MD4_Init(&ctx);
	MD4_Update(&ctx, pass_unicode, pass_len);
	MD4_Final(md4hash, &ctx);
	// Now we have NTLM  md4Hash==NTLM of the password
 
	// Get DCC1.  That is MD4( NTLM . unicode(lc username) )
	MD4_Init(&ctx);
	MD4_Update(&ctx, md4hash, 16);
	MD4_Update(&ctx, salt, salt_len);
	MD4_Final(md4hash, &ctx);
	// now we have DCC1 (mscash) which is MD4 (MD4(unicode(pass)) . unicode(lc username))
 
	// we need to change the salt a little, before calling pbkdf2 (add a big endian 32 bit 1)
	memset(&salt[salt_len], 0, 4);
	salt[salt_len + 3] = 1;
	salt_len += 4;
 
	// Now compute DCC2
	pbkdf2(md4hash, 16, salt, salt_len, 10240, digest);
 
	// Ok, now output the hash:
	printf("%s:", username);
	printf("%$%s#%s:0:1:%s\n", username_lc, to_hex(digest), password);
 
	return 0;
}