Skip to content

Commit be3cfbd

Browse files
authored
Kerberos improvements, bug fixes in LDAP (#4709)
1 parent 9a1ce84 commit be3cfbd

8 files changed

Lines changed: 144 additions & 33 deletions

File tree

scapy/config.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,11 +619,15 @@ def load(self):
619619
for k, v in distr.metadata.items() if k == 'Classifier'
620620
):
621621
try:
622-
pkg = next(
623-
k
624-
for k, v in importlib.metadata.packages_distributions().items()
625-
if distr.name in v
626-
)
622+
# Python 3.13 raises an internal warning when calling this
623+
with warnings.catch_warnings():
624+
warnings.filterwarnings("ignore", category=DeprecationWarning)
625+
pkg = next(
626+
k
627+
for k, v in
628+
importlib.metadata.packages_distributions().items()
629+
if distr.name in v
630+
)
627631
except KeyError:
628632
pkg = distr.name
629633
if pkg in self._loaded:

scapy/layers/dcerpc.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,12 +1805,14 @@ def m2i(self, pkt, m):
18051805
return self.cls(m, ndr64=pkt.ndr64, ndrendian=pkt.ndrendian, _parent=pkt)
18061806

18071807

1808-
# class _NDRPacketPadField(PadField):
1809-
# def padlen(self, flen, pkt):
1810-
# if pkt.ndr64:
1811-
# return -flen % self._align[1]
1812-
# else:
1813-
# return 0
1808+
class _NDRPacketPadField(PadField):
1809+
# [MS-RPCE] 2.2.5.3.4.1 Structure with Trailing Gap
1810+
# Structures have extra alignment/padding in NDR64.
1811+
def padlen(self, flen, pkt):
1812+
if pkt.ndr64:
1813+
return -flen % self._align[1]
1814+
else:
1815+
return 0
18141816

18151817

18161818
class NDRPacketField(NDRConstructedType, NDRAlign):
@@ -1819,9 +1821,7 @@ def __init__(self, name, default, pkt_cls, **kwargs):
18191821
self.fld = _NDRPacketField(name, default, pkt_cls=pkt_cls, **kwargs)
18201822
NDRAlign.__init__(
18211823
self,
1822-
# There is supposed to be padding after a struct in NDR64?
1823-
# _NDRPacketPadField(fld, align=pkt_cls.ALIGNMENT),
1824-
self.fld,
1824+
_NDRPacketPadField(self.fld, align=pkt_cls.ALIGNMENT),
18251825
align=pkt_cls.ALIGNMENT,
18261826
)
18271827
NDRConstructedType.__init__(self, pkt_cls.fields_desc)

