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