Skip to content

Commit 0a9e9c7

Browse files
committed
Kerberos: fix passive with DCE/RPC + improve deleg
1 parent 61251f4 commit 0a9e9c7

1 file changed

Lines changed: 111 additions & 46 deletions

File tree

scapy/layers/kerberos.py

Lines changed: 111 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ def get_usage(self):
390390
elif isinstance(self.underlayer, KRB_AS_REP):
391391
# AS-REP encrypted part
392392
return 3, EncASRepPart
393+
elif isinstance(self.underlayer, KRB_KDC_REQ_BODY):
394+
# KDC-REQ enc-authorization-data
395+
return 4, AuthorizationData
393396
elif isinstance(self.underlayer, KRB_AP_REQ) and isinstance(
394397
self.underlayer.underlayer, PADATA
395398
):
@@ -982,9 +985,10 @@ class KERB_AD_RESTRICTION_ENTRY(ASN1_Packet):
982985
class KERB_AUTH_DATA_AP_OPTIONS(Packet):
983986
name = "KERB-AUTH-DATA-AP-OPTIONS"
984987
fields_desc = [
985-
LEIntEnumField(
988+
FlagsField(
986989
"apOptions",
987990
0x4000,
991+
-32,
988992
{
989993
0x4000: "KERB_AP_OPTIONS_CBT",
990994
0x8000: "KERB_AP_OPTIONS_UNVERIFIED_TARGET_NAME",
@@ -1809,6 +1813,12 @@ class KRB_AS_REP(ASN1_Packet):
18091813
implicit_tag=ASN1_Class_KRB.AS_REP,
18101814
)
18111815

1816+
def getUPN(self):
1817+
return "%s@%s" % (
1818+
self.cname.toString(),
1819+
self.crealm.val.decode(),
1820+
)
1821+
18121822

18131823
class KRB_TGS_REP(ASN1_Packet):
18141824
ASN1_codec = ASN1_Codecs.BER
@@ -2420,11 +2430,11 @@ class KRB_AuthenticatorChecksum(Packet):
24202430
},
24212431
),
24222432
ConditionalField(
2423-
LEShortField("DlgOpt", 0),
2433+
LEShortField("DlgOpt", 1),
24242434
lambda pkt: pkt.Flags.GSS_C_DELEG_FLAG,
24252435
),
24262436
ConditionalField(
2427-
FieldLenField("Dlgth", None, length_of="Deleg"),
2437+
FieldLenField("Dlgth", None, length_of="Deleg", fmt="<H"),
24282438
lambda pkt: pkt.Flags.GSS_C_DELEG_FLAG,
24292439
),
24302440
ConditionalField(
@@ -3021,8 +3031,8 @@ class KerberosClient(Automaton):
30213031
:param dmsa: sets the 'unconditional delegation' mode for DMSA TGT retrieval
30223032
"""
30233033

3024-
RES_AS_MODE = namedtuple("AS_Result", ["asrep", "sessionkey", "kdcrep"])
3025-
RES_TGS_MODE = namedtuple("TGS_Result", ["tgsrep", "sessionkey", "kdcrep"])
3034+
RES_AS_MODE = namedtuple("AS_Result", ["asrep", "sessionkey", "kdcrep", "upn"])
3035+
RES_TGS_MODE = namedtuple("TGS_Result", ["tgsrep", "sessionkey", "kdcrep", "upn"])
30263036

30273037
class MODE(IntEnum):
30283038
AS_REQ = 0
@@ -3120,7 +3130,7 @@ def __init__(
31203130
x509 = Cert(x509)
31213131
if not isinstance(x509key, PrivKey):
31223132
x509key = PrivKey(x509key)
3123-
if not isinstance(ca, CertList):
3133+
if ca and not isinstance(ca, CertList):
31243134
ca = CertList(ca)
31253135

31263136
if mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]:
@@ -4037,7 +4047,12 @@ def decrypt_as_rep(self, pkt):
40374047
# Decrypt AS-REP response
40384048
enc = pkt.root.encPart
40394049
res = enc.decrypt(self.replykey)
4040-
self.result = self.RES_AS_MODE(pkt.root, res.key.toKey(), res)
4050+
self.result = self.RES_AS_MODE(
4051+
pkt.root,
4052+
res.key.toKey(),
4053+
res,
4054+
pkt.root.getUPN(),
4055+
)
40414056

40424057
@ATMT.receive_condition(SENT_TGS_REQ)
40434058
def receive_krb_error_tgs_req(self, pkt):
@@ -4108,7 +4123,12 @@ def decrypt_tgs_rep(self, pkt):
41084123
res = enc.decrypt(self.replykey)
41094124

41104125
# Store result
4111-
self.result = self.RES_TGS_MODE(pkt.root, res.key.toKey(), res)
4126+
self.result = self.RES_TGS_MODE(
4127+
pkt.root,
4128+
res.key.toKey(),
4129+
res,
4130+
self.upn,
4131+
)
41124132

41134133
@ATMT.state(final=1)
41144134
def FINAL(self):
@@ -4315,8 +4335,9 @@ def krb_as_and_tgs(upn, spn, ip=None, key=None, password=None, **kwargs):
43154335
res = krb_as_req(upn=upn, ip=ip, key=key, password=password, **kwargs)
43164336
if not res:
43174337
return
4338+
43184339
return krb_tgs_req(
4319-
upn=upn,
4340+
upn=res.upn, # UPN might get canonicalized
43204341
spn=spn,
43214342
sessionkey=res.sessionkey,
43224343
ticket=res.asrep.ticket,
@@ -4640,6 +4661,7 @@ def __init__(
46404661
self.KEY = KEY
46414662
self.SPN = SPN
46424663
self.TGT = TGT
4664+
self.TGTSessionKey = None
46434665
self.PASSWORD = PASSWORD
46444666
self.U2U = U2U
46454667
self.DC_IP = DC_IP
@@ -5043,12 +5065,14 @@ def GSS_Init_sec_context(
50435065
if Context.state in [self.STATE.INIT, self.STATE.CLI_SENT_TGTREQ]:
50445066
if not self.UPN:
50455067
raise ValueError("Missing UPN attribute")
5068+
50465069
# Do we have a ST?
50475070
if self.ST is None:
50485071
# Client sends an AP-req
50495072
if not self.SPN and not target_name:
50505073
raise ValueError("Missing SPN/target_name attribute")
50515074
additional_tickets = []
5075+
50525076
if self.U2U:
50535077
try:
50545078
# GSSAPI / Kerberos
@@ -5063,39 +5087,54 @@ def GSS_Init_sec_context(
50635087
tgt_rep.show()
50645088
raise ValueError("KerberosSSP: Unexpected token !")
50655089
additional_tickets = [tgt_rep.ticket]
5066-
if self.TGT is not None:
5067-
if not self.KEY:
5068-
raise ValueError("Cannot use TGT without the KEY")
5069-
# Use TGT
5070-
res = krb_tgs_req(
5071-
upn=self.UPN,
5072-
spn=self.SPN or target_name,
5073-
ip=self.DC_IP,
5074-
sessionkey=self.KEY,
5075-
ticket=self.TGT,
5076-
additional_tickets=additional_tickets,
5077-
u2u=self.U2U,
5078-
debug=self.debug,
5079-
)
5080-
else:
5081-
# Ask for TGT then ST
5082-
res = krb_as_and_tgs(
5090+
5091+
if self.TGT is None:
5092+
# Get TGT. We were passed a kerberos key
5093+
res = krb_as_req(
50835094
upn=self.UPN,
5084-
spn=self.SPN or target_name,
50855095
ip=self.DC_IP,
50865096
key=self.KEY,
50875097
password=self.PASSWORD,
5088-
additional_tickets=additional_tickets,
5089-
u2u=self.U2U,
50905098
debug=self.debug,
50915099
)
5100+
# Update UPN (could have been canonicalized)
5101+
self.UPN = res.upn
5102+
5103+
# Store TGT,
5104+
self.TGT = res.asrep.ticket
5105+
self.TGTSessionKey = res.sessionkey
5106+
else:
5107+
# We have a TGT and were passed its key
5108+
self.TGTSessionKey = self.KEY
5109+
5110+
# Get ST
5111+
if not self.TGTSessionKey:
5112+
raise ValueError("Cannot use TGT without the KEY")
5113+
5114+
res = krb_tgs_req(
5115+
upn=self.UPN,
5116+
spn=self.SPN or target_name,
5117+
ip=self.DC_IP,
5118+
sessionkey=self.TGTSessionKey,
5119+
ticket=self.TGT,
5120+
additional_tickets=additional_tickets,
5121+
u2u=self.U2U,
5122+
debug=self.debug,
5123+
)
50925124
if not res:
50935125
# Failed to retrieve the ticket
50945126
return Context, None, GSS_S_FAILURE
5095-
self.ST, self.KEY = res.tgsrep.ticket, res.sessionkey
5127+
5128+
# Store the service ticket and associated key
5129+
self.ST, Context.STSessionKey = res.tgsrep.ticket, res.sessionkey
50965130
elif not self.KEY:
50975131
raise ValueError("Must provide KEY with ST")
5098-
Context.STSessionKey = self.KEY
5132+
else:
5133+
# We were passed a ST and its key
5134+
Context.STSessionKey = self.KEY
5135+
5136+
if Context.flags & GSS_C_FLAGS.GSS_C_DELEG_FLAG:
5137+
raise ValueError("Cannot use GSS_C_DELEG_FLAG when passed a service ticket !")
50995138

51005139
# Save ServerHostname
51015140
if len(self.ST.sname.nameString) == 2:
@@ -5127,25 +5166,49 @@ def GSS_Init_sec_context(
51275166
# Get the realm of the client
51285167
_, crealm = _parse_upn(self.UPN)
51295168

5169+
# Build the RFC4121 authenticator checksum
5170+
authenticator_checksum = KRB_AuthenticatorChecksum(
5171+
# RFC 4121 sect 4.1.1.2
5172+
# "The Bnd field contains the MD5 hash of channel bindings"
5173+
Bnd=(
5174+
chan_bindings.digestMD5()
5175+
if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS
5176+
else (b"\x00" * 16)
5177+
),
5178+
Flags=int(Context.flags),
5179+
)
5180+
5181+
if Context.flags & GSS_C_FLAGS.GSS_C_DELEG_FLAG:
5182+
# Delegate TGT
5183+
raise NotImplementedError("GSS_C_DELEG_FLAG is not implemented !")
5184+
# authenticator_checksum.Deleg = KRB_CRED(
5185+
# tickets=[self.TGT],
5186+
# encPart=EncryptedData()
5187+
# )
5188+
# authenticator_checksum.encPart.encrypt(
5189+
# Context.STSessionKey,
5190+
# EncKrbCredPart(
5191+
# ticketInfo=KrbCredInfo(
5192+
# key=EncryptionKey.fromKey(self.TGTSessionKey),
5193+
# prealm=ASN1_GENERAL_STRING(crealm),
5194+
# pname=PrincipalName.fromUPN(self.UPN),
5195+
# # TODO: rework API to pass starttime... here.
5196+
# sreralm=self.TGT.realm,
5197+
# sname=self.TGT.sname,
5198+
# )
5199+
# )
5200+
# )
5201+
5202+
51305203
# Build and encrypt the full KRB_Authenticator
51315204
ap_req.authenticator.encrypt(
51325205
Context.STSessionKey,
51335206
KRB_Authenticator(
51345207
crealm=crealm,
51355208
cname=PrincipalName.fromUPN(self.UPN),
5136-
# RFC 4121 checksum
51375209
cksum=Checksum(
51385210
cksumtype="KRB-AUTHENTICATOR",
5139-
checksum=KRB_AuthenticatorChecksum(
5140-
# RFC 4121 sect 4.1.1.2
5141-
# "The Bnd field contains the MD5 hash of channel bindings"
5142-
Bnd=(
5143-
chan_bindings.digestMD5()
5144-
if chan_bindings != GSS_C_NO_CHANNEL_BINDINGS
5145-
else (b"\x00" * 16)
5146-
),
5147-
Flags=int(Context.flags),
5148-
),
5211+
checksum=authenticator_checksum
51495212
),
51505213
ctime=ASN1_GENERALIZED_TIME(now_time),
51515214
cusec=ASN1_INTEGER(0),
@@ -5510,19 +5573,21 @@ def GSS_Passive(
55105573
Context.state = self.STATE.CLI_SENT_APREQ
55115574
else:
55125575
Context.state = self.STATE.FAILED
5513-
return Context, status
55145576
elif Context.state == self.STATE.CLI_SENT_APREQ:
55155577
Context, _, status = self.GSS_Init_sec_context(
55165578
Context, token, req_flags=req_flags
55175579
)
55185580
if status == GSS_S_COMPLETE:
5581+
if req_flags & GSS_C_FLAGS.GSS_C_DCE_STYLE:
5582+
status = GSS_S_CONTINUE_NEEDED
55195583
Context.state = self.STATE.SRV_SENT_APREP
55205584
else:
55215585
Context.state == self.STATE.FAILED
5522-
return Context, status
5586+
else:
5587+
# Unknown state. Don't crash though.
5588+
status = GSS_S_FAILURE
55235589

5524-
# Unknown state. Don't crash though.
5525-
return Context, GSS_S_FAILURE
5590+
return Context, status
55265591

55275592
def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False):
55285593
if Context.IsAcceptor is not IsAcceptor:

0 commit comments

Comments
 (0)