scapy/layers/kerberos.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -695,10 +695,14 @@ def m2i(self, pkt, s):
695695
if pkt.padataType.val in _PADATA_CLASSES:
696696
cls = _PADATA_CLASSES[pkt.padataType.val]
697697
if isinstance(cls, tuple):
698-
is_reply = (
699-
pkt.underlayer.underlayer is not None
700-
and isinstance(pkt.underlayer.underlayer, KRB_ERROR)
701-
) or isinstance(pkt.underlayer, (KRB_AS_REP, KRB_TGS_REP))
698+
parent = pkt.underlayer or pkt.parent
699+
is_reply = False
700+
if parent is not None:
701+
if isinstance(parent, (KRB_AS_REP, KRB_TGS_REP)):
702+
is_reply = True
703+
else:
704+
parent = parent.underlayer or parent.parent
705+
is_reply = isinstance(parent, KRB_ERROR)
702706
cls = cls[is_reply]
703707
if not val[0].val:
704708
return val
@@ -1803,15 +1807,23 @@ def m2i(self, pkt, s):
18031807
# 25: KDC_ERR_PREAUTH_REQUIRED
18041808
# 36: KRB_AP_ERR_BADMATCH
18051809
return MethodData(val[0].val, _underlayer=pkt), val[1]
1806-
elif pkt.errorCode.val in [6, 7, 13, 18, 29, 41, 60]:
1810+
elif pkt.errorCode.val in [6, 7, 12, 13, 18, 29, 41, 60]:
18071811
# 6: KDC_ERR_C_PRINCIPAL_UNKNOWN
18081812
# 7: KDC_ERR_S_PRINCIPAL_UNKNOWN
1813+
# 12: KDC_ERR_POLICY
18091814
# 13: KDC_ERR_BADOPTION
18101815
# 18: KDC_ERR_CLIENT_REVOKED
18111816
# 29: KDC_ERR_SVC_UNAVAILABLE
18121817
# 41: KRB_AP_ERR_MODIFIED
18131818
# 60: KRB_ERR_GENERIC
1814-
return KERB_ERROR_DATA(val[0].val, _underlayer=pkt), val[1]
1819+
try:
1820+
return KERB_ERROR_DATA(val[0].val, _underlayer=pkt), val[1]
1821+
except BER_Decoding_Error:
1822+
if pkt.errorCode.val in [18]:
1823+
# Some types can also happen in FAST sessions
1824+
# 18: KDC_ERR_CLIENT_REVOKED
1825+
return MethodData(val[0].val, _underlayer=pkt), val[1]
1826+
raise
18151827
elif pkt.errorCode.val == 69:
18161828
# KRB_AP_ERR_USER_TO_USER_REQUIRED
18171829
return KRB_TGT_REP(val[0].val, _underlayer=pkt), val[1]
@@ -2669,6 +2681,7 @@ def __init__(
26692681
armor_ticket=None,
26702682
armor_ticket_upn=None,
26712683
armor_ticket_skey=None,
2684+
key_list_req=[],
26722685
etypes=None,
26732686
port=88,
26742687
timeout=5,
@@ -2769,6 +2782,7 @@ def __init__(
27692782
self.armor_ticket = armor_ticket
27702783
self.armor_ticket_upn = armor_ticket_upn
27712784
self.armor_ticket_skey = armor_ticket_skey
2785+
self.key_list_req = key_list_req
27722786
self.renew = renew
27732787
self.additional_tickets = additional_tickets # U2U + S4U2Proxy
27742788
self.u2u = u2u # U2U
@@ -3130,6 +3144,17 @@ def tgs_req(self):
31303144
# "kdc-options field: MUST include the new cname-in-addl-tkt options flag"
31313145
kdc_req.kdcOptions.set(14, 1)
31323146

3147+
# [MS-KILE] 2.2.11 KERB-KEY-LIST-REQ
3148+
if self.key_list_req:
3149+
padata.append(
3150+
PADATA(
3151+
padataType=ASN1_INTEGER(161), # KERB-KEY-LIST-REQ
3152+
padataValue=KERB_KEY_LIST_REQ(keytypes=[
3153+
ASN1_INTEGER(x) for x in self.key_list_req
3154+
])
3155+
)
3156+
)
3157+
31333158
# 3. Build the AP-req inside a PA
31343159
apreq = KRB_AP_REQ(ticket=self.ticket, authenticator=EncryptedData())
31353160
pa_tgs_req = PADATA(

scapy/layers/ldap.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,7 @@ def connect(self, ip, port=None, use_ssl=False, sslcontext=None, timeout=5):
18111811
else:
18121812
port = 389
18131813
sock = socket.socket()
1814+
self.timeout = timeout
18141815
sock.settimeout(timeout)
18151816
if self.verb:
18161817
print(
@@ -2151,7 +2152,7 @@ def search(
21512152
filter: str = "",
21522153
scope=0,
21532154
derefAliases=0,
2154-
sizeLimit=3000,
2155+
sizeLimit=300000,
21552156
timeLimit=3000,
21562157
attrsOnly=0,
21572158
attributes: List[str] = [],
@@ -2202,7 +2203,7 @@ def search(
22022203
controlType="1.2.840.113556.1.4.319",
22032204
criticality=True,
22042205
controlValue=LDAP_realSearchControlValue(
2205-
size=500, # paging to 500 per 500
2206+
size=200, # paging to 200 per 200
22062207
cookie=cookie,
22072208
),
22082209
)
@@ -2211,7 +2212,7 @@ def search(
22112212
else []
22122213
)
22132214
),
2214-
timeout=3,
2215+
timeout=self.timeout,
22152216
)
22162217
if LDAP_SearchResponseResultDone not in resp:
22172218
resp.show()
@@ -2294,7 +2295,7 @@ def modify(
22942295
changes=changes,
22952296
),
22962297
controls=controls,
2297-
timeout=3,
2298+
timeout=self.timeout,
22982299
)
22992300
if (
23002301
LDAP_ModifyResponse not in resp.protocolOp
@@ -2341,7 +2342,7 @@ def add(
23412342
attributes=attributes,
23422343
),
23432344
controls=controls,
2344-
timeout=3,
2345+
timeout=self.timeout,
23452346
)
23462347
if LDAP_AddResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0:
23472348
raise LDAP_Exception(
@@ -2382,7 +2383,7 @@ def modifydn(
23822383
deleteoldrdn=deleteoldrdn,
23832384
),
23842385
controls=controls,
2385-
timeout=3,
2386+
timeout=self.timeout,
23862387
)
23872388
if (
23882389
LDAP_ModifyDNResponse not in resp.protocolOp

scapy/layers/msrpce/msdrsr.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,60 @@
88
"""
99

1010
import uuid
11+
from dataclasses import dataclass
12+
1113
from scapy.packet import Packet
1214
from scapy.fields import LEIntField, FlagsField, UUIDField, UTCTimeField
15+
from scapy.volatile import RandShort
1316

17+
from scapy.asn1.asn1 import ASN1_OID
1418
from scapy.layers.msrpce.raw.ms_drsr import UUID
1519
from scapy.layers.msrpce.raw.ms_drsr import * # noqa: F403,F401
1620

21+
# [MS-DRSR] sect 5.16.4 ATTRTYP-to-OID Conversion
22+
23+
24+
@dataclass
25+
class Prefix:
26+
prefixString: str
27+
prefixIndex: int
28+
29+
30+
def MakeAttid(t, o):
31+
"""
32+
MakeAttid per [MS-DRSR] sect 5.16.4
33+
"""
34+
ToBinary = lambda x: bytes(ASN1_OID(x))
35+
36+
lastValue = int(o.split(".")[-1])
37+
38+
# "convert the dotted form of OID into a BER encoded binary"
39+
binaryOID = ToBinary(o)
40+
41+
# "get the prefix of the OID"
42+
if lastValue < 128:
43+
oidPrefix = binaryOID[:-1]
44+
else:
45+
oidPrefix = binaryOID[:-2]
46+
47+
lowerWord = lastValue % 16384
48+
if lastValue >= 16384:
49+
lowerWord += 32768
50+
try:
51+
upperWord = next(x.prefixIndex for x in t if x.prefixString == oidPrefix)
52+
except StopIteration:
53+
# AddPrefixTableEntry
54+
upperWord = int(RandShort())
55+
t.append(
56+
Prefix(
57+
prefixString=oidPrefix,
58+
prefixIndex=upperWord,
59+
)
60+
)
61+
62+
return upperWord * 65536 + lowerWord
63+
64+
1765
# [MS-DRSR] sect 5.39 DRS_EXTENSIONS_INT
1866

1967

scapy/layers/msrpce/rpcclient.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ def connect_and_bind(
490490
ip,
491491
interface,
492492
port=None,
493+
timeout=5,
493494
smb_kwargs={},
494495
):
495496
"""
@@ -527,7 +528,7 @@ def connect_and_bind(
527528
else:
528529
return
529530
# 2. connect to the SMB server
530-
self.connect(ip, port=port, smb_kwargs=smb_kwargs)
531+
self.connect(ip, port=port, timeout=timeout, smb_kwargs=smb_kwargs)
531532
# 3. open the new named pipe
532533
self.open_smbpipe(pipename)
533534
# Bind in RPC

scapy/layers/smbclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ def __init__(
12291229
)
12301230
try:
12311231
# Wrap with SMB_SOCKET
1232-
self.smbsock = SMB_SOCKET(self.sock)
1232+
self.smbsock = SMB_SOCKET(self.sock, timeout=self.timeout)
12331233
# Wait for either the atmt to fail, or the smb_sock_ready to timeout
12341234
_t = time.time()
12351235
while True:

scapy/modules/ticketer.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,16 +358,25 @@ def open_file(self, fname):
358358
with open(self.fname, "rb") as fd:
359359
self.ccache = CCache(fd.read())
360360

361-
def save(self, fname=None):
361+
def save(self, fname=None, i=None):
362362
"""
363363
Save opened CCache file
364+
365+
:param fname: if provided, save to a specific file.
366+
:param i: if provided, only save the ticket n°i.
364367
"""
365368
if fname:
366369
self.fname = fname
367370
if not self.fname:
368371
raise ValueError("No file opened. Specify the 'fname' argument !")
372+
if i is not None:
373+
ccache = self.ccache.copy()
374+
ccache.credentials = [ccache.credentials[i]]
375+
data = bytes(ccache)
376+
else:
377+
data = bytes(self.ccache)
369378
with open(self.fname, "wb") as fd:
370-
return fd.write(bytes(self.ccache))
379+
return fd.write(data)
371380

372381
def show(self, utc=False):
373382
"""
@@ -502,6 +511,14 @@ def update_ticket(self, i, decTkt, resign=False, hash=None, kdc_hash=None):
502511
decTkt,
503512
)
504513

514+
def remove_krb(self, i):
515+
"""
516+
Remove a ticket from the store.
517+
518+
:param i: the ticket to remove.
519+
"""
520+
del self.ccache.credentials[i]
521+
505522
def import_krb(self, res, key=None, hash=None, _inplace=None):
506523
"""
507524
Import the result of krb_[tgs/as]_req or a Ticket into the CCache.
@@ -2151,12 +2168,20 @@ def request_st(
21512168
additional_tickets=[],
21522169
fast=False,
21532170
armor_with=None,
2171+
for_user=None,
2172+
s4u2proxy=None,
21542173
**kwargs,
21552174
):
21562175
"""
2157-
Request a Kerberos TS and add it to the local CCache using another ticket
2176+
Request a Kerberos TS and add it to the local CCache using another ticket.
21582177
2159-
:param i: the ticket/sessionkey to use in the TGS request
2178+
:param i: the index of the ticket/sessionkey to use in the TGS request.
2179+
:param spn: the SPN to request a ticket for.
2180+
:param armor_with: the index of the ticket/sessionkey to armor this request.
2181+
:param s4u2proxy: if an index, the index of the additional ticket to send along
2182+
a S4U2PROXY request. If True, it will use additional_tickets
2183+
as usual.
2184+
:param for_user: if provided, requests S4U2SELF for that user.
21602185
21612186
See :func:`~scapy.layers.kerberos.krb_tgs_req` for the the other parameters.
21622187
"""
@@ -2170,6 +2195,11 @@ def request_st(
21702195
armor_with
21712196
)
21722197

2198+
# If `s4u2proxy` is an index, get the ticket to armor with
2199+
if isinstance(s4u2proxy, int):
2200+
additional_tickets.append(self.export_krb(s4u2proxy)[0])
2201+
s4u2proxy = True
2202+
21732203
res = krb_tgs_req(
21742204
upn,
21752205
spn,
@@ -2180,6 +2210,7 @@ def request_st(
21802210
realm=realm,
21812211
additional_tickets=additional_tickets,
21822212
fast=fast,
2213+
for_user=for_user,
21832214
armor_ticket=armor_ticket,
21842215
armor_ticket_upn=armor_ticket_upn,
21852216
armor_ticket_skey=armor_ticket_skey,
@@ -2190,7 +2221,7 @@ def request_st(
21902221

21912222
self.import_krb(res)
21922223

2193-
def kpasswdset(self, i, targetupn=None):
2224+
def kpasswdset(self, i, targetupn=None, newpassword=None):
21942225
"""
21952226
Use kpasswd in 'Set Password' mode to set the password of an account.
21962227
@@ -2203,6 +2234,7 @@ def kpasswdset(self, i, targetupn=None):
22032234
setpassword=True,
22042235
ticket=ticket,
22052236
key=sessionkey,
2237+
newpassword=newpassword,
22062238
)
22072239

22082240
def renew(self, i, ip=None, additional_tickets=[], **kwargs):

0 commit comments

Comments
 (0)