Skip to content

Commit c419e66

Browse files
authored
Add doc for DMSA + check signature in SFU response (#4747)
1 parent 7ea97d7 commit c419e66

3 files changed

Lines changed: 96 additions & 14 deletions

File tree

doc/scapy/layers/kerberos.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,37 @@ that are available. For more detail regarding the parameters of the functions, i
130130
Start time End time Renew until Auth time
131131
15/04/25 20:15:20 16/04/25 06:10:22 16/04/25 06:10:22 15/04/25 20:15:17
132132
133+
- **Request a ticket for a DMSA**
134+
135+
For more information about DMSAs and how to create them, consult the `relevant Microsoft documentation <https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/delegated-managed-service-accounts/delegated-managed-service-accounts-set-up-dmsa>`_. In this example we allowed ``SERVER1$`` to retrieve the managed password of ``dmsa_user$``.
136+
137+
.. code:: pycon
138+
139+
>>> load_module("ticketer")
140+
>>> t = Ticketer()
141+
>>> t.request_tgt("SERVER1$@domain.local", key=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, bytes.fromhex("63a2577d8bf6abeba0847cded36b9aed202c23750eb9c56b6155be1cc946bb1d")))
142+
>>> t.request_st(0, "krbtgt/domain.local", for_user="dmsa_user$@domain.local", dmsa=True)
143+
INFO: 3 DMSA keys found and imported !
144+
>>> t.show()
145+
Keytab name: UNSAVED
146+
Principal Timestamp KVNO Keytype
147+
dmsa_user$@domain.local 22/05/25 22:03:58 1 AES256-CTS-HMAC-SHA1-96
148+
dmsa_user$@domain.local 22/05/25 22:03:58 2 AES128-CTS-HMAC-SHA1-96
149+
dmsa_user$@domain.local 22/05/25 22:03:58 3 RC4-HMAC
150+
151+
CCache tickets:
152+
0. SERVER1$@DOMAIN.LOCAL -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL
153+
canonicalize+pre-authent+initial+renewable+forwardable
154+
Start time End time Renew until Auth time
155+
22/05/25 22:06:32 23/05/25 08:03:53 23/05/25 08:03:53 22/05/25 22:06:32
156+
157+
1. dmsa_user$@domain.local -> krbtgt/DOMAIN.LOCAL@DOMAIN.LOCAL
158+
canonicalize+pre-authent+renewable+forwardable
159+
Start time End time Renew until Auth time
160+
22/05/25 22:06:37 23/05/25 08:03:53 23/05/25 08:03:53 22/05/25 22:06:32
161+
162+
As you can see, DMSA keys were imported in the keytab. You can use those as detailed in some of the following sections.
163+
133164
- **Load and use keytab for client**
134165

135166
.. code:: pycon

scapy/layers/kerberos.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ class AD_AND_OR(ASN1_Packet):
701701
165: "PA-SUPPORTED-ENCTYPES",
702702
166: "PA-EXTENDED-ERROR",
703703
167: "PA-PAC-OPTIONS",
704+
170: "KERB-SUPERSEDED-BY-USER",
705+
171: "KERB-DMSA-KEY-PACKAGE",
704706
}
705707

706708
_PADATA_CLASSES = {
@@ -1053,6 +1055,9 @@ class KERB_SUPERSEDED_BY_USER(ASN1_Packet):
10531055
)
10541056

10551057

1058+
_PADATA_CLASSES[170] = KERB_SUPERSEDED_BY_USER
1059+
1060+
10561061
# [MS-KILE] sect 2.2.14
10571062

10581063

@@ -1070,14 +1075,17 @@ class KERB_DMSA_KEY_PACKAGE(ASN1_Packet):
10701075
"previousKeys",
10711076
[],
10721077
ASN1F_PACKET("", None, EncryptionKey),
1073-
explicit_tag=0xA0,
1078+
explicit_tag=0xA1,
10741079
),
10751080
),
10761081
KerberosTime("expirationInterval", GeneralizedTime(), explicit_tag=0xA2),
10771082
KerberosTime("fetchInterval", GeneralizedTime(), explicit_tag=0xA4),
10781083
)
10791084

