Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/ldap.py: 43%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SPDX-License-Identifier: GPL-2.0-only
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Copyright (C) Gabriel Potter <gabriel[]potter[]fr>
6"""
7LDAP
9RFC 1777 - LDAP v2
10RFC 4511 - LDAP v3
12Note: to mimic Microsoft Windows LDAP packets, you must set::
14 conf.ASN1_default_long_size = 4
16.. note::
17 You will find more complete documentation for this layer over at
18 `LDAP <https://scapy.readthedocs.io/en/latest/layers/ldap.html>`_
19"""
21import collections
22import re
23import socket
24import ssl
25import string
26import struct
27import uuid
29from scapy.arch import get_if_addr
30from scapy.ansmachine import AnsweringMachine
31from scapy.asn1.asn1 import (
32 ASN1_BOOLEAN,
33 ASN1_Class,
34 ASN1_Codecs,
35 ASN1_ENUMERATED,
36 ASN1_INTEGER,
37 ASN1_STRING,
38)
39from scapy.asn1.ber import (
40 BER_Decoding_Error,
41 BER_id_dec,
42 BER_len_dec,
43 BERcodec_STRING,
44)
45from scapy.asn1fields import (
46 ASN1F_badsequence,
47 ASN1F_BOOLEAN,
48 ASN1F_CHOICE,
49 ASN1F_ENUMERATED,
50 ASN1F_FLAGS,
51 ASN1F_INTEGER,
52 ASN1F_NULL,
53 ASN1F_optional,
54 ASN1F_PACKET,
55 ASN1F_SEQUENCE_OF,
56 ASN1F_SEQUENCE,
57 ASN1F_SET_OF,
58 ASN1F_STRING_PacketField,
59 ASN1F_STRING,
60)
61from scapy.asn1packet import ASN1_Packet
62from scapy.config import conf
63from scapy.compat import StrEnum
64from scapy.consts import WINDOWS
65from scapy.error import log_runtime
66from scapy.fields import (
67 FieldLenField,
68 FlagsField,
69 ThreeBytesField,
70)
71from scapy.packet import (
72 Packet,
73 bind_bottom_up,
74 bind_layers,
75)
76from scapy.sendrecv import send
77from scapy.supersocket import (
78 SimpleSocket,
79 StreamSocket,
80 SSLStreamSocket,
81)
83from scapy.layers.dns import dns_resolve
84from scapy.layers.inet import IP, TCP, UDP
85from scapy.layers.inet6 import IPv6
86from scapy.layers.gssapi import (
87 _GSSAPI_Field,
88 ChannelBindingType,
89 GSS_C_FLAGS,
90 GSS_C_NO_CHANNEL_BINDINGS,
91 GSS_QOP_REQ_FLAGS,
92 GSS_S_COMPLETE,
93 GSS_S_CONTINUE_NEEDED,
94 GSSAPI_BLOB_SIGNATURE,
95 GSSAPI_BLOB,
96 GssChannelBindings,
97 SSP,
98)
99from scapy.layers.netbios import NBTDatagram
100from scapy.layers.smb import (
101 NETLOGON,
102 NETLOGON_SAM_LOGON_RESPONSE_EX,
103)
104from scapy.layers.windows.erref import STATUS_ERREF
106# Typing imports
107from typing import (
108 Any,
109 Dict,
110 List,
111 Optional,
112 Union,
113)
115# Elements of protocol
116# https://datatracker.ietf.org/doc/html/rfc1777#section-4
118LDAPString = ASN1F_STRING
119LDAPOID = ASN1F_STRING
120LDAPDN = LDAPString
121RelativeLDAPDN = LDAPString
122AttributeType = LDAPString
123AttributeValue = ASN1F_STRING
124URI = LDAPString
127class AttributeValueAssertion(ASN1_Packet):
128 ASN1_codec = ASN1_Codecs.BER
129 ASN1_root = ASN1F_SEQUENCE(
130 AttributeType("attributeType", "organizationName"),
131 AttributeValue("attributeValue", ""),
132 )
135class LDAPReferral(ASN1_Packet):
136 ASN1_codec = ASN1_Codecs.BER
137 ASN1_root = LDAPString("uri", "")
140LDAPResult = (
141 ASN1F_ENUMERATED(
142 "resultCode",
143 0,
144 {
145 0: "success",
146 1: "operationsError",
147 2: "protocolError",
148 3: "timeLimitExceeded",
149 4: "sizeLimitExceeded",
150 5: "compareFalse",
151 6: "compareTrue",
152 7: "authMethodNotSupported",
153 8: "strongAuthRequired",
154 10: "referral",
155 11: "adminLimitExceeded",
156 14: "saslBindInProgress",
157 16: "noSuchAttribute",
158 17: "undefinedAttributeType",
159 18: "inappropriateMatching",
160 19: "constraintViolation",
161 20: "attributeOrValueExists",
162 21: "invalidAttributeSyntax",
163 32: "noSuchObject",
164 33: "aliasProblem",
165 34: "invalidDNSyntax",
166 35: "isLeaf",
167 36: "aliasDereferencingProblem",
168 48: "inappropriateAuthentication",
169 49: "invalidCredentials",
170 50: "insufficientAccessRights",
171 51: "busy",
172 52: "unavailable",
173 53: "unwillingToPerform",
174 54: "loopDetect",
175 64: "namingViolation",
176 65: "objectClassViolation",
177 66: "notAllowedOnNonLeaf",
178 67: "notAllowedOnRDN",
179 68: "entryAlreadyExists",
180 69: "objectClassModsProhibited",
181 70: "resultsTooLarge", # CLDAP
182 80: "other",
183 },
184 ),
185 LDAPDN("matchedDN", ""),
186 LDAPString("diagnosticMessage", ""),
187 # LDAP v3 only
188 ASN1F_optional(ASN1F_SEQUENCE_OF("referral", [], LDAPReferral, implicit_tag=0xA3)),
189)
192# ldap APPLICATION
195class ASN1_Class_LDAP(ASN1_Class):
196 name = "LDAP"
197 # APPLICATION + CONSTRUCTED = 0x40 | 0x20
198 BindRequest = 0x60
199 BindResponse = 0x61
200 UnbindRequest = 0x42 # not constructed
201 SearchRequest = 0x63
202 SearchResultEntry = 0x64
203 SearchResultDone = 0x65
204 ModifyRequest = 0x66
205 ModifyResponse = 0x67
206 AddRequest = 0x68
207 AddResponse = 0x69
208 DelRequest = 0x4A # not constructed
209 DelResponse = 0x6B
210 ModifyDNRequest = 0x6C
211 ModifyDNResponse = 0x6D
212 CompareRequest = 0x6E
213 CompareResponse = 0x7F
214 AbandonRequest = 0x50 # application + primitive
215 SearchResultReference = 0x73
216 ExtendedRequest = 0x77
217 ExtendedResponse = 0x78
220# Bind operation
221# https://datatracker.ietf.org/doc/html/rfc4511#section-4.2
224class ASN1_Class_LDAP_Authentication(ASN1_Class):
225 name = "LDAP Authentication"
226 # CONTEXT-SPECIFIC = 0x80
227 simple = 0x80
228 krbv42LDAP = 0x81
229 krbv42DSA = 0x82
230 sasl = 0xA3 # CONTEXT-SPECIFIC | CONSTRUCTED
231 # [MS-ADTS] sect 5.1.1.1
232 sicilyPackageDiscovery = 0x89
233 sicilyNegotiate = 0x8A
234 sicilyResponse = 0x8B
237# simple
238class LDAP_Authentication_simple(ASN1_STRING):
239 tag = ASN1_Class_LDAP_Authentication.simple
242class BERcodec_LDAP_Authentication_simple(BERcodec_STRING):
243 tag = ASN1_Class_LDAP_Authentication.simple
246class ASN1F_LDAP_Authentication_simple(ASN1F_STRING):
247 ASN1_tag = ASN1_Class_LDAP_Authentication.simple
250# krbv42LDAP
251class LDAP_Authentication_krbv42LDAP(ASN1_STRING):
252 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
255class BERcodec_LDAP_Authentication_krbv42LDAP(BERcodec_STRING):
256 tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
259class ASN1F_LDAP_Authentication_krbv42LDAP(ASN1F_STRING):
260 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42LDAP
263# krbv42DSA
264class LDAP_Authentication_krbv42DSA(ASN1_STRING):
265 tag = ASN1_Class_LDAP_Authentication.krbv42DSA
268class BERcodec_LDAP_Authentication_krbv42DSA(BERcodec_STRING):
269 tag = ASN1_Class_LDAP_Authentication.krbv42DSA
272class ASN1F_LDAP_Authentication_krbv42DSA(ASN1F_STRING):
273 ASN1_tag = ASN1_Class_LDAP_Authentication.krbv42DSA
276# sicilyPackageDiscovery
277class LDAP_Authentication_sicilyPackageDiscovery(ASN1_STRING):
278 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
281class BERcodec_LDAP_Authentication_sicilyPackageDiscovery(BERcodec_STRING):
282 tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
285class ASN1F_LDAP_Authentication_sicilyPackageDiscovery(ASN1F_STRING):
286 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyPackageDiscovery
289# sicilyNegotiate
290class LDAP_Authentication_sicilyNegotiate(ASN1_STRING):
291 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
294class BERcodec_LDAP_Authentication_sicilyNegotiate(BERcodec_STRING):
295 tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
298class ASN1F_LDAP_Authentication_sicilyNegotiate(ASN1F_STRING):
299 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyNegotiate
302# sicilyResponse
303class LDAP_Authentication_sicilyResponse(ASN1_STRING):
304 tag = ASN1_Class_LDAP_Authentication.sicilyResponse
307class BERcodec_LDAP_Authentication_sicilyResponse(BERcodec_STRING):
308 tag = ASN1_Class_LDAP_Authentication.sicilyResponse
311class ASN1F_LDAP_Authentication_sicilyResponse(ASN1F_STRING):
312 ASN1_tag = ASN1_Class_LDAP_Authentication.sicilyResponse
315_SASL_MECHANISMS = {b"GSS-SPNEGO": GSSAPI_BLOB, b"GSSAPI": GSSAPI_BLOB}
318class _SaslCredentialsField(ASN1F_STRING_PacketField):
319 def m2i(self, pkt, s):
320 val = super(_SaslCredentialsField, self).m2i(pkt, s)
321 if not val[0].val:
322 return val
323 if pkt.mechanism.val in _SASL_MECHANISMS:
324 return (
325 _SASL_MECHANISMS[pkt.mechanism.val](val[0].val, _underlayer=pkt),
326 val[1],
327 )
328 return val
331class LDAP_Authentication_SaslCredentials(ASN1_Packet):
332 ASN1_codec = ASN1_Codecs.BER
333 ASN1_root = ASN1F_SEQUENCE(
334 LDAPString("mechanism", ""),
335 ASN1F_optional(
336 _SaslCredentialsField("credentials", ""),
337 ),
338 implicit_tag=ASN1_Class_LDAP_Authentication.sasl,
339 )
342class LDAP_BindRequest(ASN1_Packet):
343 ASN1_codec = ASN1_Codecs.BER
344 ASN1_root = ASN1F_SEQUENCE(
345 ASN1F_INTEGER("version", 3),
346 LDAPDN("bind_name", ""),
347 ASN1F_CHOICE(
348 "authentication",
349 None,
350 ASN1F_LDAP_Authentication_simple,
351 ASN1F_LDAP_Authentication_krbv42LDAP,
352 ASN1F_LDAP_Authentication_krbv42DSA,
353 LDAP_Authentication_SaslCredentials,
354 ),
355 implicit_tag=ASN1_Class_LDAP.BindRequest,
356 )
359class LDAP_BindResponse(ASN1_Packet):
360 ASN1_codec = ASN1_Codecs.BER
361 ASN1_root = ASN1F_SEQUENCE(
362 *(
363 LDAPResult
364 + (
365 ASN1F_optional(
366 # For GSSAPI, the response is wrapped in
367 # LDAP_Authentication_SaslCredentials
368 ASN1F_STRING("serverSaslCredsWrap", "", implicit_tag=0xA7),
369 ),
370 ASN1F_optional(
371 ASN1F_STRING("serverSaslCreds", "", implicit_tag=0x87),
372 ),
373 )
374 ),
375 implicit_tag=ASN1_Class_LDAP.BindResponse,
376 )
378 @property
379 def serverCreds(self):
380 """
381 serverCreds field in SicilyBindResponse
382 """
383 return self.matchedDN.val
385 @serverCreds.setter
386 def serverCreds(self, val):
387 """
388 serverCreds field in SicilyBindResponse
389 """
390 self.matchedDN = ASN1_STRING(val)
392 @property
393 def serverSaslCredsData(self):
394 """
395 Get serverSaslCreds or serverSaslCredsWrap depending on what's available
396 """
397 if self.serverSaslCredsWrap and self.serverSaslCredsWrap.val:
398 wrap = LDAP_Authentication_SaslCredentials(self.serverSaslCredsWrap.val)
399 val = wrap.credentials
400 if isinstance(val, ASN1_STRING):
401 return val.val
402 return bytes(val)
403 elif self.serverSaslCreds and self.serverSaslCreds.val:
404 return self.serverSaslCreds.val
405 else:
406 return None
409# Unbind operation
410# https://datatracker.ietf.org/doc/html/rfc4511#section-4.3
413class LDAP_UnbindRequest(ASN1_Packet):
414 ASN1_codec = ASN1_Codecs.BER
415 ASN1_root = ASN1F_SEQUENCE(
416 ASN1F_NULL("info", 0),
417 implicit_tag=ASN1_Class_LDAP.UnbindRequest,
418 )
421# Search operation
422# https://datatracker.ietf.org/doc/html/rfc4511#section-4.5
425class LDAP_SubstringFilterInitial(ASN1_Packet):
426 ASN1_codec = ASN1_Codecs.BER
427 ASN1_root = LDAPString("val", "")
430class LDAP_SubstringFilterAny(ASN1_Packet):
431 ASN1_codec = ASN1_Codecs.BER
432 ASN1_root = LDAPString("val", "")
435class LDAP_SubstringFilterFinal(ASN1_Packet):
436 ASN1_codec = ASN1_Codecs.BER
437 ASN1_root = LDAPString("val", "")
440class LDAP_SubstringFilterStr(ASN1_Packet):
441 ASN1_codec = ASN1_Codecs.BER
442 ASN1_root = ASN1F_CHOICE(
443 "str",
444 ASN1_STRING(""),
445 ASN1F_PACKET(
446 "initial",
447 LDAP_SubstringFilterInitial(),
448 LDAP_SubstringFilterInitial,
449 implicit_tag=0x80,
450 ),
451 ASN1F_PACKET(
452 "any", LDAP_SubstringFilterAny(), LDAP_SubstringFilterAny, implicit_tag=0x81
453 ),
454 ASN1F_PACKET(
455 "final",
456 LDAP_SubstringFilterFinal(),
457 LDAP_SubstringFilterFinal,
458 implicit_tag=0x82,
459 ),
460 )
463class LDAP_SubstringFilter(ASN1_Packet):
464 ASN1_codec = ASN1_Codecs.BER
465 ASN1_root = ASN1F_SEQUENCE(
466 AttributeType("type", ""),
467 ASN1F_SEQUENCE_OF("filters", [], LDAP_SubstringFilterStr),
468 )
471_LDAP_Filter = lambda *args, **kwargs: LDAP_Filter(*args, **kwargs)
474class LDAP_FilterAnd(ASN1_Packet):
475 ASN1_codec = ASN1_Codecs.BER
476 ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter)
479class LDAP_FilterOr(ASN1_Packet):
480 ASN1_codec = ASN1_Codecs.BER
481 ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter)
484class LDAP_FilterNot(ASN1_Packet):
485 ASN1_codec = ASN1_Codecs.BER
486 ASN1_root = ASN1F_SEQUENCE(
487 ASN1F_PACKET("val", None, None, next_cls_cb=lambda *args, **kwargs: LDAP_Filter)
488 )
491class LDAP_FilterPresent(ASN1_Packet):
492 ASN1_codec = ASN1_Codecs.BER
493 ASN1_root = AttributeType("present", "objectClass")
496class LDAP_FilterEqual(ASN1_Packet):
497 ASN1_codec = ASN1_Codecs.BER
498 ASN1_root = AttributeValueAssertion.ASN1_root
501class LDAP_FilterGreaterOrEqual(ASN1_Packet):
502 ASN1_codec = ASN1_Codecs.BER
503 ASN1_root = AttributeValueAssertion.ASN1_root
506class LDAP_FilterLessOrEqual(ASN1_Packet):
507 ASN1_codec = ASN1_Codecs.BER
508 ASN1_root = AttributeValueAssertion.ASN1_root
511class LDAP_FilterApproxMatch(ASN1_Packet):
512 ASN1_codec = ASN1_Codecs.BER
513 ASN1_root = AttributeValueAssertion.ASN1_root
516class LDAP_FilterExtensibleMatch(ASN1_Packet):
517 ASN1_codec = ASN1_Codecs.BER
518 ASN1_root = ASN1F_SEQUENCE(
519 ASN1F_optional(
520 LDAPString("matchingRule", "", implicit_tag=0x81),
521 ),
522 ASN1F_optional(
523 LDAPString("type", "", implicit_tag=0x81),
524 ),
525 AttributeValue("matchValue", "", implicit_tag=0x82),
526 ASN1F_BOOLEAN("dnAttributes", False, implicit_tag=0x84),
527 )
530class ASN1_Class_LDAP_Filter(ASN1_Class):
531 name = "LDAP Filter"
532 # CONTEXT-SPECIFIC + CONSTRUCTED = 0x80 | 0x20
533 And = 0xA0
534 Or = 0xA1
535 Not = 0xA2
536 EqualityMatch = 0xA3
537 Substrings = 0xA4
538 GreaterOrEqual = 0xA5
539 LessOrEqual = 0xA6
540 Present = 0x87 # not constructed
541 ApproxMatch = 0xA8
542 ExtensibleMatch = 0xA9
545class LDAP_Filter(ASN1_Packet):
546 ASN1_codec = ASN1_Codecs.BER
547 ASN1_root = ASN1F_CHOICE(
548 "filter",
549 LDAP_FilterPresent(),
550 ASN1F_PACKET(
551 "and_", None, LDAP_FilterAnd, implicit_tag=ASN1_Class_LDAP_Filter.And
552 ),
553 ASN1F_PACKET(
554 "or_", None, LDAP_FilterOr, implicit_tag=ASN1_Class_LDAP_Filter.Or
555 ),
556 ASN1F_PACKET(
557 "not_", None, LDAP_FilterNot, implicit_tag=ASN1_Class_LDAP_Filter.Not
558 ),
559 ASN1F_PACKET(
560 "equalityMatch",
561 None,
562 LDAP_FilterEqual,
563 implicit_tag=ASN1_Class_LDAP_Filter.EqualityMatch,
564 ),
565 ASN1F_PACKET(
566 "substrings",
567 None,
568 LDAP_SubstringFilter,
569 implicit_tag=ASN1_Class_LDAP_Filter.Substrings,
570 ),
571 ASN1F_PACKET(
572 "greaterOrEqual",
573 None,
574 LDAP_FilterGreaterOrEqual,
575 implicit_tag=ASN1_Class_LDAP_Filter.GreaterOrEqual,
576 ),
577 ASN1F_PACKET(
578 "lessOrEqual",
579 None,
580 LDAP_FilterLessOrEqual,
581 implicit_tag=ASN1_Class_LDAP_Filter.LessOrEqual,
582 ),
583 ASN1F_PACKET(
584 "present",
585 None,
586 LDAP_FilterPresent,
587 implicit_tag=ASN1_Class_LDAP_Filter.Present,
588 ),
589 ASN1F_PACKET(
590 "approxMatch",
591 None,
592 LDAP_FilterApproxMatch,
593 implicit_tag=ASN1_Class_LDAP_Filter.ApproxMatch,
594 ),
595 ASN1F_PACKET(
596 "extensibleMatch",
597 None,
598 LDAP_FilterExtensibleMatch,
599 implicit_tag=ASN1_Class_LDAP_Filter.ExtensibleMatch,
600 ),
601 )
603 @staticmethod
604 def from_rfc2254_string(filter: str):
605 """
606 Convert a RFC-2254 filter to LDAP_Filter
607 """
608 # Note: this code is very dumb to be readable.
609 _lerr = "Invalid LDAP filter string: "
610 if filter.lstrip()[0] != "(":
611 filter = "(%s)" % filter
613 # 1. Cheap lexer.
614 tokens = []
615 cur = tokens
616 backtrack = []
617 filterlen = len(filter)
618 i = 0
619 while i < filterlen:
620 c = filter[i]
621 i += 1
622 if c in [" ", "\t", "\n"]:
623 # skip spaces
624 continue
625 elif c == "(":
626 # enclosure
627 cur.append([])
628 backtrack.append(cur)
629 cur = cur[-1]
630 elif c == ")":
631 # end of enclosure
632 if not backtrack:
633 raise ValueError(_lerr + "parenthesis unmatched.")
634 cur = backtrack.pop(-1)
635 elif c in "&|!":
636 # and / or / not
637 cur.append(c)
638 elif c in "=":
639 # filtertype
640 if cur[-1] in "~><:":
641 cur[-1] += c
642 continue
643 cur.append(c)
644 elif c in "~><":
645 # comparisons
646 cur.append(c)
647 elif c == ":":
648 # extensible
649 cur.append(c)
650 elif c == "*":
651 # substring
652 cur.append(c)
653 else:
654 # value
655 v = ""
656 for x in filter[i - 1 :]:
657 if x in "():!|&~<>=*":
658 break
659 v += x
660 if not v:
661 raise ValueError(_lerr + "critical failure (impossible).")
662 i += len(v) - 1
663 cur.append(v)
665 # Check that parenthesis were closed
666 if backtrack:
667 raise ValueError(_lerr + "parenthesis unmatched.")
669 # LDAP filters must have an empty enclosure ()
670 tokens = tokens[0]
672 # 2. Cheap grammar parser.
673 # Doing it recursively is trivial.
674 def _getfld(x):
675 if not x:
676 raise ValueError(_lerr + "empty enclosure.")
677 elif len(x) == 1 and isinstance(x[0], list):
678 # useless enclosure
679 return _getfld(x[0])
680 elif x[0] in "&|":
681 # multinary operator
682 if len(x) < 3:
683 raise ValueError(_lerr + "bad use of multinary operator.")
684 return (LDAP_FilterAnd if x[0] == "&" else LDAP_FilterOr)(
685 vals=[LDAP_Filter(filter=_getfld(y)) for y in x[1:]]
686 )
687 elif x[0] == "!":
688 # unary operator
689 if len(x) != 2:
690 raise ValueError(_lerr + "bad use of unary operator.")
691 return LDAP_FilterNot(
692 val=LDAP_Filter(filter=_getfld(x[1])),
693 )
694 elif "=" in x and "*" in x:
695 # substring
696 if len(x) < 3 or x[1] != "=":
697 raise ValueError(_lerr + "bad use of substring.")
698 return LDAP_SubstringFilter(
699 type=ASN1_STRING(x[0].strip()),
700 filters=[
701 LDAP_SubstringFilterStr(
702 str=(
703 LDAP_SubstringFilterFinal
704 if i == (len(x) - 3)
705 else (
706 LDAP_SubstringFilterInitial
707 if i == 0
708 else LDAP_SubstringFilterAny
709 )
710 )(val=ASN1_STRING(y))
711 )
712 for i, y in enumerate(x[2:])
713 if y != "*"
714 ],
715 )
716 elif ":=" in x:
717 # extensible
718 raise NotImplementedError("Extensible not implemented.")
719 elif any(y in ["<=", ">=", "~=", "="] for y in x):
720 # simple
721 if len(x) != 3 or "=" not in x[1]:
722 raise ValueError(_lerr + "bad use of comparison.")
723 if x[2] == "*":
724 return LDAP_FilterPresent(present=ASN1_STRING(x[0]))
725 return (
726 LDAP_FilterLessOrEqual
727 if "<=" in x
728 else (
729 LDAP_FilterGreaterOrEqual
730 if ">=" in x
731 else LDAP_FilterApproxMatch if "~=" in x else LDAP_FilterEqual
732 )
733 )(
734 attributeType=ASN1_STRING(x[0].strip()),
735 attributeValue=ASN1_STRING(x[2]),
736 )
737 else:
738 raise ValueError(_lerr + "invalid filter.")
740 return LDAP_Filter(filter=_getfld(tokens))
743class LDAP_SearchRequestAttribute(ASN1_Packet):
744 ASN1_codec = ASN1_Codecs.BER
745 ASN1_root = AttributeType("type", "")
748class LDAP_SearchRequest(ASN1_Packet):
749 ASN1_codec = ASN1_Codecs.BER
750 ASN1_root = ASN1F_SEQUENCE(
751 LDAPDN("baseObject", ""),
752 ASN1F_ENUMERATED(
753 "scope", 0, {0: "baseObject", 1: "singleLevel", 2: "wholeSubtree"}
754 ),
755 ASN1F_ENUMERATED(
756 "derefAliases",
757 0,
758 {
759 0: "neverDerefAliases",
760 1: "derefInSearching",
761 2: "derefFindingBaseObj",
762 3: "derefAlways",
763 },
764 ),
765 ASN1F_INTEGER("sizeLimit", 0),
766 ASN1F_INTEGER("timeLimit", 0),
767 ASN1F_BOOLEAN("attrsOnly", False),
768 ASN1F_PACKET("filter", LDAP_Filter(), LDAP_Filter),
769 ASN1F_SEQUENCE_OF("attributes", [], LDAP_SearchRequestAttribute),
770 implicit_tag=ASN1_Class_LDAP.SearchRequest,
771 )
774class LDAP_AttributeValue(ASN1_Packet):
775 ASN1_codec = ASN1_Codecs.BER
776 ASN1_root = AttributeValue("value", "")
779class LDAP_PartialAttribute(ASN1_Packet):
780 ASN1_codec = ASN1_Codecs.BER
781 ASN1_root = ASN1F_SEQUENCE(
782 AttributeType("type", ""),
783 ASN1F_SET_OF("values", [], LDAP_AttributeValue),
784 )
787class LDAP_SearchResponseEntry(ASN1_Packet):
788 ASN1_codec = ASN1_Codecs.BER
789 ASN1_root = ASN1F_SEQUENCE(
790 LDAPDN("objectName", ""),
791 ASN1F_SEQUENCE_OF(
792 "attributes",
793 LDAP_PartialAttribute(),
794 LDAP_PartialAttribute,
795 ),
796 implicit_tag=ASN1_Class_LDAP.SearchResultEntry,
797 )
800class LDAP_SearchResponseResultDone(ASN1_Packet):
801 ASN1_codec = ASN1_Codecs.BER
802 ASN1_root = ASN1F_SEQUENCE(
803 *LDAPResult,
804 implicit_tag=ASN1_Class_LDAP.SearchResultDone,
805 )
808class LDAP_SearchResponseReference(ASN1_Packet):
809 ASN1_codec = ASN1_Codecs.BER
810 ASN1_root = ASN1F_SEQUENCE_OF(
811 "uris",
812 [],
813 URI,
814 implicit_tag=ASN1_Class_LDAP.SearchResultReference,
815 )
818# Modify Operation
819# https://datatracker.ietf.org/doc/html/rfc4511#section-4.6
822class LDAP_ModifyRequestChange(ASN1_Packet):
823 ASN1_codec = ASN1_Codecs.BER
824 ASN1_root = ASN1F_SEQUENCE(
825 ASN1F_ENUMERATED(
826 "operation",
827 0,
828 {
829 0: "add",
830 1: "delete",
831 2: "replace",
832 },
833 ),
834 ASN1F_PACKET("modification", LDAP_PartialAttribute(), LDAP_PartialAttribute),
835 )
838class LDAP_ModifyRequest(ASN1_Packet):
839 ASN1_codec = ASN1_Codecs.BER
840 ASN1_root = ASN1F_SEQUENCE(
841 LDAPDN("object", ""),
842 ASN1F_SEQUENCE_OF("changes", [], LDAP_ModifyRequestChange),
843 implicit_tag=ASN1_Class_LDAP.ModifyRequest,
844 )
847class LDAP_ModifyResponse(ASN1_Packet):
848 ASN1_codec = ASN1_Codecs.BER
849 ASN1_root = ASN1F_SEQUENCE(
850 *LDAPResult,
851 implicit_tag=ASN1_Class_LDAP.ModifyResponse,
852 )
855# Add Operation
856# https://datatracker.ietf.org/doc/html/rfc4511#section-4.7
859class LDAP_Attribute(ASN1_Packet):
860 ASN1_codec = ASN1_Codecs.BER
861 ASN1_root = LDAP_PartialAttribute.ASN1_root
864class LDAP_AddRequest(ASN1_Packet):
865 ASN1_codec = ASN1_Codecs.BER
866 ASN1_root = ASN1F_SEQUENCE(
867 LDAPDN("entry", ""),
868 ASN1F_SEQUENCE_OF(
869 "attributes",
870 LDAP_Attribute(),
871 LDAP_Attribute,
872 ),
873 implicit_tag=ASN1_Class_LDAP.AddRequest,
874 )
877class LDAP_AddResponse(ASN1_Packet):
878 ASN1_codec = ASN1_Codecs.BER
879 ASN1_root = ASN1F_SEQUENCE(
880 *LDAPResult,
881 implicit_tag=ASN1_Class_LDAP.AddResponse,
882 )
885# Delete Operation
886# https://datatracker.ietf.org/doc/html/rfc4511#section-4.8
889class LDAP_DelRequest(ASN1_Packet):
890 ASN1_codec = ASN1_Codecs.BER
891 ASN1_root = LDAPDN(
892 "entry",
893 "",
894 implicit_tag=ASN1_Class_LDAP.DelRequest,
895 )
898class LDAP_DelResponse(ASN1_Packet):
899 ASN1_codec = ASN1_Codecs.BER
900 ASN1_root = ASN1F_SEQUENCE(
901 *LDAPResult,
902 implicit_tag=ASN1_Class_LDAP.DelResponse,
903 )
906# Modify DN Operation
907# https://datatracker.ietf.org/doc/html/rfc4511#section-4.9
910class LDAP_ModifyDNRequest(ASN1_Packet):
911 ASN1_codec = ASN1_Codecs.BER
912 ASN1_root = ASN1F_SEQUENCE(
913 LDAPDN("entry", ""),
914 LDAPDN("newrdn", ""),
915 ASN1F_BOOLEAN("deleteoldrdn", ASN1_BOOLEAN(False)),
916 ASN1F_optional(LDAPDN("newSuperior", None, implicit_tag=0xA0)),
917 implicit_tag=ASN1_Class_LDAP.ModifyDNRequest,
918 )
921class LDAP_ModifyDNResponse(ASN1_Packet):
922 ASN1_codec = ASN1_Codecs.BER
923 ASN1_root = ASN1F_SEQUENCE(
924 *LDAPResult,
925 implicit_tag=ASN1_Class_LDAP.ModifyDNResponse,
926 )
929# Abandon Operation
930# https://datatracker.ietf.org/doc/html/rfc4511#section-4.11
933class LDAP_AbandonRequest(ASN1_Packet):
934 ASN1_codec = ASN1_Codecs.BER
935 ASN1_root = ASN1F_SEQUENCE(
936 ASN1F_INTEGER("messageID", 0),
937 implicit_tag=ASN1_Class_LDAP.AbandonRequest,
938 )
941# LDAP v3
943# RFC 4511 sect 4.12 - Extended Operation
946class LDAP_ExtendedResponse(ASN1_Packet):
947 ASN1_codec = ASN1_Codecs.BER
948 ASN1_root = ASN1F_SEQUENCE(
949 *(
950 LDAPResult
951 + (
952 ASN1F_optional(LDAPOID("responseName", None, implicit_tag=0x8A)),
953 ASN1F_optional(ASN1F_STRING("responseValue", None, implicit_tag=0x8B)),
954 )
955 ),
956 implicit_tag=ASN1_Class_LDAP.ExtendedResponse,
957 )
959 def do_dissect(self, x):
960 # Note: Windows builds this packet with a buggy sequence size, that does not
961 # include the optional fields. Do another pass of dissection on the optionals.
962 s = super(LDAP_ExtendedResponse, self).do_dissect(x)
963 if not s:
964 return s
965 for obj in self.ASN1_root.seq[-2:]: # only on the 2 optional fields
966 try:
967 s = obj.dissect(self, s)
968 except ASN1F_badsequence:
969 break
970 return s
973# RFC 4511 sect 4.1.11
975_LDAP_CONTROLS = {}
978class _ControlValue_Field(ASN1F_STRING_PacketField):
979 def m2i(self, pkt, s):
980 val = super(_ControlValue_Field, self).m2i(pkt, s)
981 if not val[0].val:
982 return val
983 controlType = pkt.controlType.val.decode()
984 if controlType in _LDAP_CONTROLS:
985 return (
986 _LDAP_CONTROLS[controlType](val[0].val, _underlayer=pkt),
987 val[1],
988 )
989 return val
992class LDAP_Control(ASN1_Packet):
993 ASN1_codec = ASN1_Codecs.BER
994 ASN1_root = ASN1F_SEQUENCE(
995 LDAPOID("controlType", ""),
996 ASN1F_optional(
997 ASN1F_BOOLEAN("criticality", False),
998 ),
999 ASN1F_optional(_ControlValue_Field("controlValue", "")),
1000 )
1003# RFC 2696 - LDAP Control Extension for Simple Paged Results Manipulation
1006class LDAP_realSearchControlValue(ASN1_Packet):
1007 ASN1_codec = ASN1_Codecs.BER
1008 ASN1_root = ASN1F_SEQUENCE(
1009 ASN1F_INTEGER("size", 0),
1010 ASN1F_STRING("cookie", ""),
1011 )
1014_LDAP_CONTROLS["1.2.840.113556.1.4.319"] = LDAP_realSearchControlValue
1017# [MS-ADTS]
1020class LDAP_serverSDFlagsControl(ASN1_Packet):
1021 ASN1_codec = ASN1_Codecs.BER
1022 ASN1_root = ASN1F_SEQUENCE(
1023 ASN1F_FLAGS(
1024 "flags",
1025 None,
1026 [
1027 "OWNER",
1028 "GROUP",
1029 "DACL",
1030 "SACL",
1031 ],
1032 )
1033 )
1036_LDAP_CONTROLS["1.2.840.113556.1.4.801"] = LDAP_serverSDFlagsControl
1039# LDAP main class
1042class LDAP(ASN1_Packet):
1043 ASN1_codec = ASN1_Codecs.BER
1044 ASN1_root = ASN1F_SEQUENCE(
1045 ASN1F_INTEGER("messageID", 0),
1046 ASN1F_CHOICE(
1047 "protocolOp",
1048 LDAP_SearchRequest(),
1049 LDAP_BindRequest,
1050 LDAP_BindResponse,
1051 LDAP_SearchRequest,
1052 LDAP_SearchResponseEntry,
1053 LDAP_SearchResponseResultDone,
1054 LDAP_AbandonRequest,
1055 LDAP_SearchResponseReference,
1056 LDAP_ModifyRequest,
1057 LDAP_ModifyResponse,
1058 LDAP_AddRequest,
1059 LDAP_AddResponse,
1060 LDAP_DelRequest,
1061 LDAP_DelResponse,
1062 LDAP_ModifyDNRequest,
1063 LDAP_ModifyDNResponse,
1064 LDAP_UnbindRequest,
1065 LDAP_ExtendedResponse,
1066 ),
1067 # LDAP v3 only
1068 ASN1F_optional(
1069 ASN1F_SEQUENCE_OF("Controls", None, LDAP_Control, implicit_tag=0xA0)
1070 ),
1071 )
1073 show_indent = 0
1075 @classmethod
1076 def dispatch_hook(cls, _pkt=None, *args, **kargs):
1077 if _pkt and len(_pkt) >= 4:
1078 # Heuristic to detect SASL_Buffer
1079 if _pkt[0] != 0x30:
1080 if struct.unpack("!I", _pkt[:4])[0] + 4 == len(_pkt):
1081 return LDAP_SASL_Buffer
1082 return conf.raw_layer
1083 return cls
1085 @classmethod
1086 def tcp_reassemble(cls, data, *args, **kwargs):
1087 if len(data) < 4:
1088 return None
1089 # For LDAP, we would prefer to have the entire LDAP response
1090 # (multiple LDAP concatenated) in one go, to stay consistent with
1091 # what you get when using SASL.
1092 remaining = data
1093 while remaining:
1094 try:
1095 length, x = BER_len_dec(BER_id_dec(remaining)[1])
1096 except (BER_Decoding_Error, IndexError):
1097 return None
1098 if length and len(x) >= length:
1099 remaining = x[length:]
1100 if not remaining:
1101 pkt = cls(data)
1102 # Packet can be a whole response yet still miss some content.
1103 if (
1104 LDAP_SearchResponseEntry in pkt
1105 and LDAP_SearchResponseResultDone not in pkt
1106 ):
1107 return None
1108 return pkt
1109 else:
1110 return None
1111 return None
1113 def hashret(self):
1114 return b"ldap"
1116 @property
1117 def unsolicited(self):
1118 # RFC4511 sect 4.4. - Unsolicited Notification
1119 return self.messageID == 0 and isinstance(
1120 self.protocolOp, LDAP_ExtendedResponse
1121 )
1123 def answers(self, other):
1124 if self.unsolicited:
1125 return True
1126 return isinstance(other, LDAP) and other.messageID == self.messageID
1128 def mysummary(self):
1129 if not self.protocolOp or not self.messageID:
1130 return ""
1131 return (
1132 "%s(%s)"
1133 % (
1134 self.protocolOp.__class__.__name__.replace("_", " "),
1135 self.messageID.val,
1136 ),
1137 [LDAP],
1138 )
1141bind_layers(LDAP, LDAP)
1143bind_bottom_up(TCP, LDAP, dport=389)
1144bind_bottom_up(TCP, LDAP, sport=389)
1145bind_bottom_up(TCP, LDAP, dport=3268)
1146bind_bottom_up(TCP, LDAP, sport=3268)
1147bind_layers(TCP, LDAP, sport=389, dport=389)
1149# CLDAP - rfc1798
1152class CLDAP(ASN1_Packet):
1153 ASN1_codec = ASN1_Codecs.BER
1154 ASN1_root = ASN1F_SEQUENCE(
1155 LDAP.ASN1_root.seq[0], # messageID
1156 ASN1F_optional(
1157 LDAPDN("user", ""),
1158 ),
1159 LDAP.ASN1_root.seq[1], # protocolOp
1160 )
1162 def answers(self, other):
1163 return isinstance(other, CLDAP) and other.messageID == self.messageID
1166bind_layers(CLDAP, CLDAP)
1168bind_bottom_up(UDP, CLDAP, dport=389)
1169bind_bottom_up(UDP, CLDAP, sport=389)
1170bind_layers(UDP, CLDAP, sport=389, dport=389)
1172# [MS-ADTS] sect 3.1.1.2.3.3
1174LDAP_PROPERTY_SET = {
1175 uuid.UUID(
1176 "C7407360-20BF-11D0-A768-00AA006E0529"
1177 ): "Domain Password & Lockout Policies",
1178 uuid.UUID("59BA2F42-79A2-11D0-9020-00C04FC2D3CF"): "General Information",
1179 uuid.UUID("4C164200-20C0-11D0-A768-00AA006E0529"): "Account Restrictions",
1180 uuid.UUID("5F202010-79A5-11D0-9020-00C04FC2D4CF"): "Logon Information",
1181 uuid.UUID("BC0AC240-79A9-11D0-9020-00C04FC2D4CF"): "Group Membership",
1182 uuid.UUID("E45795B2-9455-11D1-AEBD-0000F80367C1"): "Phone and Mail Options",
1183 uuid.UUID("77B5B886-944A-11D1-AEBD-0000F80367C1"): "Personal Information",
1184 uuid.UUID("E45795B3-9455-11D1-AEBD-0000F80367C1"): "Web Information",
1185 uuid.UUID("E48D0154-BCF8-11D1-8702-00C04FB96050"): "Public Information",
1186 uuid.UUID("037088F8-0AE1-11D2-B422-00A0C968F939"): "Remote Access Information",
1187 uuid.UUID("B8119FD0-04F6-4762-AB7A-4986C76B3F9A"): "Other Domain Parameters",
1188 uuid.UUID("72E39547-7B18-11D1-ADEF-00C04FD8D5CD"): "DNS Host Name Attributes",
1189 uuid.UUID("FFA6F046-CA4B-4FEB-B40D-04DFEE722543"): "MS-TS-GatewayAccess",
1190 uuid.UUID("91E647DE-D96F-4B70-9557-D63FF4F3CCD8"): "Private Information",
1191 uuid.UUID("5805BC62-BDC9-4428-A5E2-856A0F4C185E"): "Terminal Server License Server",
1192}
1194# [MS-ADTS] sect 5.1.3.2.1
1196LDAP_CONTROL_ACCESS_RIGHTS = {
1197 uuid.UUID("ee914b82-0a98-11d1-adbb-00c04fd8d5cd"): "Abandon-Replication",
1198 uuid.UUID("440820ad-65b4-11d1-a3da-0000f875ae0d"): "Add-GUID",
1199 uuid.UUID("1abd7cf8-0a99-11d1-adbb-00c04fd8d5cd"): "Allocate-Rids",
1200 uuid.UUID("68b1d179-0d15-4d4f-ab71-46152e79a7bc"): "Allowed-To-Authenticate",
1201 uuid.UUID("edacfd8f-ffb3-11d1-b41d-00a0c968f939"): "Apply-Group-Policy",
1202 uuid.UUID("0e10c968-78fb-11d2-90d4-00c04f79dc55"): "Certificate-Enrollment",
1203 uuid.UUID("a05b8cc2-17bc-4802-a710-e7c15ab866a2"): "Certificate-AutoEnrollment",
1204 uuid.UUID("014bf69c-7b3b-11d1-85f6-08002be74fab"): "Change-Domain-Master",
1205 uuid.UUID("cc17b1fb-33d9-11d2-97d4-00c04fd8d5cd"): "Change-Infrastructure-Master",
1206 uuid.UUID("bae50096-4752-11d1-9052-00c04fc2d4cf"): "Change-PDC",
1207 uuid.UUID("d58d5f36-0a98-11d1-adbb-00c04fd8d5cd"): "Change-Rid-Master",
1208 uuid.UUID("e12b56b6-0a95-11d1-adbb-00c04fd8d5cd"): "Change-Schema-Master",
1209 uuid.UUID("e2a36dc9-ae17-47c3-b58b-be34c55ba633"): "Create-Inbound-Forest-Trust",
1210 uuid.UUID("fec364e0-0a98-11d1-adbb-00c04fd8d5cd"): "Do-Garbage-Collection",
1211 uuid.UUID("ab721a52-1e2f-11d0-9819-00aa0040529b"): "Domain-Administer-Server",
1212 uuid.UUID("69ae6200-7f46-11d2-b9ad-00c04f79f805"): "DS-Check-Stale-Phantoms",
1213 uuid.UUID("2f16c4a5-b98e-432c-952a-cb388ba33f2e"): "DS-Execute-Intentions-Script",
1214 uuid.UUID("9923a32a-3607-11d2-b9be-0000f87a36b2"): "DS-Install-Replica",
1215 uuid.UUID("4ecc03fe-ffc0-4947-b630-eb672a8a9dbc"): "DS-Query-Self-Quota",
1216 uuid.UUID("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes",
1217 uuid.UUID("1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes-All",
1218 uuid.UUID(
1219 "89e95b76-444d-4c62-991a-0facbeda640c"
1220 ): "DS-Replication-Get-Changes-In-Filtered-Set",
1221 uuid.UUID("1131f6ac-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Manage-Topology",
1222 uuid.UUID(
1223 "f98340fb-7c5b-4cdb-a00b-2ebdfa115a96"
1224 ): "DS-Replication-Monitor-Topology",
1225 uuid.UUID("1131f6ab-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Synchronize",
1226 uuid.UUID(
1227 "05c74c5e-4deb-43b4-bd9f-86664c2a7fd5"
1228 ): "Enable-Per-User-Reversibly-Encrypted-Password",
1229 uuid.UUID("b7b1b3de-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Logging",
1230 uuid.UUID("b7b1b3dd-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Planning",
1231 uuid.UUID("7c0e2a7c-a419-48e4-a995-10180aad54dd"): "Manage-Optional-Features",
1232 uuid.UUID("ba33815a-4f93-4c76-87f3-57574bff8109"): "Migrate-SID-History",
1233 uuid.UUID("b4e60130-df3f-11d1-9c86-006008764d0e"): "msmq-Open-Connector",
1234 uuid.UUID("06bd3201-df3e-11d1-9c86-006008764d0e"): "msmq-Peek",
1235 uuid.UUID("4b6e08c3-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-computer-Journal",
1236 uuid.UUID("4b6e08c1-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-Dead-Letter",
1237 uuid.UUID("06bd3200-df3e-11d1-9c86-006008764d0e"): "msmq-Receive",
1238 uuid.UUID("4b6e08c2-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-computer-Journal",
1239 uuid.UUID("4b6e08c0-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-Dead-Letter",
1240 uuid.UUID("06bd3203-df3e-11d1-9c86-006008764d0e"): "msmq-Receive-journal",
1241 uuid.UUID("06bd3202-df3e-11d1-9c86-006008764d0e"): "msmq-Send",
1242 uuid.UUID("a1990816-4298-11d1-ade2-00c04fd8d5cd"): "Open-Address-Book",
1243 uuid.UUID(
1244 "1131f6ae-9c07-11d1-f79f-00c04fc2dcd2"
1245 ): "Read-Only-Replication-Secret-Synchronization",
1246 uuid.UUID("45ec5156-db7e-47bb-b53f-dbeb2d03c40f"): "Reanimate-Tombstones",
1247 uuid.UUID("0bc1554e-0a99-11d1-adbb-00c04fd8d5cd"): "Recalculate-Hierarchy",
1248 uuid.UUID(
1249 "62dd28a8-7f46-11d2-b9ad-00c04f79f805"
1250 ): "Recalculate-Security-Inheritance",
1251 uuid.UUID("ab721a56-1e2f-11d0-9819-00aa0040529b"): "Receive-As",
1252 uuid.UUID("9432c620-033c-4db7-8b58-14ef6d0bf477"): "Refresh-Group-Cache",
1253 uuid.UUID("1a60ea8d-58a6-4b20-bcdc-fb71eb8a9ff8"): "Reload-SSL-Certificate",
1254 uuid.UUID("7726b9d5-a4b4-4288-a6b2-dce952e80a7f"): "Run-Protect_Admin_Groups-Task",
1255 uuid.UUID("91d67418-0135-4acc-8d79-c08e857cfbec"): "SAM-Enumerate-Entire-Domain",
1256 uuid.UUID("ab721a54-1e2f-11d0-9819-00aa0040529b"): "Send-As",
1257 uuid.UUID("ab721a55-1e2f-11d0-9819-00aa0040529b"): "Send-To",
1258 uuid.UUID("ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501"): "Unexpire-Password",
1259 uuid.UUID(
1260 "280f369c-67c7-438e-ae98-1d46f3c6f541"
1261 ): "Update-Password-Not-Required-Bit",
1262 uuid.UUID("be2bb760-7f46-11d2-b9ad-00c04f79f805"): "Update-Schema-Cache",
1263 uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"): "User-Change-Password",
1264 uuid.UUID("00299570-246d-11d0-a768-00aa006e0529"): "User-Force-Change-Password",
1265 uuid.UUID("3e0f7e18-2c7a-4c10-ba82-4d926db99a3e"): "DS-Clone-Domain-Controller",
1266 uuid.UUID("084c93a2-620d-4879-a836-f0ae47de0e89"): "DS-Read-Partition-Secrets",
1267 uuid.UUID("94825a8d-b171-4116-8146-1e34d8f54401"): "DS-Write-Partition-Secrets",
1268 uuid.UUID("4125c71f-7fac-4ff0-bcb7-f09a41325286"): "DS-Set-Owner",
1269 uuid.UUID("88a9933e-e5c8-4f2a-9dd7-2527416b8092"): "DS-Bypass-Quota",
1270 uuid.UUID("9b026da6-0d3c-465c-8bee-5199d7165cba"): "DS-Validated-Write-Computer",
1271}
1273# [MS-ADTS] sect 5.1.3.2 and
1274# https://learn.microsoft.com/en-us/windows/win32/secauthz/directory-services-access-rights
1276LDAP_DS_ACCESS_RIGHTS = {
1277 0x00000001: "CREATE_CHILD",
1278 0x00000002: "DELETE_CHILD",
1279 0x00000004: "LIST_CONTENTS",
1280 0x00000008: "WRITE_PROPERTY_EXTENDED",
1281 0x00000010: "READ_PROP",
1282 0x00000020: "WRITE_PROP",
1283 0x00000040: "DELETE_TREE",
1284 0x00000080: "LIST_OBJECT",
1285 0x00000100: "CONTROL_ACCESS",
1286 0x00010000: "DELETE",
1287 0x00020000: "READ_CONTROL",
1288 0x00040000: "WRITE_DAC",
1289 0x00080000: "WRITE_OWNER",
1290 0x00100000: "SYNCHRONIZE",
1291 0x01000000: "ACCESS_SYSTEM_SECURITY",
1292 0x80000000: "GENERIC_READ",
1293 0x40000000: "GENERIC_WRITE",
1294 0x20000000: "GENERIC_EXECUTE",
1295 0x10000000: "GENERIC_ALL",
1296}
1299# Small CLDAP Answering machine: [MS-ADTS] 6.3.3 - Ldap ping
1302class LdapPing_am(AnsweringMachine):
1303 function_name = "ldappingd"
1304 filter = "udp port 389 or 138"
1305 send_function = staticmethod(send)
1307 def parse_options(
1308 self,
1309 NetbiosDomainName="DOMAIN",
1310 DomainGuid=uuid.UUID("192bc4b3-0085-4521-83fe-062913ef59f2"),
1311 DcSiteName="Default-First-Site-Name",
1312 NetbiosComputerName="SRV1",
1313 DnsForestName=None,
1314 DnsHostName=None,
1315 src_ip=None,
1316 src_ip6=None,
1317 ):
1318 self.NetbiosDomainName = NetbiosDomainName
1319 self.DnsForestName = DnsForestName or (NetbiosDomainName + ".LOCAL")
1320 self.DomainGuid = DomainGuid
1321 self.DcSiteName = DcSiteName
1322 self.NetbiosComputerName = NetbiosComputerName
1323 self.DnsHostName = DnsHostName or (
1324 NetbiosComputerName + "." + self.DnsForestName
1325 )
1326 self.src_ip = src_ip
1327 self.src_ip6 = src_ip6
1329 def is_request(self, req):
1330 # [MS-ADTS] 6.3.3 - Example:
1331 # (&(DnsDomain=abcde.corp.microsoft.com)(Host=abcdefgh-dev)(User=abcdefgh-
1332 # dev$)(AAC=\80\00\00\00)(DomainGuid=\3b\b0\21\ca\d3\6d\d1\11\8a\7d\b8\df\b1\56\87\1f)(NtVer
1333 # =\06\00\00\00))
1334 if NBTDatagram in req:
1335 # special case: mailslot ping
1336 from scapy.layers.smb import SMBMailslot_Write, NETLOGON_SAM_LOGON_REQUEST
1338 try:
1339 return (
1340 SMBMailslot_Write in req and NETLOGON_SAM_LOGON_REQUEST in req.Data
1341 )
1342 except AttributeError:
1343 return False
1344 if CLDAP not in req or not isinstance(req.protocolOp, LDAP_SearchRequest):
1345 return False
1346 req = req.protocolOp
1347 return (
1348 req.attributes
1349 and req.attributes[0].type.val.lower() == b"netlogon"
1350 and req.filter
1351 and isinstance(req.filter.filter, LDAP_FilterAnd)
1352 and any(
1353 x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.vals
1354 )
1355 )
1357 def make_reply(self, req):
1358 if NBTDatagram in req:
1359 # Special case
1360 return self.make_mailslot_ping_reply(req)
1361 if IPv6 in req:
1362 resp = IPv6(dst=req[IPv6].src, src=self.src_ip6 or req[IPv6].dst)
1363 else:
1364 resp = IP(dst=req[IP].src, src=self.src_ip or req[IP].dst)
1365 resp /= UDP(sport=req.dport, dport=req.sport)
1366 # get the DnsDomainName from the request
1367 try:
1368 DnsDomainName = next(
1369 x.filter.attributeValue.val
1370 for x in req.protocolOp.filter.filter.vals
1371 if x.filter.attributeType.val == b"DnsDomain"
1372 )
1373 except StopIteration:
1374 return
1375 return (
1376 resp
1377 / CLDAP(
1378 protocolOp=LDAP_SearchResponseEntry(
1379 attributes=[
1380 LDAP_PartialAttribute(
1381 values=[
1382 LDAP_AttributeValue(
1383 value=ASN1_STRING(
1384 val=bytes(
1385 NETLOGON_SAM_LOGON_RESPONSE_EX(
1386 # Mandatory fields
1387 DnsDomainName=DnsDomainName,
1388 NtVersion="V1+V5",
1389 LmNtToken=65535,
1390 Lm20Token=65535,
1391 # Below can be customized
1392 Flags=0x3F3FD,
1393 DomainGuid=self.DomainGuid,
1394 DnsForestName=self.DnsForestName,
1395 DnsHostName=self.DnsHostName,
1396 NetbiosDomainName=self.NetbiosDomainName, # noqa: E501
1397 NetbiosComputerName=self.NetbiosComputerName, # noqa: E501
1398 UserName=b".",
1399 DcSiteName=self.DcSiteName,
1400 ClientSiteName=self.DcSiteName,
1401 )
1402 )
1403 )
1404 )
1405 ],
1406 type=ASN1_STRING(b"Netlogon"),
1407 )
1408 ],
1409 ),
1410 messageID=req.messageID,
1411 user=None,
1412 )
1413 / CLDAP(
1414 protocolOp=LDAP_SearchResponseResultDone(
1415 referral=None,
1416 resultCode=0,
1417 ),
1418 messageID=req.messageID,
1419 user=None,
1420 )
1421 )
1423 def make_mailslot_ping_reply(self, req):
1424 # type: (Packet) -> Packet
1425 from scapy.layers.smb import (
1426 SMBMailslot_Write,
1427 SMB_Header,
1428 DcSockAddr,
1429 NETLOGON_SAM_LOGON_RESPONSE_EX,
1430 )
1432 resp = IP(dst=req[IP].src) / UDP(
1433 sport=req.dport,
1434 dport=req.sport,
1435 )
1436 address = self.src_ip or get_if_addr(self.optsniff.get("iface", conf.iface))
1437 resp /= (
1438 NBTDatagram(
1439 SourceName=req.DestinationName,
1440 SUFFIX1=req.SUFFIX2,
1441 DestinationName=req.SourceName,
1442 SUFFIX2=req.SUFFIX1,
1443 SourceIP=address,
1444 )
1445 / SMB_Header()
1446 / SMBMailslot_Write(
1447 Name=req.Data.MailslotName,
1448 )
1449 )
1450 NetbiosDomainName = req.DestinationName.strip()
1451 resp.Data = NETLOGON_SAM_LOGON_RESPONSE_EX(
1452 # Mandatory fields
1453 NetbiosDomainName=NetbiosDomainName,
1454 DcSockAddr=DcSockAddr(
1455 sin_addr=address,
1456 ),
1457 NtVersion="V1+V5EX+V5EX_WITH_IP",
1458 LmNtToken=65535,
1459 Lm20Token=65535,
1460 # Below can be customized
1461 Flags=0x3F3FD,
1462 DomainGuid=self.DomainGuid,
1463 DnsForestName=self.DnsForestName,
1464 DnsDomainName=self.DnsForestName,
1465 DnsHostName=self.DnsHostName,
1466 NetbiosComputerName=self.NetbiosComputerName,
1467 DcSiteName=self.DcSiteName,
1468 ClientSiteName=self.DcSiteName,
1469 )
1470 return resp
1473_located_dc = collections.namedtuple("LocatedDC", ["ip", "samlogon"])
1474_dclocatorcache = conf.netcache.new_cache("dclocator", 600)
1477@conf.commands.register
1478def dclocator(
1479 realm, qtype="A", mode="ldap", port=None, timeout=1, NtVersion=None, debug=0
1480):
1481 """
1482 Perform a DC Locator as per [MS-ADTS] sect 6.3.6 or RFC4120.
1484 :param realm: the kerberos realm to locate
1485 :param mode: Detect if a server is up and joinable thanks to one of:
1487 - 'nocheck': Do not check that servers are online.
1488 - 'ldap': Use the LDAP ping (CLDAP) per [MS-ADTS]. Default.
1489 This will however not work with MIT Kerberos servers.
1490 - 'connect': connect to specified port to test the connection.
1492 :param mode: in connect mode, the port to connect to. (e.g. 88)
1493 :param debug: print debug logs
1495 This is cached in conf.netcache.dclocator.
1496 """
1497 if NtVersion is None:
1498 # Windows' default
1499 NtVersion = (
1500 0x00000002 # V5
1501 | 0x00000004 # V5EX
1502 | 0x00000010 # V5EX_WITH_CLOSEST_SITE
1503 | 0x01000000 # AVOID_NT4EMUL
1504 | 0x20000000 # IP
1505 )
1506 # Check cache
1507 cache_ident = ";".join([realm, qtype, mode, str(NtVersion)]).lower()
1508 if cache_ident in _dclocatorcache:
1509 return _dclocatorcache[cache_ident]
1510 # Perform DNS-Based discovery (6.3.6.1)
1511 # 1. SRV records
1512 qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower()
1513 if debug:
1514 log_runtime.info("DC Locator: requesting SRV for '%s' ..." % qname)
1515 try:
1516 hosts = [
1517 x.target
1518 for x in dns_resolve(
1519 qname=qname,
1520 qtype="SRV",
1521 timeout=timeout,
1522 )
1523 ]
1524 except TimeoutError:
1525 raise TimeoutError("Resolution of %s timed out" % qname)
1526 if not hosts:
1527 raise ValueError("No DNS record found for %s" % qname)
1528 elif debug:
1529 log_runtime.info(
1530 "DC Locator: got %s. Resolving %s records ..." % (hosts, qtype)
1531 )
1532 # 2. A records
1533 ips = []
1534 for host in hosts:
1535 arec = dns_resolve(
1536 qname=host,
1537 qtype=qtype,
1538 timeout=timeout,
1539 )
1540 if arec:
1541 ips.extend(x.rdata for x in arec)
1542 if not ips:
1543 raise ValueError("Could not get any %s records for %s" % (qtype, hosts))
1544 elif debug:
1545 log_runtime.info("DC Locator: got %s . Mode: %s" % (ips, mode))
1546 # Pick first online host. We have three options
1547 if mode == "nocheck":
1548 # Don't check anything. Not recommended
1549 return _located_dc(ips[0], None)
1550 elif mode == "connect":
1551 assert port is not None, "Must provide a port in connect mode !"
1552 # Compatibility with MIT Kerberos servers
1553 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]"
1554 if debug:
1555 log_runtime.info("DC Locator: connecting to %s on %s ..." % (ip, port))
1556 try:
1557 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1558 sock.settimeout(timeout)
1559 sock.connect((ip, port))
1560 # Success
1561 result = _located_dc(ip, None)
1562 # Cache
1563 _dclocatorcache[cache_ident] = result
1564 return result
1565 except OSError:
1566 # Host timed out, No route to host, etc.
1567 if debug:
1568 log_runtime.info("DC Locator: %s timed out." % ip)
1569 continue
1570 finally:
1571 sock.close()
1572 raise ValueError("No host was reachable on port %s among %s" % (port, ips))
1573 elif mode == "ldap":
1574 # Real 'LDAP Ping' per [MS-ADTS]
1575 for ip in ips: # TODO: "addresses in weighted random order [RFC2782]"
1576 if debug:
1577 log_runtime.info("DC Locator: LDAP Ping %s on ..." % ip)
1578 try:
1579 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1580 sock.settimeout(timeout)
1581 sock.connect((ip, 389))
1582 sock = SimpleSocket(sock, CLDAP)
1583 pkt = sock.sr1(
1584 CLDAP(
1585 protocolOp=LDAP_SearchRequest(
1586 filter=LDAP_Filter(
1587 filter=LDAP_FilterAnd(
1588 vals=[
1589 LDAP_Filter(
1590 filter=LDAP_FilterEqual(
1591 attributeType=ASN1_STRING(b"DnsDomain"),
1592 attributeValue=ASN1_STRING(realm),
1593 )
1594 ),
1595 LDAP_Filter(
1596 filter=LDAP_FilterEqual(
1597 attributeType=ASN1_STRING(b"NtVer"),
1598 attributeValue=ASN1_STRING(
1599 struct.pack("<I", NtVersion)
1600 ),
1601 )
1602 ),
1603 ]
1604 )
1605 ),
1606 attributes=[
1607 LDAP_SearchRequestAttribute(
1608 type=ASN1_STRING(b"Netlogon")
1609 )
1610 ],
1611 ),
1612 user=None,
1613 ),
1614 timeout=timeout,
1615 verbose=0,
1616 )
1617 if pkt:
1618 # Check if we have a search response
1619 response = None
1620 if isinstance(pkt.protocolOp, LDAP_SearchResponseEntry):
1621 try:
1622 response = next(
1623 NETLOGON(x.values[0].value.val)
1624 for x in pkt.protocolOp.attributes
1625 if x.type.val == b"Netlogon"
1626 )
1627 except StopIteration:
1628 pass
1629 result = _located_dc(ip, response)
1630 # Cache
1631 _dclocatorcache[cache_ident] = result
1632 return result
1633 except OSError:
1634 # Host timed out, No route to host, etc.
1635 if debug:
1636 log_runtime.info("DC Locator: %s timed out." % ip)
1637 continue
1638 finally:
1639 sock.close()
1640 raise ValueError("No LDAP ping succeeded on any of %s. Try another mode?" % ips)
1643#####################
1644# Basic LDAP client #
1645#####################
1648class LDAP_BIND_MECHS(StrEnum):
1649 NONE = "ANONYMOUS"
1650 SIMPLE = "SIMPLE"
1651 SASL_GSSAPI = "GSSAPI"
1652 SASL_GSS_SPNEGO = "GSS-SPNEGO"
1653 SASL_EXTERNAL = "EXTERNAL"
1654 SASL_DIGEST_MD5 = "DIGEST-MD5"
1655 # [MS-ADTS] extension
1656 SICILY = "SICILY"
1659class LDAP_SASL_GSSAPI_SsfCap(Packet):
1660 """
1661 RFC2222 sect 7.2.1 and 7.2.2 negotiate token
1662 """
1664 fields_desc = [
1665 FlagsField(
1666 "supported_security_layers",
1667 0,
1668 -8,
1669 {
1670 # https://github.com/cyrusimap/cyrus-sasl/blob/7e2feaeeb2e37d38cb5fa957d0e8a599ced22612/plugins/gssapi.c#L221
1671 0x01: "NONE",
1672 0x02: "INTEGRITY",
1673 0x04: "CONFIDENTIALITY",
1674 },
1675 ),
1676 ThreeBytesField("max_output_token_size", 0),
1677 ]
1680class LDAP_SASL_Buffer(Packet):
1681 """
1682 RFC 4422 sect 3.7
1683 """
1685 # "Each buffer of protected data is transferred over the underlying
1686 # transport connection as a sequence of octets prepended with a four-
1687 # octet field in network byte order that represents the length of the
1688 # buffer."
1690 fields_desc = [
1691 FieldLenField("BufferLength", None, fmt="!I", length_of="Buffer"),
1692 _GSSAPI_Field("Buffer", LDAP),
1693 ]
1695 def hashret(self):
1696 return b"ldap"
1698 def answers(self, other):
1699 return isinstance(other, LDAP_SASL_Buffer)
1701 @classmethod
1702 def tcp_reassemble(cls, data, *args, **kwargs):
1703 if len(data) < 4:
1704 return None
1705 if data[0] == 0x30:
1706 # Add a heuristic to detect LDAP errors
1707 xlen, x = BER_len_dec(BER_id_dec(data)[1])
1708 if xlen and xlen == len(x):
1709 return LDAP(data)
1710 # Check BufferLength
1711 length = struct.unpack("!I", data[:4])[0] + 4
1712 if len(data) >= length:
1713 return cls(data)
1716class LDAP_Exception(RuntimeError):
1717 __slots__ = ["resultCode", "diagnosticMessage"]
1719 def __init__(self, *args, **kwargs):
1720 resp = kwargs.pop("resp", None)
1721 if resp:
1722 self.resultCode = resp.protocolOp.sprintf("%resultCode%")
1723 self.diagnosticMessage = resp.protocolOp.diagnosticMessage.val.rstrip(
1724 b"\x00"
1725 ).decode(errors="backslashreplace")
1726 else:
1727 self.resultCode = kwargs.pop("resultCode", None)
1728 self.diagnosticMessage = kwargs.pop("diagnosticMessage", None)
1729 super(LDAP_Exception, self).__init__(*args, **kwargs)
1730 # If there's a 'data' string argument, attempt to parse the error code.
1731 try:
1732 m = re.match(r"(\d+): LdapErr.*", self.diagnosticMessage)
1733 if m:
1734 errstr = m.group(1)
1735 err = int(errstr, 16)
1736 if err in STATUS_ERREF:
1737 self.diagnosticMessage = self.diagnosticMessage.replace(
1738 errstr, errstr + " (%s)" % STATUS_ERREF[err], 1
1739 )
1740 except ValueError:
1741 pass
1742 # Add note if this exception is raised
1743 self.add_note(self.diagnosticMessage)
1746class LDAP_Client(object):
1747 """
1748 A basic LDAP client
1750 The complete documentation is available at
1751 https://scapy.readthedocs.io/en/latest/layers/ldap.html
1753 Example 1 - SICILY - NTLM (with encryption)::
1755 client = LDAP_Client()
1756 client.connect("192.168.0.100")
1757 ssp = NTLMSSP(UPN="Administrator", PASSWORD="Password1!")
1758 client.bind(
1759 LDAP_BIND_MECHS.SICILY,
1760 ssp=ssp,
1761 encrypt=True,
1762 )
1764 Example 2 - SASL_GSSAPI - Kerberos (with signing)::
1766 client = LDAP_Client()
1767 client.connect("192.168.0.100")
1768 ssp = KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!",
1769 SPN="ldap/dc1.domain.local")
1770 client.bind(
1771 LDAP_BIND_MECHS.SASL_GSSAPI,
1772 ssp=ssp,
1773 sign=True,
1774 )
1776 Example 3 - SASL_GSS_SPNEGO - NTLM / Kerberos::
1778 client = LDAP_Client()
1779 client.connect("192.168.0.100")
1780 ssp = SPNEGOSSP([
1781 NTLMSSP(UPN="Administrator", PASSWORD="Password1!"),
1782 KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!",
1783 SPN="ldap/dc1.domain.local"),
1784 ])
1785 client.bind(
1786 LDAP_BIND_MECHS.SASL_GSS_SPNEGO,
1787 ssp=ssp,
1788 )
1790 Example 4 - Simple bind over TLS::
1792 client = LDAP_Client()
1793 client.connect("192.168.0.100", use_ssl=True)
1794 client.bind(
1795 LDAP_BIND_MECHS.SIMPLE,
1796 simple_username="Administrator",
1797 simple_password="Password1!",
1798 )
1799 """
1801 def __init__(
1802 self,
1803 verb=True,
1804 ):
1805 self.sock = None
1806 self.host = None
1807 self.verb = verb
1808 self.ssl = False
1809 self.sslcontext = None
1810 self.ssp = None
1811 self.sspcontext = None
1812 self.encrypt = False
1813 self.sign = False
1814 # Session status
1815 self.sasl_wrap = False
1816 self.chan_bindings = GSS_C_NO_CHANNEL_BINDINGS
1817 self.bound = False
1818 self.messageID = 0
1820 def connect(
1821 self,
1822 host,
1823 port=None,
1824 use_ssl=False,
1825 sslcontext=None,
1826 sni=None,
1827 no_check_certificate=False,
1828 timeout=5,
1829 ):
1830 """
1831 Initiate a connection
1833 :param host: the IP or hostname to connect to.
1834 :param port: the port to connect to. (Default: 389 or 636)
1836 :param use_ssl: whether to use LDAPS or not. (Default: False)
1837 :param sslcontext: an optional SSLContext to use.
1838 :param sni: (optional) specify the SNI to use if LDAPS, otherwise use ip.
1839 :param no_check_certificate: with SSL, do not check the certificate
1840 """
1841 self.ssl = use_ssl
1842 self.sslcontext = sslcontext
1843 self.timeout = timeout
1844 self.host = host
1846 if port is None:
1847 if self.ssl:
1848 port = 636
1849 else:
1850 port = 389
1852 # Create and configure socket
1853 sock = socket.socket()
1854 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
1855 sock.settimeout(timeout)
1857 # Connect
1858 if self.verb:
1859 print(
1860 "\u2503 Connecting to %s on port %s%s..."
1861 % (
1862 host,
1863 port,
1864 " with SSL" if self.ssl else "",
1865 )
1866 )
1867 sock.connect((host, port))
1868 if self.verb:
1869 print(
1870 conf.color_theme.green(
1871 "\u2514 Connected from %s" % repr(sock.getsockname())
1872 )
1873 )
1875 # For SSL, build and apply SSLContext
1876 if self.ssl:
1877 if self.sslcontext is None:
1878 if no_check_certificate:
1879 context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
1880 context.check_hostname = False
1881 context.verify_mode = ssl.CERT_NONE
1882 else:
1883 context = ssl.create_default_context()
1884 else:
1885 context = self.sslcontext
1886 sock = context.wrap_socket(sock, server_hostname=sni or host)
1888 # Wrap the socket in a Scapy socket
1889 if self.ssl:
1890 # Compute the channel binding token (CBT)
1891 self.chan_bindings = GssChannelBindings.fromssl(
1892 ChannelBindingType.TLS_SERVER_END_POINT,
1893 sslsock=sock,
1894 )
1896 self.sock = SSLStreamSocket(sock, LDAP)
1897 else:
1898 self.sock = StreamSocket(sock, LDAP)
1900 def sr1(self, protocolOp, controls: List[LDAP_Control] = None, **kwargs):
1901 self.messageID += 1
1902 if self.verb:
1903 print(conf.color_theme.opening(">> %s" % protocolOp.__class__.__name__))
1905 # Build packet
1906 pkt = LDAP(
1907 messageID=self.messageID,
1908 protocolOp=protocolOp,
1909 Controls=controls,
1910 )
1912 # If signing / encryption is used, apply
1913 if self.sasl_wrap:
1914 pkt = LDAP_SASL_Buffer(
1915 Buffer=self.ssp.GSS_Wrap(
1916 self.sspcontext,
1917 bytes(pkt),
1918 conf_req_flag=self.encrypt,
1919 # LDAP on Windows doesn't use SECBUFFER_PADDING, which
1920 # isn't supported by GSS_WrapEx. We add our own flag to
1921 # tell it.
1922 qop_req=GSS_QOP_REQ_FLAGS.GSS_S_NO_SECBUFFER_PADDING,
1923 )
1924 )
1926 # Send / Receive
1927 resp = self.sock.sr1(
1928 pkt,
1929 verbose=0,
1930 **kwargs,
1931 )
1932 # Check for unsolicited notification
1933 if resp and LDAP in resp and resp[LDAP].unsolicited:
1934 if self.verb:
1935 resp.show()
1936 print(conf.color_theme.fail("! Got unsolicited notification."))
1937 return resp
1939 # If signing / encryption is used, unpack
1940 if self.sasl_wrap:
1941 if resp.Buffer:
1942 resp = LDAP(
1943 self.ssp.GSS_Unwrap(
1944 self.sspcontext,
1945 resp.Buffer,
1946 )
1947 )
1948 else:
1949 resp = None
1951 # Verbose display
1952 if self.verb:
1953 if not resp:
1954 print(conf.color_theme.fail("! Bad response."))
1955 return
1956 else:
1957 print(
1958 conf.color_theme.success(
1959 "<< %s"
1960 % (
1961 resp.protocolOp.__class__.__name__
1962 if LDAP in resp
1963 else resp.__class__.__name__
1964 )
1965 )
1966 )
1967 return resp
1969 def bind(
1970 self,
1971 mech,
1972 ssp=None,
1973 sign: Optional[bool] = None,
1974 encrypt: Optional[bool] = None,
1975 simple_username=None,
1976 simple_password=None,
1977 ):
1978 """
1979 Send Bind request.
1981 :param mech: one of LDAP_BIND_MECHS
1982 :param ssp: the SSP object to use for binding
1984 :param sign: request signing when binding
1985 :param encrypt: request encryption when binding
1987 :
1988 This acts differently based on the :mech: provided during initialization.
1989 """
1990 # Bind default values: if NTLM then encrypt, else sign unless anonymous/simple
1991 if encrypt is None:
1992 encrypt = mech == LDAP_BIND_MECHS.SICILY
1993 if sign is None and not encrypt:
1994 sign = mech not in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE]
1996 # Store and check consistency
1997 self.mech = mech
1998 self.ssp = ssp # type: SSP
1999 self.sign = sign
2000 self.encrypt = encrypt
2001 self.sspcontext = None
2003 if mech is None or not isinstance(mech, LDAP_BIND_MECHS):
2004 raise ValueError(
2005 "'mech' attribute is required and must be one of LDAP_BIND_MECHS."
2006 )
2008 if mech == LDAP_BIND_MECHS.SASL_GSSAPI:
2009 from scapy.layers.kerberos import KerberosSSP
2011 if not isinstance(self.ssp, KerberosSSP):
2012 raise ValueError("Only raw KerberosSSP is supported with SASL_GSSAPI !")
2013 elif mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO:
2014 from scapy.layers.spnego import SPNEGOSSP
2016 if not isinstance(self.ssp, SPNEGOSSP):
2017 if WINDOWS:
2018 from scapy.arch.windows.sspi import WinSSP
2020 if not isinstance(self.ssp, WinSSP):
2021 raise ValueError(
2022 "Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !"
2023 )
2024 else:
2025 raise ValueError(
2026 "Only SPNEGOSSP is supported with SASL_GSS_SPNEGO !"
2027 )
2028 elif mech == LDAP_BIND_MECHS.SICILY:
2029 from scapy.layers.ntlm import NTLMSSP
2031 if not isinstance(self.ssp, NTLMSSP):
2032 raise ValueError("Only raw NTLMSSP is supported with SICILY !")
2033 if self.sign and not self.encrypt:
2034 raise ValueError(
2035 "NTLM on LDAP does not support signing without encryption !"
2036 )
2037 elif mech in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE]:
2038 if self.sign or self.encrypt:
2039 raise ValueError("Cannot use 'sign' or 'encrypt' with NONE or SIMPLE !")
2040 else:
2041 raise ValueError("Mech %s is still unimplemented !" % mech)
2043 if self.ssp is not None and mech in [
2044 LDAP_BIND_MECHS.NONE,
2045 LDAP_BIND_MECHS.SIMPLE,
2046 ]:
2047 raise ValueError("%s cannot be used with a ssp !" % mech.value)
2049 # Now perform the bind, depending on the mech
2050 if self.mech == LDAP_BIND_MECHS.SIMPLE:
2051 # Simple binding
2052 resp = self.sr1(
2053 LDAP_BindRequest(
2054 bind_name=ASN1_STRING(simple_username or ""),
2055 authentication=LDAP_Authentication_simple(
2056 simple_password or "",
2057 ),
2058 )
2059 )
2060 if (
2061 LDAP not in resp
2062 or not isinstance(resp.protocolOp, LDAP_BindResponse)
2063 or resp.protocolOp.resultCode != 0
2064 ):
2065 raise LDAP_Exception(
2066 "LDAP simple bind failed !",
2067 resp=resp,
2068 )
2069 status = GSS_S_COMPLETE
2070 elif self.mech == LDAP_BIND_MECHS.SICILY:
2071 # [MS-ADTS] sect 5.1.1.1.3
2072 # 1. Package Discovery
2073 resp = self.sr1(
2074 LDAP_BindRequest(
2075 bind_name=ASN1_STRING(b""),
2076 authentication=LDAP_Authentication_sicilyPackageDiscovery(b""),
2077 )
2078 )
2079 if resp.protocolOp.resultCode != 0:
2080 raise LDAP_Exception(
2081 "Sicily package discovery failed !",
2082 resp=resp,
2083 )
2084 # 2. First exchange: Negotiate
2085 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
2086 self.sspcontext,
2087 target_name="ldap/" + self.host,
2088 req_flags=(
2089 GSS_C_FLAGS.GSS_C_REPLAY_FLAG
2090 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG
2091 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
2092 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0)
2093 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0)
2094 ),
2095 )
2096 resp = self.sr1(
2097 LDAP_BindRequest(
2098 bind_name=ASN1_STRING(b"NTLM"),
2099 authentication=LDAP_Authentication_sicilyNegotiate(
2100 bytes(token),
2101 ),
2102 )
2103 )
2104 val = resp.protocolOp.serverCreds
2105 if not val:
2106 raise LDAP_Exception(
2107 "Sicily negotiate failed !",
2108 resp=resp,
2109 )
2110 # 3. Second exchange: Response
2111 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
2112 self.sspcontext,
2113 input_token=GSSAPI_BLOB(val),
2114 target_name="ldap/" + self.host,
2115 chan_bindings=self.chan_bindings,
2116 )
2117 resp = self.sr1(
2118 LDAP_BindRequest(
2119 bind_name=ASN1_STRING(b"NTLM"),
2120 authentication=LDAP_Authentication_sicilyResponse(
2121 bytes(token),
2122 ),
2123 )
2124 )
2125 if resp.protocolOp.resultCode != 0:
2126 raise LDAP_Exception(
2127 "Sicily response failed !",
2128 resp=resp,
2129 )
2130 elif self.mech in [
2131 LDAP_BIND_MECHS.SASL_GSS_SPNEGO,
2132 LDAP_BIND_MECHS.SASL_GSSAPI,
2133 ]:
2134 # GSSAPI or SPNEGO
2135 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
2136 self.sspcontext,
2137 target_name="ldap/" + self.host,
2138 req_flags=(
2139 # Required flags for GSSAPI: RFC4752 sect 3.1
2140 GSS_C_FLAGS.GSS_C_REPLAY_FLAG
2141 | GSS_C_FLAGS.GSS_C_SEQUENCE_FLAG
2142 | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
2143 | (GSS_C_FLAGS.GSS_C_INTEG_FLAG if self.sign else 0)
2144 | (GSS_C_FLAGS.GSS_C_CONF_FLAG if self.encrypt else 0)
2145 ),
2146 chan_bindings=self.chan_bindings,
2147 )
2148 if status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]:
2149 raise RuntimeError(
2150 "%s: GSS_Init_sec_context failed with %s !"
2151 % (self.mech.name, repr(status)),
2152 )
2153 while token:
2154 resp = self.sr1(
2155 LDAP_BindRequest(
2156 bind_name=ASN1_STRING(b""),
2157 authentication=LDAP_Authentication_SaslCredentials(
2158 mechanism=ASN1_STRING(self.mech.value),
2159 credentials=ASN1_STRING(bytes(token)),
2160 ),
2161 )
2162 )
2163 if not isinstance(resp.protocolOp, LDAP_BindResponse):
2164 raise LDAP_Exception(
2165 "%s bind failed !" % self.mech.name,
2166 resp=resp,
2167 )
2168 val = resp.protocolOp.serverSaslCredsData
2169 if resp.protocolOp.resultCode not in [0, 14]:
2170 raise LDAP_Exception(
2171 "SASL authentication failed !",
2172 resp=resp,
2173 )
2174 self.sspcontext, token, status = self.ssp.GSS_Init_sec_context(
2175 self.sspcontext,
2176 input_token=GSSAPI_BLOB(val),
2177 target_name="ldap/" + self.host,
2178 chan_bindings=self.chan_bindings,
2179 )
2180 else:
2181 status = GSS_S_COMPLETE
2182 if status != GSS_S_COMPLETE:
2183 raise RuntimeError(
2184 "%s: GSS_Init_sec_context failed with %s !"
2185 % (self.mech.name, repr(status)),
2186 )
2187 elif self.mech == LDAP_BIND_MECHS.SASL_GSSAPI:
2188 # GSSAPI has 2 extra exchanges
2189 # https://datatracker.ietf.org/doc/html/rfc2222#section-7.2.1
2190 resp = self.sr1(
2191 LDAP_BindRequest(
2192 bind_name=ASN1_STRING(b""),
2193 authentication=LDAP_Authentication_SaslCredentials(
2194 mechanism=ASN1_STRING(self.mech.value),
2195 credentials=None,
2196 ),
2197 )
2198 )
2199 # Parse server-supported layers
2200 saslOptions = LDAP_SASL_GSSAPI_SsfCap(
2201 self.ssp.GSS_Unwrap(
2202 self.sspcontext,
2203 GSSAPI_BLOB_SIGNATURE(resp.protocolOp.serverSaslCredsData),
2204 )
2205 )
2206 if self.sign and not saslOptions.supported_security_layers.INTEGRITY:
2207 raise RuntimeError("GSSAPI SASL failed to negotiate INTEGRITY !")
2208 if (
2209 self.encrypt
2210 and not saslOptions.supported_security_layers.CONFIDENTIALITY
2211 ):
2212 raise RuntimeError("GSSAPI SASL failed to negotiate CONFIDENTIALITY !")
2213 # Announce client-supported layers
2214 saslOptions = LDAP_SASL_GSSAPI_SsfCap(
2215 supported_security_layers=(
2216 "+".join(
2217 (["INTEGRITY"] if self.sign else [])
2218 + (["CONFIDENTIALITY"] if self.encrypt else [])
2219 )
2220 if (self.sign or self.encrypt)
2221 else "NONE"
2222 ),
2223 # Same as server
2224 max_output_token_size=saslOptions.max_output_token_size,
2225 )
2226 resp = self.sr1(
2227 LDAP_BindRequest(
2228 bind_name=ASN1_STRING(b""),
2229 authentication=LDAP_Authentication_SaslCredentials(
2230 mechanism=ASN1_STRING(self.mech.value),
2231 credentials=self.ssp.GSS_Wrap(
2232 self.sspcontext,
2233 bytes(saslOptions),
2234 # We still haven't finished negotiating
2235 conf_req_flag=False,
2236 ),
2237 ),
2238 )
2239 )
2240 if resp.protocolOp.resultCode != 0:
2241 raise LDAP_Exception(
2242 "GSSAPI SASL failed to negotiate client security flags !",
2243 resp=resp,
2244 )
2246 # If we use SPNEGO and NTLMSSP was used, understand we can't use sign
2247 if self.mech == LDAP_BIND_MECHS.SASL_GSS_SPNEGO:
2248 from scapy.layers.ntlm import NTLMSSP
2250 if isinstance(self.sspcontext.ssp, NTLMSSP):
2251 self.sign = False
2253 # SASL wrapping is now available.
2254 self.sasl_wrap = self.encrypt or self.sign
2255 if self.sasl_wrap:
2256 self.sock.closed = True # prevent closing by marking it as already closed.
2257 self.sock = StreamSocket(self.sock.ins, LDAP_SASL_Buffer)
2259 # Success.
2260 if self.verb:
2261 print("%s bind succeeded !" % self.mech.name)
2262 self.bound = True
2264 _TEXT_REG = re.compile(b"^[%s]*$" % re.escape(string.printable.encode()))
2266 def search(
2267 self,
2268 baseObject: str = "",
2269 filter: str = "",
2270 scope=0,
2271 derefAliases=0,
2272 sizeLimit=300000,
2273 timeLimit=3000,
2274 attrsOnly=0,
2275 attributes: List[str] = [],
2276 controls: List[LDAP_Control] = [],
2277 ) -> Dict[str, List[Any]]:
2278 """
2279 Perform a LDAP search.
2281 :param baseObject: the dn of the base object to search in.
2282 :param filter: the filter to apply to the search (currently unsupported)
2283 :param scope: 0=baseObject, 1=singleLevel, 2=wholeSubtree
2284 """
2285 if baseObject == "rootDSE":
2286 baseObject = ""
2287 if filter:
2288 filter = LDAP_Filter.from_rfc2254_string(filter)
2289 else:
2290 # Default filter: (objectClass=*)
2291 filter = LDAP_Filter(
2292 filter=LDAP_FilterPresent(
2293 present=ASN1_STRING(b"objectClass"),
2294 )
2295 )
2296 # we loop as we might need more than one packet thanks to paging
2297 cookie = b""
2298 entries = {}
2299 while True:
2300 resp = self.sr1(
2301 LDAP_SearchRequest(
2302 filter=filter,
2303 attributes=[
2304 LDAP_SearchRequestAttribute(type=ASN1_STRING(attr))
2305 for attr in attributes
2306 ],
2307 baseObject=ASN1_STRING(baseObject),
2308 scope=ASN1_ENUMERATED(scope),
2309 derefAliases=ASN1_ENUMERATED(derefAliases),
2310 sizeLimit=ASN1_INTEGER(sizeLimit),
2311 timeLimit=ASN1_INTEGER(timeLimit),
2312 attrsOnly=ASN1_BOOLEAN(attrsOnly),
2313 ),
2314 controls=(
2315 controls
2316 + (
2317 [
2318 # This control is only usable when bound.
2319 LDAP_Control(
2320 controlType="1.2.840.113556.1.4.319",
2321 criticality=True,
2322 controlValue=LDAP_realSearchControlValue(
2323 size=100, # paging to 100 per 100
2324 cookie=cookie,
2325 ),
2326 )
2327 ]
2328 if self.bound
2329 else []
2330 )
2331 ),
2332 timeout=self.timeout,
2333 )
2334 if LDAP_SearchResponseResultDone not in resp:
2335 resp.show()
2336 raise TimeoutError("Search timed out.")
2337 # Now, reassemble the results
2339 def _s(x):
2340 try:
2341 return x.decode()
2342 except UnicodeDecodeError:
2343 return x
2345 def _ssafe(x):
2346 if self._TEXT_REG.match(x):
2347 return x.decode()
2348 else:
2349 return x
2351 # For each individual packet response
2352 while resp:
2353 # Find all 'LDAP' layers
2354 if LDAP not in resp:
2355 log_runtime.warning("Invalid response: %s", repr(resp))
2356 break
2357 if LDAP_SearchResponseEntry in resp.protocolOp:
2358 attrs = {
2359 _s(attr.type.val): [_ssafe(x.value.val) for x in attr.values]
2360 for attr in resp.protocolOp.attributes
2361 }
2362 entries[_s(resp.protocolOp.objectName.val)] = attrs
2363 elif LDAP_SearchResponseResultDone in resp.protocolOp:
2364 resultCode = resp.protocolOp.resultCode
2365 if resultCode != 0x0: # != success
2366 log_runtime.warning(
2367 resp.protocolOp.sprintf("Got response: %resultCode%")
2368 )
2369 raise LDAP_Exception(
2370 "LDAP search failed !",
2371 resp=resp,
2372 )
2373 else:
2374 # success
2375 if resp.Controls:
2376 # We have controls back
2377 realSearchControlValue = next(
2378 (
2379 c.controlValue
2380 for c in resp.Controls
2381 if isinstance(
2382 c.controlValue, LDAP_realSearchControlValue
2383 )
2384 ),
2385 None,
2386 )
2387 if realSearchControlValue is not None:
2388 # has paging !
2389 cookie = realSearchControlValue.cookie.val
2390 break
2391 break
2392 resp = resp.payload
2393 # If we have a cookie, continue
2394 if not cookie:
2395 break
2396 return entries
2398 def modify(
2399 self,
2400 object: str,
2401 changes: List[LDAP_ModifyRequestChange],
2402 controls: List[LDAP_Control] = [],
2403 ) -> None:
2404 """
2405 Perform a LDAP modify request.
2407 :returns:
2408 """
2409 resp = self.sr1(
2410 LDAP_ModifyRequest(
2411 object=object,
2412 changes=changes,
2413 ),
2414 controls=controls,
2415 timeout=self.timeout,
2416 )
2417 if (
2418 LDAP_ModifyResponse not in resp.protocolOp
2419 or resp.protocolOp.resultCode != 0
2420 ):
2421 raise LDAP_Exception(
2422 "LDAP modify failed !",
2423 resp=resp,
2424 )
2426 def add(
2427 self,
2428 entry: str,
2429 attributes: Union[Dict[str, List[Any]], List[ASN1_Packet]],
2430 controls: List[LDAP_Control] = [],
2431 ):
2432 """
2433 Perform a LDAP add request.
2435 :param attributes: the attributes to add. We support two formats:
2436 - a list of LDAP_Attribute (or LDAP_PartialAttribute)
2437 - a dict following {attribute: [list of values]}
2439 :returns:
2440 """
2441 # We handle the two cases in the type of attributes
2442 if isinstance(attributes, dict):
2443 attributes = [
2444 LDAP_Attribute(
2445 type=ASN1_STRING(k),
2446 values=[
2447 LDAP_AttributeValue(
2448 value=ASN1_STRING(x),
2449 )
2450 for x in v
2451 ],
2452 )
2453 for k, v in attributes.items()
2454 ]
2456 resp = self.sr1(
2457 LDAP_AddRequest(
2458 entry=ASN1_STRING(entry),
2459 attributes=attributes,
2460 ),
2461 controls=controls,
2462 timeout=self.timeout,
2463 )
2464 if LDAP_AddResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0:
2465 raise LDAP_Exception(
2466 "LDAP add failed !",
2467 resp=resp,
2468 )
2470 def modifydn(
2471 self,
2472 entry: str,
2473 newdn: str,
2474 deleteoldrdn=True,
2475 controls: List[LDAP_Control] = [],
2476 ):
2477 """
2478 Perform a LDAP modify DN request.
2480 ..note:: This functions calculates the relative DN and superior required for
2481 LDAP ModifyDN automatically.
2483 :param entry: the DN of the entry to rename.
2484 :param newdn: the new FULL DN of the entry.
2485 :returns:
2486 """
2487 # RFC4511 sect 4.9
2488 # Calculate the newrdn (relative DN) and superior
2489 newrdn, newSuperior = newdn.split(",", 1)
2490 _, cur_superior = entry.split(",", 1)
2491 # If the superior hasn't changed, don't update it.
2492 if cur_superior == newSuperior:
2493 newSuperior = None
2494 # Send the request
2495 resp = self.sr1(
2496 LDAP_ModifyDNRequest(
2497 entry=entry,
2498 newrdn=newrdn,
2499 newSuperior=newSuperior,
2500 deleteoldrdn=deleteoldrdn,
2501 ),
2502 controls=controls,
2503 timeout=self.timeout,
2504 )
2505 if (
2506 LDAP_ModifyDNResponse not in resp.protocolOp
2507 or resp.protocolOp.resultCode != 0
2508 ):
2509 raise LDAP_Exception(
2510 "LDAP modify failed !",
2511 resp=resp,
2512 )
2514 def close(self):
2515 if self.verb:
2516 print("X Connection closed\n")
2517 self.sock.close()
2518 self.bound = False
2519 self.sspcontext = None