【弱密码】域认证

概述:Windows 域认证

相关文章推荐:

#MD4 #NTLM #mimikatz #横向渗透

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

域认证

#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 认证流程

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

2. Ticket Granting Service Exchange

TGS(Ticket Granting Service)Exchange 通过 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。

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 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 的这个方案还是在使用场景上有缺陷。

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

方案二 quarkspwdump

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

域用户 Hash Crash

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

域缓存 hash

定义可以看 【域渗透】域Hash缓存

使用 mimikatz 提取

查询命令

1
2
3
privilege::debug
token::elevate
lsadump::cache

quarkspwdump

查询命令

1
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 的形式如下所示:

1100 Domain Cached Credentials (DCC), MS Cache 4dd8965d1d476fa0d026722989a6b772:3060147285011

使用示例

1
2
3
{MsCache}:{uername}

hashcat -m 1100 -w 4 -a 3 4dd8965d1d476fa0d026722989a6b772:3060147285011

DCC2 Crash

工具

主要借助 hashcat 来探测

format

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

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

使用示例

1
2
3
$DCC2$10240#{username}#{MsCacheV2}

hashcat -m 2100 -w 4 -a 3 $DCC2$10240#tom#e4e938d12fe5974dc42a90120bd9c90f

相关代码

实测可用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
* 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;
}

【弱密码】域认证
https://hodlyounger.github.io/wiki/弱密码/【弱密码】域认证/
作者
mingming
发布于
2024年9月9日
许可协议