10801085

1086+
_PADATA_CLASSES[171] = KERB_DMSA_KEY_PACKAGE
1087+
1088+
10811089
# RFC6113 sect 5.4.1
10821090

10831091

@@ -1558,6 +1566,12 @@ class KRB_TGS_REP(ASN1_Packet):
15581566
implicit_tag=ASN1_Class_KRB.TGS_REP,
15591567
)
15601568

1569+
def getUPN(self):
1570+
return "%s@%s" % (
1571+
self.cname.toString(),
1572+
self.crealm.val.decode(),
1573+
)
1574+
15611575

15621576
class LastReqItem(ASN1_Packet):
15631577
ASN1_codec = ASN1_Codecs.BER
@@ -3389,6 +3403,19 @@ def _process_padatas_and_key(self, padatas):
33893403
)
33903404
elif padata.padataType == 137: # PA-FX-ERROR
33913405
self.fast_error = padata.padataValue
3406+
elif padata.padataType == 130: # PA-FOR-X509-USER
3407+
# Verify S4U checksum
3408+
key_usage_number = None
3409+
pasfux509 = padata.padataValue
3410+
# [MS-SFU] sect 2.2.2
3411+
# "In a reply, indicates that it was signed with key usage 27"
3412+
if pasfux509.userId.options.val[2] == "1": # USE_REPLY_KEY_USAGE
3413+
key_usage_number = 27
3414+
pasfux509.checksum.verify(
3415+
self.key,
3416+
bytes(pasfux509.userId),
3417+
key_usage_number=key_usage_number,
3418+
)
33923419

33933420
# 2. Update the current key if necessary
33943421

@@ -3455,10 +3482,10 @@ def receive_krb_error_as_req(self, pkt):
34553482
return
34563483

34573484
if pkt.root.errorCode == 25: # KDC_ERR_PREAUTH_REQUIRED
3458-
if not self.key and (not self.upn or not self.password):
3485+
if not self.key:
34593486
log_runtime.error(
34603487
"Got 'KDC_ERR_PREAUTH_REQUIRED', "
3461-
"but no key, nor upn+pass was passed."
3488+
"but no possible key could be computed."
34623489
)
34633490
raise self.FINAL()
34643491
self.should_followup = True
@@ -3542,7 +3569,11 @@ def receive_krb_error_tgs_req(self, pkt):
35423569
@ATMT.receive_condition(SENT_TGS_REQ)
35433570
def receive_tgs_rep(self, pkt):
35443571
if Kerberos in pkt and isinstance(pkt.root, KRB_TGS_REP):
3545-
if not self.renew and pkt.root.ticket.sname.nameString[0].val == b"krbtgt":
3572+
if (
3573+
not self.renew
3574+
and not self.dmsa
3575+
and pkt.root.ticket.sname.nameString[0].val == b"krbtgt"
3576+
):
35463577
log_runtime.warning("Received a cross-realm referral ticket !")
35473578
raise self.FINAL().action_parameters(pkt)
35483579

@@ -3570,6 +3601,8 @@ def decrypt_tgs_rep(self, pkt):
35703601
res = enc.decrypt(self.replykey, key_usage_number=9, cls=EncTGSRepPart)
35713602
else:
35723603
res = enc.decrypt(self.replykey)
3604+
3605+
# Store result
35733606
self.result = self.RES_TGS_MODE(pkt.root, res.key.toKey(), res)
35743607

35753608
@ATMT.state(final=1)
@@ -4169,6 +4202,8 @@ def GSS_WrapEx(self, Context, msgs, qop_req=0):
41694202
Data = b"".join(x.data for x in msgs if x.conf_req_flag)
41704203
DataLen = len(Data)
41714204
# 2. Add filler
4205+
# [MS-KILE] sect 3.4.5.4.1 - "For AES-SHA1 ciphers, the EC must not
4206+
# be zero"
41724207
tok.root.EC = ((-DataLen) % Context.KrbSessionKey.ep.blocksize) or 16
41734208
Filler = b"\x00" * tok.root.EC
41744209
Data += Filler

scapy/modules/ticketer.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,21 @@ def import_krb(self, res, key=None, hash=None, _inplace=None):
695695
rep = res.asrep
696696
elif isinstance(res, KerberosClient.RES_TGS_MODE):
697697
rep = res.tgsrep
698+
699+
# There could be 171 = KERB_DMSA_KEY_PACKAGE to import
700+
for padata in res.kdcrep.encryptedPaData:
701+
if padata.padataType == 171:
702+
# We have keys to import.
703+
key_package = padata.padataValue
704+
for key in key_package.currentKeys:
705+
self.add_cred(
706+
principal=rep.getUPN(),
707+
key=key.toKey(),
708+
)
709+
log_interactive.info(
710+
"%s DMSA keys found and imported !"
711+
% len(key_package.currentKeys)
712+
)
698713
else:
699714
raise ValueError("Unknown type of obj !")
700715
cred.set_from_krb(
@@ -2343,15 +2358,16 @@ def _resign_ticket(self, tkt, spn, hash=None, kdc_hash=None):
23432358
hash=kdc_hash,
23442359
)
23452360

2346-
# NOTE: the doc is very unclear regarding the order of the Signatures.
2361+
# Doc was updated after feedback ! it's now very clear.
23472362

2348-
# "The extended KDC signature is a keyed hash [RFC4757] of the entire PAC
2349-
# message, with the Signature fields of all other PAC_SIGNATURE_DATA structures
2350-
# (section 2.8) set to zero."
2351-
# ==> This is wrong.
2352-
# The Ticket Signature is present when computing the Extended KDC Signature.
2363+
# [MS-PAC] sect 2.8.1
2364+
# Signatures are computed in this order:
2365+
# - Ticket signature
2366+
# - Extended KDC signature
2367+
# - Server signature
2368+
# - KDC signature
23532369

2354-
# sect 2.8.3 - Ticket Signature
2370+
# sect 2.8.2 - Ticket Signature
23552371

23562372
if 0x00000010 in sig_i:
23572373
# "The ad-data in the PAC’s AuthorizationData element ([RFC4120]
@@ -2365,7 +2381,7 @@ def _resign_ticket(self, tkt, spn, hash=None, kdc_hash=None):
23652381
# included in the PAC when signing it for Extended Server Signature & Server Signature
23662382
pac.Payloads[sig_i[0x00000010]].Signature = ticket_sig
23672383

2368-
# sect 2.8.4 - Extended KDC Signature
2384+
# sect 2.8.3 - Extended KDC Signature
23692385

23702386
if 0x00000013 in sig_i:
23712387
rpac.Payloads[sig_i[0x00000013]].Signature = extended_kdc_sig = (
@@ -2374,13 +2390,13 @@ def _resign_ticket(self, tkt, spn, hash=None, kdc_hash=None):
23742390
# included in the PAC when signing it for Server Signature
23752391
pac.Payloads[sig_i[0x00000013]].Signature = extended_kdc_sig
23762392

2377-
# sect 2.8.1 - Server Signature
2393+
# sect 2.8.4 - Server Signature
23782394

23792395
rpac.Payloads[sig_i[0x00000006]].Signature = server_sig = key_srv.make_checksum(
23802396
17, bytes(pac) # KERB_NON_KERB_CKSUM_SALT(17)
23812397
)
23822398

2383-
# sect 2.8.2 - KDC Signature
2399+
# sect 2.8.5 - KDC Signature
23842400

23852401
rpac.Payloads[sig_i[0x00000007]].Signature = key_kdc.make_checksum(
23862402
17, server_sig # KERB_NON_KERB_CKSUM_SALT(17)

0 commit comments

Comments
 (0)