diff --git a/.config/ci/install.sh b/.config/ci/install.sh index bd21565896b..716e6892309 100755 --- a/.config/ci/install.sh +++ b/.config/ci/install.sh @@ -27,7 +27,9 @@ then fi fi -# Install wireshark data, ifconfig, vcan, samba +CUR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) + +# Install wireshark data, ifconfig, vcan, samba, openldap if [ "$OSTYPE" = "linux-gnu" ] then sudo apt-get update @@ -35,6 +37,12 @@ then sudo apt-get -qy install can-utils || exit 1 sudo apt-get -qy install linux-modules-extra-$(uname -r) || exit 1 sudo apt-get -qy install samba smbclient + # For OpenLDAP, we need to pre-populate some setup questions + sudo debconf-set-selections <<< 'slapd slapd/password2 password Bonjour1' + sudo debconf-set-selections <<< 'slapd slapd/password1 password Bonjour1' + sudo debconf-set-selections <<< 'slapd slapd/domain string scapy.net' + sudo apt-get -qy install slapd + ldapadd -D "cn=admin,dc=scapy,dc=net" -w Bonjour1 -f $CUR/openldap-testdata.ldif -c # Make sure libpcap is installed if [ ! -z $SCAPY_USE_LIBPCAP ] then diff --git a/.config/ci/openldap-testdata.ldif b/.config/ci/openldap-testdata.ldif new file mode 100644 index 00000000000..56a429afef2 --- /dev/null +++ b/.config/ci/openldap-testdata.ldif @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: OLDAP-2.8 +# This file is https://git.openldap.org/openldap/openldap/-/blob/master/tests/data/ppolicy.ldif?ref_type=heads +# (renamed to dc=scapy, dc=net) + +dn: dc=scapy, dc=net +objectClass: top +objectClass: organization +objectClass: dcObject +o: Scapy +dc: scapy + +dn: ou=People, dc=scapy, dc=net +objectClass: top +objectClass: organizationalUnit +ou: People + +dn: ou=Groups, dc=scapy, dc=net +objectClass: organizationalUnit +ou: Groups + +dn: cn=Policy Group, ou=Groups, dc=scapy, dc=net +objectClass: groupOfNames +cn: Policy Group +member: uid=nd, ou=People, dc=scapy, dc=net +owner: uid=ndadmin, ou=People, dc=scapy, dc=net + +dn: cn=Test Group, ou=Groups, dc=scapy, dc=net +objectClass: groupOfNames +cn: Policy Group +member: uid=another, ou=People, dc=scapy, dc=net + +dn: ou=Policies, dc=scapy, dc=net +objectClass: top +objectClass: organizationalUnit +ou: Policies + +dn: cn=Standard Policy, ou=Policies, dc=scapy, dc=net +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Standard Policy +pwdAttribute: 2.5.4.35 +pwdLockoutDuration: 15 +pwdInHistory: 6 +pwdCheckQuality: 2 +pwdExpireWarning: 10 +pwdMaxAge: 30 +pwdMinLength: 5 +pwdMaxLength: 13 +pwdGraceAuthnLimit: 3 +pwdAllowUserChange: TRUE +pwdMustChange: TRUE +pwdMaxFailure: 3 +pwdFailureCountInterval: 120 +pwdSafeModify: TRUE +pwdLockout: TRUE + +dn: cn=Idle Expiration Policy, ou=Policies, dc=scapy, dc=net +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Idle Expiration Policy +pwdAttribute: 2.5.4.35 +pwdLockoutDuration: 15 +pwdInHistory: 6 +pwdCheckQuality: 2 +pwdExpireWarning: 10 +pwdMaxIdle: 15 +pwdMinLength: 5 +pwdMaxLength: 13 +pwdGraceAuthnLimit: 3 +pwdAllowUserChange: TRUE +pwdMustChange: TRUE +pwdMaxFailure: 3 +pwdFailureCountInterval: 120 +pwdSafeModify: TRUE +pwdLockout: TRUE + +dn: cn=Stricter Policy, ou=Policies, dc=scapy, dc=net +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Stricter Policy +pwdAttribute: 2.5.4.35 +pwdLockoutDuration: 15 +pwdInHistory: 6 +pwdCheckQuality: 2 +pwdExpireWarning: 10 +pwdMaxAge: 15 +pwdMinLength: 5 +pwdMaxLength: 13 +pwdAllowUserChange: TRUE +pwdMustChange: TRUE +pwdMaxFailure: 3 +pwdFailureCountInterval: 120 +pwdSafeModify: TRUE +pwdLockout: TRUE + +dn: cn=Another Policy, ou=Policies, dc=scapy, dc=net +objectClass: top +objectClass: device +objectClass: pwdPolicy +cn: Test Policy +pwdAttribute: 2.5.4.35 + +dn: uid=nd, ou=People, dc=scapy, dc=net +objectClass: top +objectClass: person +objectClass: inetOrgPerson +cn: Neil Dunbar +uid: nd +sn: Dunbar +givenName: Neil +userPassword: testpassword + +dn: uid=ndadmin, ou=People, dc=scapy, dc=net +objectClass: top +objectClass: person +objectClass: inetOrgPerson +cn: Neil Dunbar (Admin) +uid: ndadmin +sn: Dunbar +givenName: Neil +userPassword: testpw + +dn: uid=test, ou=People, dc=scapy, dc=net +objectClass: top +objectClass: person +objectClass: inetOrgPerson +cn: test test +uid: test +sn: Test +givenName: Test +userPassword: kfhgkjhfdgkfd +pwdPolicySubEntry: cn=No Policy, ou=Policies, dc=scapy, dc=net + +dn: uid=another, ou=People, dc=scapy, dc=net +objectClass: top +objectClass: person +objectClass: inetOrgPerson +cn: Another Test +uid: another +sn: Test +givenName: Another +userPassword: testing + diff --git a/doc/scapy/layers/ldap.rst b/doc/scapy/layers/ldap.rst index 6a14102f049..73de23432a5 100644 --- a/doc/scapy/layers/ldap.rst +++ b/doc/scapy/layers/ldap.rst @@ -4,9 +4,8 @@ LDAP Scapy fully implements the LDAPv2 / LDAPv3 messages, in addition to a very basic :class:`~scapy.layers.ldap.LDAP_Client` class. .. warning:: - *The String Representation of LDAP Search Filters* (RFC2254) is currently **unsupported**. - This means that you can't use the commonly known LDAP search syntax, and instead have to use the binary format. - PRs are welcome ! + Scapy's LDAP client is currently read-only. PRs are welcome ! + LDAP client usage ----------------- @@ -16,6 +15,7 @@ The general idea when using the :class:`~scapy.layers.ldap.LDAP_Client` class co - instantiating the class - calling :func:`~scapy.layers.ldap.LDAP_Client.connect` with the IP (this is where to specify whether to use SSL or not) - calling :func:`~scapy.layers.ldap.LDAP_Client.bind` (this is where to specify a SSP if authentication is desired) +- calling :func:`~scapy.layers.ldap.LDAP_Client.search` to search data. The simplest, unauthenticated demo of the client would be something like: @@ -172,9 +172,28 @@ Once the LDAP connection is bound, it becomes possible to perform requests. For client.sr1(LDAP_SearchRequest()).show() -Querying more complicated requests is a bit tedious, as it *currently* requires you to build the Search request yourself. +We can also use the :func:`~scapy.layers.ldap.LDAP_Client.search` passing a base DN, a filter (as specified by RFC2254) and a scope.\\ + +The scope can be one of the following: + +- 0=baseObject: only the base DN's attributes are queried +- 1=singleLevel: the base DN's children are queried +- 2=wholeSubtree: the entire subtree under the base DN is included + For instance, this corresponds to querying the DN ``CN=Users,DC=domain,DC=local`` with the filter ``(objectCategory=person)`` and asking for the attributes ``objectClass,name,description,canonicalName``: +.. code:: python + + resp = client.search( + "CN=Users,DC=domain,DC=local", + "(objectCategory=person)", + ["objectClass", "name", "description", "canonicalName"], + scope=1, # children + ) + resp.show() + +To understand exactly what's going on, note that the previous call is exactly identical to the following: + .. code:: python resp = client.sr1( @@ -199,4 +218,7 @@ For instance, this corresponds to querying the DN ``CN=Users,DC=domain,DC=local` attrsOnly=ASN1_BOOLEAN(0) ) ) - resp.show() + + +.. warning:: + Our RFC2254 parser currently does not support 'Extensible Match'. diff --git a/scapy/layers/dcerpc.py b/scapy/layers/dcerpc.py index bd3b12c030e..346de69bab2 100644 --- a/scapy/layers/dcerpc.py +++ b/scapy/layers/dcerpc.py @@ -1686,9 +1686,6 @@ def i2h(self, pkt, x): def h2i(self, pkt, x): return x - # def i2count(self, pkt, x): - # return 1 - def i2len(self, pkt, x): if x is None: return 0 @@ -2156,7 +2153,7 @@ def i2len(self, pkt, x): def any2i(self, pkt, x): # User-friendly helper if self.conformant_in_struct: - return x + return super(_NDRConfField, self).any2i(pkt, x) if self.CONFORMANT_STRING and not isinstance(x, NDRConformantString): return NDRConformantString( value=super(_NDRConfField, self).any2i(pkt, x), diff --git a/scapy/layers/kerberos.py b/scapy/layers/kerberos.py index 205c5c362f0..4b816c8c3b4 100644 --- a/scapy/layers/kerberos.py +++ b/scapy/layers/kerberos.py @@ -2261,8 +2261,7 @@ class KRB_GSS_Wrap(Packet): lambda pkt: pkt.Flags.Sealed, ) ], - XStrLenField("Data", b"", - length_from=lambda pkt: pkt.EC), + XStrLenField("Data", b"", length_from=lambda pkt: pkt.EC), ), ] @@ -2514,6 +2513,7 @@ class KerberosClient(Automaton): class MODE(IntEnum): AS_REQ = 0 TGS_REQ = 1 + GET_SALT = 2 def __init__( self, @@ -2544,7 +2544,7 @@ def __init__( if not spn: raise ValueError("Invalid spn") if realm is None: - if mode == self.MODE.AS_REQ: + if mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: _, realm = _parse_upn(upn) elif mode == self.MODE.TGS_REQ: _, realm = _parse_spn(spn) @@ -2555,7 +2555,7 @@ def __init__( else: raise ValueError("Invalid realm") - if mode == self.MODE.AS_REQ: + if mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: if not host: raise ValueError("Invalid host") elif mode == self.MODE.TGS_REQ: @@ -2574,7 +2574,17 @@ def __init__( debug=kwargs.get("debug", 0), ).ip - if etypes is None: + if mode == self.MODE.GET_SALT: + if etypes is not None: + raise ValueError("Cannot specify etypes in GET_SALT mode !") + + from scapy.libs.rfc3961 import EncryptionType + + etypes = [ + EncryptionType.AES256_CTS_HMAC_SHA1_96, + EncryptionType.AES128_CTS_HMAC_SHA1_96, + ] + elif etypes is None: from scapy.libs.rfc3961 import EncryptionType etypes = [ @@ -2594,7 +2604,7 @@ def __init__( self._port = port sock = self._connect() - if self.mode == self.MODE.AS_REQ: + if self.mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: self.host = host.upper() self.password = password and bytes_encode(password) self.spn = spn @@ -2792,7 +2802,7 @@ def BEGIN(self): @ATMT.condition(BEGIN) def should_send_as_req(self): - if self.mode == self.MODE.AS_REQ: + if self.mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]: raise self.SENT_AP_REQ() @ATMT.condition(BEGIN) @@ -2842,12 +2852,35 @@ def _process_padatas_and_key(self, padatas): salt, ) - @ATMT.receive_condition(SENT_AP_REQ) + @ATMT.receive_condition(SENT_AP_REQ, prio=0) + def receive_salt_mode(self, pkt): + # This is only for "Salt-Mode", a mode where we get the salt then + # exit. + if self.mode == self.MODE.GET_SALT: + if Kerberos not in pkt: + raise self.FINAL() + if not isinstance(pkt.root, KRB_ERROR): + log_runtime.error("Pre-auth is likely disabled !") + raise self.FINAL() + if pkt.root.errorCode == 25: # KDC_ERR_PREAUTH_REQUIRED + for padata in pkt.root.eData.seq: + if padata.padataType == 0x13: # PA-ETYPE-INFO2 + elt = padata.padataValue.seq[0] + if elt.etype.val in self.etypes: + self.result = elt.salt.val + raise self.FINAL() + else: + log_runtime.error("Failed to retrieve the salt !") + raise self.FINAL() + + @ATMT.receive_condition(SENT_AP_REQ, prio=1) def receive_krb_error_as_req(self, pkt): + # We check for a PREAUTH_REQUIRED error. This means that preauth is required + # and we need to do a second exchange. if Kerberos in pkt and isinstance(pkt.root, KRB_ERROR): if pkt.root.errorCode == 25: # KDC_ERR_PREAUTH_REQUIRED if not self.key and (not self.upn or not self.password): - log_runtime.warning( + log_runtime.error( "Got 'KDC_ERR_PREAUTH_REQUIRED', " "but no key, nor upn+pass was passed." ) @@ -2857,11 +2890,11 @@ def receive_krb_error_as_req(self, pkt): self.pre_auth = True raise self.BEGIN() else: - log_runtime.warning("Received KRB_ERROR") + log_runtime.error("Received KRB_ERROR") pkt.show() raise self.FINAL() - @ATMT.receive_condition(SENT_AP_REQ) + @ATMT.receive_condition(SENT_AP_REQ, prio=2) def receive_as_rep(self, pkt): if Kerberos in pkt and isinstance(pkt.root, KRB_AS_REP): raise self.FINAL().action_parameters(pkt) @@ -2874,7 +2907,7 @@ def retry_after_eof_in_apreq(self): self.update_sock(self._connect()) raise self.BEGIN() else: - log_runtime.warning("Socket was closed in an unexpected state") + log_runtime.error("Socket was closed in an unexpected state") raise self.FINAL() @ATMT.action(receive_as_rep) @@ -3077,6 +3110,26 @@ def krb_as_and_tgs(upn, spn, ip=None, key=None, password=None, **kwargs): ) +def krb_get_salt(upn, ip=None, realm=None, host="WIN10", **kwargs): + """ + Kerberos AS-Req only to get the salt associated with the UPN. + """ + if realm is None: + _, realm = _parse_upn(upn) + cli = KerberosClient( + mode=KerberosClient.MODE.GET_SALT, + realm=realm, + ip=ip, + spn="krbtgt/" + realm, + upn=upn, + host=host, + **kwargs, + ) + cli.run() + cli.stop() + return cli.result + + def kpasswd( upn, targetupn=None, @@ -3854,10 +3907,11 @@ def GSS_Init_sec_context( os.urandom(16), ) Context.SendSeqNum = RandNum(0, 0x7FFFFFFF)._fix() + _, crealm = _parse_upn(self.UPN) ap_req.authenticator.encrypt( Context.STSessionKey, KRB_Authenticator( - crealm=self.ST.realm, + crealm=crealm, cname=PrincipalName.fromUPN(self.UPN), # RFC 4121 checksum cksum=Checksum( diff --git a/scapy/layers/ldap.py b/scapy/layers/ldap.py index 26db10ce838..fb29c5493d5 100644 --- a/scapy/layers/ldap.py +++ b/scapy/layers/ldap.py @@ -19,8 +19,10 @@ """ import collections -import ssl +import re import socket +import ssl +import string import struct import uuid @@ -29,20 +31,25 @@ from scapy.arch import get_if_addr from scapy.ansmachine import AnsweringMachine from scapy.asn1.asn1 import ( - ASN1_STRING, + ASN1_BOOLEAN, ASN1_Class, ASN1_Codecs, + ASN1_ENUMERATED, + ASN1_INTEGER, + ASN1_STRING, ) from scapy.asn1.ber import ( - BERcodec_STRING, + BER_Decoding_Error, BER_id_dec, BER_len_dec, + BERcodec_STRING, ) from scapy.asn1fields import ( ASN1F_badsequence, ASN1F_BOOLEAN, ASN1F_CHOICE, ASN1F_ENUMERATED, + ASN1F_FLAGS, ASN1F_INTEGER, ASN1F_NULL, ASN1F_optional, @@ -50,8 +57,8 @@ ASN1F_SEQUENCE_OF, ASN1F_SEQUENCE, ASN1F_SET_OF, - ASN1F_STRING, ASN1F_STRING_PacketField, + ASN1F_STRING, ) from scapy.asn1packet import ASN1_Packet from scapy.config import conf @@ -90,6 +97,10 @@ NETLOGON_SAM_LOGON_RESPONSE_EX, ) +# Typing imports +from typing import ( + List, +) # Elements of protocol # https://datatracker.ietf.org/doc/html/rfc1777#section-4 @@ -403,17 +414,17 @@ class LDAP_UnbindRequest(ASN1_Packet): class LDAP_SubstringFilterInitial(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = LDAPString("initial", "") + ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterAny(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = LDAPString("any", "") + ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterFinal(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = LDAPString("final", "") + ASN1_root = LDAPString("val", "") class LDAP_SubstringFilterStr(ASN1_Packet): @@ -452,12 +463,19 @@ class LDAP_SubstringFilter(ASN1_Packet): class LDAP_FilterAnd(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_SET_OF("and_", [], _LDAP_Filter) + ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) class LDAP_FilterOr(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_SET_OF("or_", [], _LDAP_Filter) + ASN1_root = ASN1F_SET_OF("vals", [], _LDAP_Filter) + + +class LDAP_FilterNot(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_PACKET("val", None, None, next_cls_cb=lambda *args, **kwargs: LDAP_Filter) + ) class LDAP_FilterPresent(ASN1_Packet): @@ -475,19 +493,28 @@ class LDAP_FilterGreaterOrEqual(ASN1_Packet): ASN1_root = AttributeValueAssertion.ASN1_root -class LDAP_FilterLesserOrEqual(ASN1_Packet): +class LDAP_FilterLessOrEqual(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root -class LDAP_FilterLessOrEqual(ASN1_Packet): +class LDAP_FilterApproxMatch(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER ASN1_root = AttributeValueAssertion.ASN1_root -class LDAP_FilterApproxMatch(ASN1_Packet): +class LDAP_FilterExtensibleMatch(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = AttributeValueAssertion.ASN1_root + ASN1_root = ASN1F_SEQUENCE( + ASN1F_optional( + LDAPString("matchingRule", "", implicit_tag=0x81), + ), + ASN1F_optional( + LDAPString("type", "", implicit_tag=0x81), + ), + AttributeValue("matchValue", "", implicit_tag=0x82), + ASN1F_BOOLEAN("dnAttributes", False, implicit_tag=0x84), + ) class ASN1_Class_LDAP_Filter(ASN1_Class): @@ -502,6 +529,7 @@ class ASN1_Class_LDAP_Filter(ASN1_Class): LessOrEqual = 0xA6 Present = 0x87 # not constructed ApproxMatch = 0xA8 + ExtensibleMatch = 0xA9 class LDAP_Filter(ASN1_Packet): @@ -516,7 +544,7 @@ class LDAP_Filter(ASN1_Packet): "or_", None, LDAP_FilterOr, implicit_tag=ASN1_Class_LDAP_Filter.Or ), ASN1F_PACKET( - "not_", None, _LDAP_Filter, implicit_tag=ASN1_Class_LDAP_Filter.Not + "not_", None, LDAP_FilterNot, implicit_tag=ASN1_Class_LDAP_Filter.Not ), ASN1F_PACKET( "equalityMatch", @@ -554,8 +582,151 @@ class LDAP_Filter(ASN1_Packet): LDAP_FilterApproxMatch, implicit_tag=ASN1_Class_LDAP_Filter.ApproxMatch, ), + ASN1F_PACKET( + "extensibleMatch", + None, + LDAP_FilterExtensibleMatch, + implicit_tag=ASN1_Class_LDAP_Filter.ExtensibleMatch, + ), ) + @staticmethod + def from_rfc2254_string(filter: str): + """ + Convert a RFC-2254 filter to LDAP_Filter + """ + # Note: this code is very dumb to be readable. + _lerr = "Invalid LDAP filter string: " + if filter.lstrip()[0] != "(": + filter = "(%s)" % filter + + # 1. Cheap lexer. + tokens = [] + cur = tokens + backtrack = [] + filterlen = len(filter) + i = 0 + while i < filterlen: + c = filter[i] + i += 1 + if c in [" ", "\t", "\n"]: + # skip spaces + continue + elif c == "(": + # enclosure + cur.append([]) + backtrack.append(cur) + cur = cur[-1] + elif c == ")": + # end of enclosure + if not backtrack: + raise ValueError(_lerr + "parenthesis unmatched.") + cur = backtrack.pop(-1) + elif c in "&|!": + # and / or / not + cur.append(c) + elif c in "=": + # filtertype + if cur[-1] in "~><:": + cur[-1] += c + continue + cur.append(c) + elif c in "~><": + # comparisons + cur.append(c) + elif c == ":": + # extensible + cur.append(c) + elif c == "*": + # substring + cur.append(c) + else: + # value + v = "" + for x in filter[i - 1 :]: + if x in "():!|&~<>=*": + break + v += x + if not v: + raise ValueError(_lerr + "critical failure (impossible).") + i += len(v) - 1 + cur.append(v) + + # Check that parenthesis were closed + if backtrack: + raise ValueError(_lerr + "parenthesis unmatched.") + + # LDAP filters must have an empty enclosure () + tokens = tokens[0] + + # 2. Cheap grammar parser. + # Doing it recursively is trivial. + def _getfld(x): + if not x: + raise ValueError(_lerr + "empty enclosure.") + elif len(x) == 1 and isinstance(x[0], list): + # useless enclosure + return _getfld(x[0]) + elif x[0] in "&|": + # multinary operator + if len(x) < 3: + raise ValueError(_lerr + "bad use of multinary operator.") + return (LDAP_FilterAnd if x[0] == "&" else LDAP_FilterOr)( + vals=[LDAP_Filter(filter=_getfld(y)) for y in x[1:]] + ) + elif x[0] == "!": + # unary operator + if len(x) != 2: + raise ValueError(_lerr + "bad use of unary operator.") + return LDAP_FilterNot( + val=LDAP_Filter(filter=_getfld(x[1])), + ) + elif "=" in x and "*" in x: + # substring + if len(x) < 3 or x[1] != "=": + raise ValueError(_lerr + "bad use of substring.") + return LDAP_SubstringFilter( + type=ASN1_STRING(x[0].strip()), + filters=[ + LDAP_SubstringFilterStr( + str=( + LDAP_SubstringFilterFinal + if i == (len(x) - 3) + else LDAP_SubstringFilterInitial + if i == 0 + else LDAP_SubstringFilterAny + )(val=ASN1_STRING(y)) + ) + for i, y in enumerate(x[2:]) + if y != "*" + ], + ) + elif ":=" in x: + # extensible + raise NotImplementedError("Extensible not implemented.") + elif any(y in ["<=", ">=", "~=", "="] for y in x): + # simple + if len(x) != 3 or "=" not in x[1]: + raise ValueError(_lerr + "bad use of comparison.") + if x[2] == "*": + return LDAP_FilterPresent(present=ASN1_STRING(x[0])) + return ( + LDAP_FilterLessOrEqual + if "<=" in x + else LDAP_FilterGreaterOrEqual + if ">=" in x + else LDAP_FilterApproxMatch + if "~=" in x + else LDAP_FilterEqual + )( + attributeType=ASN1_STRING(x[0].strip()), + attributeValue=ASN1_STRING(x[2]), + ) + else: + raise ValueError(_lerr + "invalid filter.") + + return LDAP_Filter(filter=_getfld(tokens)) + class LDAP_SearchRequestAttribute(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER @@ -630,22 +801,18 @@ class LDAP_AbandonRequest(ASN1_Packet): ) -# LDAP v3 - -# RFC 4511 sect 4.1.11 - - -class LDAP_Control(ASN1_Packet): +class LDAP_SearchResponseReference(ASN1_Packet): ASN1_codec = ASN1_Codecs.BER - ASN1_root = ASN1F_SEQUENCE( - LDAPOID("controlType", ""), - ASN1F_optional( - ASN1F_BOOLEAN("criticality", False), - ), - ASN1F_optional(ASN1F_STRING("controlValue", "")), + ASN1_root = ASN1F_SEQUENCE_OF( + "uris", + [], + URI, + implicit_tag=ASN1_Class_LDAP.SearchResultReference, ) +# LDAP v3 + # RFC 4511 sect 4.12 - Extended Operation @@ -676,6 +843,72 @@ def do_dissect(self, x): return s +# RFC 4511 sect 4.1.11 + +_LDAP_CONTROLS = {} + + +class _ControlValue_Field(ASN1F_STRING_PacketField): + def m2i(self, pkt, s): + val = super(_ControlValue_Field, self).m2i(pkt, s) + if not val[0].val: + return val + controlType = pkt.controlType.val.decode() + if controlType in _LDAP_CONTROLS: + return ( + _LDAP_CONTROLS[controlType](val[0].val, _underlayer=pkt), + val[1], + ) + return val + + +class LDAP_Control(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + LDAPOID("controlType", ""), + ASN1F_optional( + ASN1F_BOOLEAN("criticality", False), + ), + ASN1F_optional(_ControlValue_Field("controlValue", "")), + ) + + +# RFC 2696 - LDAP Control Extension for Simple Paged Results Manipulation + + +class LDAP_realSearchControlValue(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_INTEGER("size", 0), + ASN1F_STRING("cookie", ""), + ) + + +_LDAP_CONTROLS["1.2.840.113556.1.4.319"] = LDAP_realSearchControlValue + + +# [MS-ADTS] + + +class LDAP_serverSDFlagsControl(ASN1_Packet): + ASN1_codec = ASN1_Codecs.BER + ASN1_root = ASN1F_SEQUENCE( + ASN1F_FLAGS( + "flags", + None, + [ + "OWNER", + "GROUP", + "DACL", + "SACL", + ], + ) + ) + + +_LDAP_CONTROLS["1.2.840.113556.1.4.801"] = LDAP_serverSDFlagsControl + + # LDAP main class @@ -692,6 +925,7 @@ class LDAP(ASN1_Packet): LDAP_SearchResponseEntry, LDAP_SearchResponseResultDone, LDAP_AbandonRequest, + LDAP_SearchResponseReference, LDAP_UnbindRequest, LDAP_ExtendedResponse, ), @@ -713,6 +947,27 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs): return conf.raw_layer return cls + @classmethod + def tcp_reassemble(cls, data, *args, **kwargs): + if len(data) < 4: + return None + # For LDAP, we would prefer to have the entire LDAP response + # (multiple LDAP concatenated) in one go, to stay consistent with + # what you get when using SASL. + remaining = data + while remaining: + try: + length, x = BER_len_dec(BER_id_dec(remaining)[1]) + except (BER_Decoding_Error, IndexError): + return None + if length and len(x) >= length: + remaining = x[length:] + if not remaining: + return cls(data) + else: + return None + return None + def hashret(self): return b"ldap" @@ -772,6 +1027,132 @@ def answers(self, other): bind_bottom_up(UDP, CLDAP, sport=389) bind_layers(UDP, CLDAP, sport=389, dport=389) +# [MS-ADTS] sect 3.1.1.2.3.3 + +LDAP_PROPERTY_SET = { + uuid.UUID( + "C7407360-20BF-11D0-A768-00AA006E0529" + ): "Domain Password & Lockout Policies", + uuid.UUID("59BA2F42-79A2-11D0-9020-00C04FC2D3CF"): "General Information", + uuid.UUID("4C164200-20C0-11D0-A768-00AA006E0529"): "Account Restrictions", + uuid.UUID("5F202010-79A5-11D0-9020-00C04FC2D4CF"): "Logon Information", + uuid.UUID("BC0AC240-79A9-11D0-9020-00C04FC2D4CF"): "Group Membership", + uuid.UUID("E45795B2-9455-11D1-AEBD-0000F80367C1"): "Phone and Mail Options", + uuid.UUID("77B5B886-944A-11D1-AEBD-0000F80367C1"): "Personal Information", + uuid.UUID("E45795B3-9455-11D1-AEBD-0000F80367C1"): "Web Information", + uuid.UUID("E48D0154-BCF8-11D1-8702-00C04FB96050"): "Public Information", + uuid.UUID("037088F8-0AE1-11D2-B422-00A0C968F939"): "Remote Access Information", + uuid.UUID("B8119FD0-04F6-4762-AB7A-4986C76B3F9A"): "Other Domain Parameters", + uuid.UUID("72E39547-7B18-11D1-ADEF-00C04FD8D5CD"): "DNS Host Name Attributes", + uuid.UUID("FFA6F046-CA4B-4FEB-B40D-04DFEE722543"): "MS-TS-GatewayAccess", + uuid.UUID("91E647DE-D96F-4B70-9557-D63FF4F3CCD8"): "Private Information", + uuid.UUID("5805BC62-BDC9-4428-A5E2-856A0F4C185E"): "Terminal Server License Server", +} + +# [MS-ADTS] sect 5.1.3.2.1 + +LDAP_CONTROL_ACCESS_RIGHTS = { + uuid.UUID("ee914b82-0a98-11d1-adbb-00c04fd8d5cd"): "Abandon-Replication", + uuid.UUID("440820ad-65b4-11d1-a3da-0000f875ae0d"): "Add-GUID", + uuid.UUID("1abd7cf8-0a99-11d1-adbb-00c04fd8d5cd"): "Allocate-Rids", + uuid.UUID("68b1d179-0d15-4d4f-ab71-46152e79a7bc"): "Allowed-To-Authenticate", + uuid.UUID("edacfd8f-ffb3-11d1-b41d-00a0c968f939"): "Apply-Group-Policy", + uuid.UUID("0e10c968-78fb-11d2-90d4-00c04f79dc55"): "Certificate-Enrollment", + uuid.UUID("a05b8cc2-17bc-4802-a710-e7c15ab866a2"): "Certificate-AutoEnrollment", + uuid.UUID("014bf69c-7b3b-11d1-85f6-08002be74fab"): "Change-Domain-Master", + uuid.UUID("cc17b1fb-33d9-11d2-97d4-00c04fd8d5cd"): "Change-Infrastructure-Master", + uuid.UUID("bae50096-4752-11d1-9052-00c04fc2d4cf"): "Change-PDC", + uuid.UUID("d58d5f36-0a98-11d1-adbb-00c04fd8d5cd"): "Change-Rid-Master", + uuid.UUID("e12b56b6-0a95-11d1-adbb-00c04fd8d5cd"): "Change-Schema-Master", + uuid.UUID("e2a36dc9-ae17-47c3-b58b-be34c55ba633"): "Create-Inbound-Forest-Trust", + uuid.UUID("fec364e0-0a98-11d1-adbb-00c04fd8d5cd"): "Do-Garbage-Collection", + uuid.UUID("ab721a52-1e2f-11d0-9819-00aa0040529b"): "Domain-Administer-Server", + uuid.UUID("69ae6200-7f46-11d2-b9ad-00c04f79f805"): "DS-Check-Stale-Phantoms", + uuid.UUID("2f16c4a5-b98e-432c-952a-cb388ba33f2e"): "DS-Execute-Intentions-Script", + uuid.UUID("9923a32a-3607-11d2-b9be-0000f87a36b2"): "DS-Install-Replica", + uuid.UUID("4ecc03fe-ffc0-4947-b630-eb672a8a9dbc"): "DS-Query-Self-Quota", + uuid.UUID("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes", + uuid.UUID("1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Get-Changes-All", + uuid.UUID( + "89e95b76-444d-4c62-991a-0facbeda640c" + ): "DS-Replication-Get-Changes-In-Filtered-Set", + uuid.UUID("1131f6ac-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Manage-Topology", + uuid.UUID( + "f98340fb-7c5b-4cdb-a00b-2ebdfa115a96" + ): "DS-Replication-Monitor-Topology", + uuid.UUID("1131f6ab-9c07-11d1-f79f-00c04fc2dcd2"): "DS-Replication-Synchronize", + uuid.UUID( + "05c74c5e-4deb-43b4-bd9f-86664c2a7fd5" + ): "Enable-Per-User-Reversibly-Encrypted-Password", + uuid.UUID("b7b1b3de-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Logging", + uuid.UUID("b7b1b3dd-ab09-4242-9e30-9980e5d322f7"): "Generate-RSoP-Planning", + uuid.UUID("7c0e2a7c-a419-48e4-a995-10180aad54dd"): "Manage-Optional-Features", + uuid.UUID("ba33815a-4f93-4c76-87f3-57574bff8109"): "Migrate-SID-History", + uuid.UUID("b4e60130-df3f-11d1-9c86-006008764d0e"): "msmq-Open-Connector", + uuid.UUID("06bd3201-df3e-11d1-9c86-006008764d0e"): "msmq-Peek", + uuid.UUID("4b6e08c3-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-computer-Journal", + uuid.UUID("4b6e08c1-df3c-11d1-9c86-006008764d0e"): "msmq-Peek-Dead-Letter", + uuid.UUID("06bd3200-df3e-11d1-9c86-006008764d0e"): "msmq-Receive", + uuid.UUID("4b6e08c2-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-computer-Journal", + uuid.UUID("4b6e08c0-df3c-11d1-9c86-006008764d0e"): "msmq-Receive-Dead-Letter", + uuid.UUID("06bd3203-df3e-11d1-9c86-006008764d0e"): "msmq-Receive-journal", + uuid.UUID("06bd3202-df3e-11d1-9c86-006008764d0e"): "msmq-Send", + uuid.UUID("a1990816-4298-11d1-ade2-00c04fd8d5cd"): "Open-Address-Book", + uuid.UUID( + "1131f6ae-9c07-11d1-f79f-00c04fc2dcd2" + ): "Read-Only-Replication-Secret-Synchronization", + uuid.UUID("45ec5156-db7e-47bb-b53f-dbeb2d03c40f"): "Reanimate-Tombstones", + uuid.UUID("0bc1554e-0a99-11d1-adbb-00c04fd8d5cd"): "Recalculate-Hierarchy", + uuid.UUID( + "62dd28a8-7f46-11d2-b9ad-00c04f79f805" + ): "Recalculate-Security-Inheritance", + uuid.UUID("ab721a56-1e2f-11d0-9819-00aa0040529b"): "Receive-As", + uuid.UUID("9432c620-033c-4db7-8b58-14ef6d0bf477"): "Refresh-Group-Cache", + uuid.UUID("1a60ea8d-58a6-4b20-bcdc-fb71eb8a9ff8"): "Reload-SSL-Certificate", + uuid.UUID("7726b9d5-a4b4-4288-a6b2-dce952e80a7f"): "Run-Protect_Admin_Groups-Task", + uuid.UUID("91d67418-0135-4acc-8d79-c08e857cfbec"): "SAM-Enumerate-Entire-Domain", + uuid.UUID("ab721a54-1e2f-11d0-9819-00aa0040529b"): "Send-As", + uuid.UUID("ab721a55-1e2f-11d0-9819-00aa0040529b"): "Send-To", + uuid.UUID("ccc2dc7d-a6ad-4a7a-8846-c04e3cc53501"): "Unexpire-Password", + uuid.UUID( + "280f369c-67c7-438e-ae98-1d46f3c6f541" + ): "Update-Password-Not-Required-Bit", + uuid.UUID("be2bb760-7f46-11d2-b9ad-00c04f79f805"): "Update-Schema-Cache", + uuid.UUID("ab721a53-1e2f-11d0-9819-00aa0040529b"): "User-Change-Password", + uuid.UUID("00299570-246d-11d0-a768-00aa006e0529"): "User-Force-Change-Password", + uuid.UUID("3e0f7e18-2c7a-4c10-ba82-4d926db99a3e"): "DS-Clone-Domain-Controller", + uuid.UUID("084c93a2-620d-4879-a836-f0ae47de0e89"): "DS-Read-Partition-Secrets", + uuid.UUID("94825a8d-b171-4116-8146-1e34d8f54401"): "DS-Write-Partition-Secrets", + uuid.UUID("4125c71f-7fac-4ff0-bcb7-f09a41325286"): "DS-Set-Owner", + uuid.UUID("88a9933e-e5c8-4f2a-9dd7-2527416b8092"): "DS-Bypass-Quota", + uuid.UUID("9b026da6-0d3c-465c-8bee-5199d7165cba"): "DS-Validated-Write-Computer", +} + +# [MS-ADTS] sect 5.1.3.2 and +# https://learn.microsoft.com/en-us/windows/win32/secauthz/directory-services-access-rights + +LDAP_DS_ACCESS_RIGHTS = { + 0x00000001: "CREATE_CHILD", + 0x00000002: "DELETE_CHILD", + 0x00000004: "LIST_CONTENTS", + 0x00000008: "WRITE_PROPERTY_EXTENDED", + 0x00000010: "READ_PROP", + 0x00000020: "WRITE_PROP", + 0x00000040: "DELETE_TREE", + 0x00000080: "LIST_OBJECT", + 0x00000100: "CONTROL_ACCESS", + 0x00010000: "DELETE", + 0x00020000: "READ_CONTROL", + 0x00040000: "WRITE_DAC", + 0x00080000: "WRITE_OWNER", + 0x00100000: "SYNCHRONIZE", + 0x01000000: "ACCESS_SYSTEM_SECURITY", + 0x80000000: "GENERIC_READ", + 0x40000000: "GENERIC_WRITE", + 0x20000000: "GENERIC_EXECUTE", + 0x10000000: "GENERIC_ALL", +} + # Small CLDAP Answering machine: [MS-ADTS] 6.3.3 - Ldap ping @@ -827,7 +1208,7 @@ def is_request(self, req): and req.filter and isinstance(req.filter.filter, LDAP_FilterAnd) and any( - x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.and_ + x.filter.attributeType.val == b"NtVer" for x in req.filter.filter.vals ) ) @@ -844,7 +1225,7 @@ def make_reply(self, req): try: DnsDomainName = next( x.filter.attributeValue.val - for x in req.protocolOp.filter.filter.and_ + for x in req.protocolOp.filter.filter.vals if x.filter.attributeType.val == b"DnsDomain" ) except StopIteration: @@ -1062,7 +1443,7 @@ def dclocator( protocolOp=LDAP_SearchRequest( filter=LDAP_Filter( filter=LDAP_FilterAnd( - and_=[ + vals=[ LDAP_Filter( filter=LDAP_FilterEqual( attributeType=ASN1_STRING(b"DnsDomain"), @@ -1165,8 +1546,7 @@ class LDAP_SASL_Buffer(Packet): # buffer." fields_desc = [ - FieldLenField("BufferLength", None, - fmt="!I", length_of="Buffer"), + FieldLenField("BufferLength", None, fmt="!I", length_of="Buffer"), _GSSAPI_Field("Buffer", LDAP), ] @@ -1191,6 +1571,22 @@ def tcp_reassemble(cls, data, *args, **kwargs): return cls(data) +class LDAP_Exception(RuntimeError): + __slots__ = ["resultCode", "diagnosticMessage"] + + def __init__(self, *args, **kwargs): + resp = kwargs.pop("resp", None) + if resp: + self.resultCode = resp.protocolOp.resultCode + self.diagnosticMessage = resp.protocolOp.diagnosticMessage.val.rstrip( + b"\x00" + ).decode(errors="backslashreplace") + else: + self.resultCode = kwargs.pop("resultCode", None) + self.diagnosticMessage = kwargs.pop("diagnosticMessage", None) + super(LDAP_Exception, self).__init__(*args, **kwargs) + + class LDAP_Client(object): """ A basic LDAP client @@ -1228,7 +1624,7 @@ class LDAP_Client(object): ssp = SPNEGOSSP([ NTLMSSP(UPN="Administrator", PASSWORD="Password1!"), KerberosSSP(UPN="Administrator@domain.local", PASSWORD="Password1!", - SPN="ldap/dc1.domain.local"), + SPN="ldap/dc1.domain.local"), ]) client.bind( LDAP_BIND_MECHS.SASL_GSS_SPNEGO, @@ -1260,6 +1656,7 @@ def __init__( self.sign = False # Session status self.sasl_wrap = False + self.bound = False self.messageID = 0 def connect(self, ip, port=None, use_ssl=False, sslcontext=None, timeout=5): @@ -1312,7 +1709,7 @@ def connect(self, ip, port=None, use_ssl=False, sslcontext=None, timeout=5): else: self.sock = StreamSocket(sock, LDAP) - def sr1(self, protocolOp, controls=None, **kwargs): + def sr1(self, protocolOp, controls: List[LDAP_Control] = None, **kwargs): self.messageID += 1 if self.verb: print(conf.color_theme.opening(">> %s" % protocolOp.__class__.__name__)) @@ -1339,10 +1736,10 @@ def sr1(self, protocolOp, controls=None, **kwargs): ) # Check for unsolicited notification if resp and LDAP in resp and resp[LDAP].unsolicited: - resp.show() if self.verb: + resp.show() print(conf.color_theme.fail("! Got unsolicited notification.")) - return resp + return resp # If signing / encryption is used, unpack if self.sasl_wrap: if resp.Buffer: @@ -1357,6 +1754,7 @@ def sr1(self, protocolOp, controls=None, **kwargs): if self.verb: if not resp: print(conf.color_theme.fail("! Bad response.")) + return else: print( conf.color_theme.success( @@ -1396,8 +1794,13 @@ def bind( self.ssp = ssp # type: SSP self.sign = sign self.encrypt = encrypt + self.sspcontext = None + + if mech is None or not isinstance(mech, LDAP_BIND_MECHS): + raise ValueError( + "'mech' attribute is required and must be one of LDAP_BIND_MECHS." + ) - assert isinstance(mech, LDAP_BIND_MECHS) if mech == LDAP_BIND_MECHS.SASL_GSSAPI: from scapy.layers.kerberos import KerberosSSP @@ -1417,11 +1820,9 @@ def bind( raise ValueError( "NTLM on LDAP does not support signing without encryption !" ) - elif mech == LDAP_BIND_MECHS.NONE: + elif mech in [LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE]: if self.sign or self.encrypt: - raise ValueError( - "Cannot use 'sign' or 'encrypt' with unauthenticated (NONE) !" - ) + raise ValueError("Cannot use 'sign' or 'encrypt' with NONE or SIMPLE !") if self.ssp is not None and mech in [ LDAP_BIND_MECHS.NONE, LDAP_BIND_MECHS.SIMPLE, @@ -1447,6 +1848,7 @@ def bind( if self.verb: resp.show() raise RuntimeError("LDAP simple bind failed !") + status = GSS_S_COMPLETE elif self.mech == LDAP_BIND_MECHS.SICILY: # [MS-ADTS] sect 5.1.1.1.3 # 1. Package Discovery @@ -1495,8 +1897,10 @@ def bind( ) ) if resp.protocolOp.resultCode != 0: - resp.show() - raise RuntimeError("Sicily response failed !") + raise LDAP_Exception( + "Sicily response failed !", + resp=resp, + ) elif self.mech in [ LDAP_BIND_MECHS.SASL_GSS_SPNEGO, LDAP_BIND_MECHS.SASL_GSSAPI, @@ -1592,9 +1996,9 @@ def bind( ) ) if resp.protocolOp.resultCode != 0: - resp.show() - raise RuntimeError( - "GSSAPI SASL failed to negotiate client security flags !" + raise LDAP_Exception( + "GSSAPI SASL failed to negotiate client security flags !", + resp=resp, ) # SASL wrapping is now available. self.sasl_wrap = self.encrypt or self.sign @@ -1604,8 +2008,139 @@ def bind( # Success. if self.verb: print("%s bind succeeded !" % self.mech.name) + self.bound = True + + _TEXT_REG = re.compile(b"^[%s]*$" % re.escape(string.printable.encode())) + + def search( + self, + baseObject: str = "", + filter: str = "", + scope=0, + derefAliases=0, + sizeLimit=3000, + timeLimit=3000, + attrsOnly=0, + attributes: List[str] = [], + controls: List[LDAP_Control] = [], + ): + """ + Perform a LDAP search. + + :param baseObject: the dn of the base object to search in. + :param filter: the filter to apply to the search (currently unsupported) + :param scope: 0=baseObject, 1=singleLevel, 2=wholeSubtree + """ + if baseObject == "rootDSE": + baseObject = "" + if filter: + filter = LDAP_Filter.from_rfc2254_string(filter) + else: + # Default filter: (objectClass=*) + filter = LDAP_Filter( + filter=LDAP_FilterPresent( + present=ASN1_STRING(b"objectClass"), + ) + ) + # we loop as we might need more than one packet thanks to paging + cookie = b"" + entries = {} + while True: + resp = self.sr1( + LDAP_SearchRequest( + filter=filter, + attributes=[ + LDAP_SearchRequestAttribute(type=ASN1_STRING(attr)) + for attr in attributes + ], + baseObject=ASN1_STRING(baseObject), + scope=ASN1_ENUMERATED(scope), + derefAliases=ASN1_ENUMERATED(derefAliases), + sizeLimit=ASN1_INTEGER(sizeLimit), + timeLimit=ASN1_INTEGER(timeLimit), + attrsOnly=ASN1_BOOLEAN(attrsOnly), + ), + controls=( + controls + + ( + [ + # This control is only usable when bound. + LDAP_Control( + controlType="1.2.840.113556.1.4.319", + criticality=True, + controlValue=LDAP_realSearchControlValue( + size=500, # paging to 500 per 500 + cookie=cookie, + ), + ) + ] + if self.bound + else [] + ) + ), + timeout=3, + ) + if LDAP_SearchResponseResultDone not in resp: + resp.show() + raise TimeoutError("Search timed out.") + # Now, reassemble the results + _s = lambda x: x.decode(errors="backslashreplace") + + def _ssafe(x): + if self._TEXT_REG.match(x): + return x.decode() + else: + return x + + # For each individual packet response + while resp: + # Find all 'LDAP' layers + if LDAP not in resp: + log_runtime.warning("Invalid response: %s", repr(resp)) + break + if LDAP_SearchResponseEntry in resp.protocolOp: + attrs = { + _s(attr.type.val): [_ssafe(x.value.val) for x in attr.values] + for attr in resp.protocolOp.attributes + } + entries[_s(resp.protocolOp.objectName.val)] = attrs + elif LDAP_SearchResponseResultDone in resp.protocolOp: + resultCode = resp.protocolOp.resultCode + if resultCode != 0x0: # != success + log_runtime.warning( + resp.protocolOp.sprintf("Got response: %resultCode%") + ) + raise LDAP_Exception( + "LDAP search failed !", + resp=resp, + ) + else: + # success + if resp.Controls: + # We have controls back + realSearchControlValue = next( + ( + c.controlValue + for c in resp.Controls + if isinstance( + c.controlValue, LDAP_realSearchControlValue + ) + ), + None, + ) + if realSearchControlValue is not None: + # has paging ! + cookie = realSearchControlValue.cookie.val + break + break + resp = resp.payload + # If we have a cookie, continue + if not cookie: + break + return entries def close(self): if self.verb: print("X Connection closed\n") self.sock.close() + self.bound = False diff --git a/scapy/layers/smb2.py b/scapy/layers/smb2.py index 4899e9dbb1c..bf4f7f7fb63 100644 --- a/scapy/layers/smb2.py +++ b/scapy/layers/smb2.py @@ -11,10 +11,11 @@ `SMB `_ """ -import os import collections import functools import hashlib +import os +import re import struct from scapy.automaton import select_objects @@ -758,7 +759,7 @@ class FileStreamInformation(Packet): class WINNT_SID_IDENTIFIER_AUTHORITY(Packet): fields_desc = [ - StrFixedLenField("Value", b"", length=6), + StrFixedLenField("Value", b"\x00\x00\x00\x00\x00\x01", length=6), ] def default_payload_class(self, payload): @@ -779,7 +780,7 @@ class WINNT_SID(Packet): ), FieldListField( "SubAuthority", - [], + [0], LEIntField("", 0), count_from=lambda pkt: pkt.SubAuthorityCount, ), @@ -788,6 +789,22 @@ class WINNT_SID(Packet): def default_payload_class(self, payload): return conf.padding_layer + _SID_REG = re.compile(r"^S-(\d)-(\d+)((?:-\d+)*)$") + + @staticmethod + def fromstr(x): + m = WINNT_SID._SID_REG.match(x) + if not m: + raise ValueError("Invalid SID format !") + rev, authority, subauthority = m.groups() + return WINNT_SID( + Revision=int(rev), + IdentifierAuthority=WINNT_SID_IDENTIFIER_AUTHORITY( + Value=struct.pack(">Q", int(authority))[2:] + ), + SubAuthority=[int(x) for x in subauthority[1:].split("-")], + ) + def summary(self): return "S-%s-%s%s" % ( self.Revision, @@ -798,6 +815,83 @@ def summary(self): ) +# https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers + +WELL_KNOWN_SIDS = { + # Universal well-known SID + "S-1-0-0": "Null SID", + "S-1-1-0": "Everyone", + "S-1-2-0": "Local", + "S-1-2-1": "Console Logon", + "S-1-3-0": "Creator Owner ID", + "S-1-3-1": "Creator Group ID", + "S-1-3-2": "Owner Server", + "S-1-3-3": "Group Server", + "S-1-3-4": "Owner Rights", + "S-1-4": "Non-unique Authority", + "S-1-5": "NT Authority", + "S-1-5-80-0": "All Services", + # NT well-known SIDs + "S-1-5-1": "Dialup", + "S-1-5-113": "Local account", + "S-1-5-114": "Local account and member of Administrators group", + "S-1-5-2": "Network", + "S-1-5-3": "Batch", + "S-1-5-4": "Interactive", + "S-1-5-6": "Service", + "S-1-5-7": "Anonymous Logon", + "S-1-5-8": "Proxy", + "S-1-5-9": "Enterprise Domain Controllers", + "S-1-5-10": "Self", + "S-1-5-11": "Authenticated Users", + "S-1-5-12": "Restricted Code", + "S-1-5-13": "Terminal Server User", + "S-1-5-14": "Remote Interactive Logon", + "S-1-5-15": "This Organization", + "S-1-5-17": "IUSR", + "S-1-5-18": "System (or LocalSystem)", + "S-1-5-19": "NT Authority (LocalService)", + "S-1-5-20": "Network Service", + "S-1-5-32-544": "Administrators", + "S-1-5-32-545": "Users", + "S-1-5-32-546": "Guests", + "S-1-5-32-547": "Power Users", + "S-1-5-32-548": "Account Operators", + "S-1-5-32-549": "Server Operators", + "S-1-5-32-550": "Print Operators", + "S-1-5-32-551": "Backup Operators", + "S-1-5-32-552": "Replicators", + "S-1-5-32-554": r"Builtin\Pre-Windows 2000 Compatible Access", + "S-1-5-32-555": r"Builtin\Remote Desktop Users", + "S-1-5-32-556": r"Builtin\Network Configuration Operators", + "S-1-5-32-557": r"Builtin\Incoming Forest Trust Builders", + "S-1-5-32-558": r"Builtin\Performance Monitor Users", + "S-1-5-32-559": r"Builtin\Performance Log Users", + "S-1-5-32-560": r"Builtin\Windows Authorization Access Group", + "S-1-5-32-561": r"Builtin\Terminal Server License Servers", + "S-1-5-32-562": r"Builtin\Distributed COM Users", + "S-1-5-32-568": r"Builtin\IIS_IUSRS", + "S-1-5-32-569": r"Builtin\Cryptographic Operators", + "S-1-5-32-573": r"Builtin\Event Log Readers", + "S-1-5-32-574": r"Builtin\Certificate Service DCOM Access", + "S-1-5-32-575": r"Builtin\RDS Remote Access Servers", + "S-1-5-32-576": r"Builtin\RDS Endpoint Servers", + "S-1-5-32-577": r"Builtin\RDS Management Servers", + "S-1-5-32-578": r"Builtin\Hyper-V Administrators", + "S-1-5-32-579": r"Builtin\Access Control Assistance Operators", + "S-1-5-32-580": r"Builtin\Remote Management Users", + "S-1-5-32-581": r"Builtin\Default Account", + "S-1-5-32-582": r"Builtin\Storage Replica Admins", + "S-1-5-32-583": r"Builtin\Device Owners", + "S-1-5-64-10": "NTLM Authentication", + "S-1-5-64-14": "SChannel Authentication", + "S-1-5-64-21": "Digest Authentication", + "S-1-5-80": "NT Service", + "S-1-5-80-0": "All Services", + "S-1-5-83-0": r"NT VIRTUAL MACHINE\Virtual Machines", +} + + # [MS-DTYP] sect 2.4.3 _WINNT_ACCESS_MASK = { @@ -818,6 +912,17 @@ def summary(self): # [MS-DTYP] sect 2.4.4.1 +WINNT_ACE_FLAGS = { + 0x01: "OBJECT_INHERIT", + 0x02: "CONTAINER_INHERIT", + 0x04: "NO_PROPAGATE_INHERIT", + 0x08: "INHERIT_ONLY", + 0x10: "INHERITED_ACE", + 0x40: "SUCCESSFUL_ACCESS", + 0x80: "FAILED_ACCESS", +} + + class WINNT_ACE_HEADER(Packet): fields_desc = [ ByteEnumField( @@ -850,15 +955,7 @@ class WINNT_ACE_HEADER(Packet): "AceFlags", 0, 8, - { - 0x01: "OBJECT_INHERIT", - 0x02: "CONTAINER_INHERIT", - 0x04: "NO_PROPAGATE_INHERIT", - 0x08: "INHERIT_ONLY", - 0x10: "INHERITED_ACE", - 0x40: "SUCCESSFUL_ACCESS", - 0x80: "FAILED_ACCESS", - }, + WINNT_ACE_FLAGS, ), LenField("AceSize", None, fmt=" conditional expression - condexpr = "" + cond_expr = None if hasattr(self.payload, "ApplicationData"): # Parse tokens res = [] @@ -925,7 +1023,23 @@ def lit(ct): raise ValueError("Unhandled token type %s" % ct.TokenType) if len(res) != 1: raise ValueError("Incomplete SDDL !") - condexpr = ";(%s)" % res[0] + cond_expr = "(%s)" % res[0] + return { + "ace-flags-string": ace_flag_string, + "sid-string": sid_string, + "mask": mask, + "object-guid": object_guid, + "inherited-object-guid": inherit_object_guid, + "cond-expr": cond_expr, + } + # fmt: on + + def toSDDL(self, accessMask=None): + """ + Return SDDL + """ + data = self.extractData(accessMask=accessMask) + ace_rights = "" # TODO if self.AceType in [0x9, 0xA, 0xB, 0xD]: # Conditional ACE conditional_ace_type = { 0x09: "XA", @@ -934,14 +1048,19 @@ def lit(ct): 0x0D: "ZA", }[self.AceType] return "D:(%s)" % ( - ";".join([ - conditional_ace_type, - ace_flag_string, - ace_rights, - object_guid, - inherit_object_guid, - sid - ]) + condexpr + ";".join( + x + for x in [ + conditional_ace_type, + data["ace-flags-string"], + ace_rights, + str(data["object-guid"]), + str(data["inherited-object-guid"]), + data["sid-string"], + data["cond-expr"], + ] + if x is not None + ) ) else: ace_type = { @@ -955,20 +1074,22 @@ def lit(ct): 0x13: "SP", }[self.AceType] return "(%s)" % ( - ";".join([ - ace_type, - ace_flag_string, - ace_rights, - object_guid, - inherit_object_guid, - sid - ]) + condexpr + ";".join( + x + for x in [ + ace_type, + data["ace-flags-string"], + ace_rights, + str(data["object-guid"]), + str(data["inherited-object-guid"]), + data["sid-string"], + data["cond-expr"], + ] + if x is not None + ) ) -# fmt: on - - # [MS-DTYP] sect 2.4.4.2 @@ -982,6 +1103,36 @@ class WINNT_ACCESS_ALLOWED_ACE(Packet): bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_ACE, AceType=0x00) +# [MS-DTYP] sect 2.4.4.3 + + +class WINNT_ACCESS_ALLOWED_OBJECT_ACE(Packet): + fields_desc = [ + FlagsField("Mask", 0, -32, _WINNT_ACCESS_MASK), + FlagsField( + "Flags", + 0, + -32, + { + 0x00000001: "OBJECT_TYPE_PRESENT", + 0x00000002: "INHERITED_OBJECT_TYPE_PRESENT", + }, + ), + ConditionalField( + UUIDField("ObjectType", None, uuid_fmt=UUIDField.FORMAT_LE), + lambda pkt: pkt.Flags.OBJECT_TYPE_PRESENT, + ), + ConditionalField( + UUIDField("InheritedObjectType", None, uuid_fmt=UUIDField.FORMAT_LE), + lambda pkt: pkt.Flags.INHERITED_OBJECT_TYPE_PRESENT, + ), + PacketField("Sid", WINNT_SID(), WINNT_SID), + ] + + +bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_OBJECT_ACE, AceType=0x05) + + # [MS-DTYP] sect 2.4.4.4 @@ -992,6 +1143,16 @@ class WINNT_ACCESS_DENIED_ACE(Packet): bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_ACE, AceType=0x01) +# [MS-DTYP] sect 2.4.4.5 + + +class WINNT_ACCESS_DENIED_OBJECT_ACE(Packet): + fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc + + +bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_OBJECT_ACE, AceType=0x06) + + # [MS-DTYP] sect 2.4.4.17.4+ @@ -1054,7 +1215,7 @@ def default_payload_class(self, payload): lambda pkt: pkt.TokenType in [ 0x10, # Unicode string 0x18, # Octet string - 0xf8, 0xf8, 0xfa, 0xfb, # Attribute tokens + 0xf8, 0xf9, 0xfa, 0xfb, # Attribute tokens 0x50, # Composite ] ), @@ -1074,7 +1235,7 @@ def default_payload_class(self, payload): StrLenFieldUtf16("value", b"", length_from=lambda pkt: pkt.length), lambda pkt: pkt.TokenType in [ 0x10, # Unicode string - 0xf8, 0xf8, 0xfa, 0xfb, # Attribute tokens + 0xf8, 0xf9, 0xfa, 0xfb, # Attribute tokens ] ), ( @@ -1091,7 +1252,7 @@ def default_payload_class(self, payload): StrFixedLenField("value", b"", length=0), ), lambda pkt: pkt.TokenType in [ - 0x01, 0x02, 0x03, 0x04, 0x10, 0x18, 0xf8, 0xf8, 0xfa, 0xfb, 0x50 + 0x01, 0x02, 0x03, 0x04, 0x10, 0x18, 0xf8, 0xf9, 0xfa, 0xfb, 0x50 ] ), ConditionalField( @@ -1154,7 +1315,7 @@ class WINNT_ACCESS_ALLOWED_CALLBACK_ACE(Packet): bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_CALLBACK_ACE, AceType=0x09) -# [MS-DTYP] sect 2.4.4.6 +# [MS-DTYP] sect 2.4.4.7 class WINNT_ACCESS_DENIED_CALLBACK_ACE(Packet): @@ -1164,15 +1325,163 @@ class WINNT_ACCESS_DENIED_CALLBACK_ACE(Packet): bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_CALLBACK_ACE, AceType=0x0A) +# [MS-DTYP] sect 2.4.4.8 + + +class WINNT_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE(Packet): + fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc + [ + PacketField( + "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA + ), + ] + + +bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, AceType=0x0B) + + +# [MS-DTYP] sect 2.4.4.9 + + +class WINNT_ACCESS_DENIED_CALLBACK_OBJECT_ACE(Packet): + fields_desc = WINNT_ACCESS_DENIED_OBJECT_ACE.fields_desc + [ + PacketField( + "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA + ), + ] + + +bind_layers(WINNT_ACE_HEADER, WINNT_ACCESS_DENIED_CALLBACK_OBJECT_ACE, AceType=0x0C) + + # [MS-DTYP] sect 2.4.4.10 -class WINNT_AUDIT_ACE(Packet): +class WINNT_SYSTEM_AUDIT_ACE(Packet): fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc -bind_layers(WINNT_ACE_HEADER, WINNT_AUDIT_ACE, AceType=0x02) +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_ACE, AceType=0x02) + + +# [MS-DTYP] sect 2.4.4.11 + + +class WINNT_SYSTEM_AUDIT_OBJECT_ACE(Packet): + # doc is wrong. + fields_desc = WINNT_ACCESS_ALLOWED_OBJECT_ACE.fields_desc + + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_OBJECT_ACE, AceType=0x07) + + +# [MS-DTYP] sect 2.4.4.12 + + +class WINNT_SYSTEM_AUDIT_CALLBACK_ACE(Packet): + fields_desc = WINNT_SYSTEM_AUDIT_ACE.fields_desc + [ + PacketField( + "ApplicationData", WINNT_APPLICATION_DATA(), WINNT_APPLICATION_DATA + ), + ] + + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_CALLBACK_ACE, AceType=0x0D) + + +# [MS-DTYP] sect 2.4.4.13 + + +class WINNT_SYSTEM_MANDATORY_LABEL_ACE(Packet): + fields_desc = WINNT_SYSTEM_AUDIT_ACE.fields_desc + + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_MANDATORY_LABEL_ACE, AceType=0x11) + + +# [MS-DTYP] sect 2.4.4.14 + + +class WINNT_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE(Packet): + fields_desc = WINNT_SYSTEM_AUDIT_OBJECT_ACE.fields_desc + + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, AceType=0x0F) + +# [MS-DTYP] sect 2.4.10.1 + + +class CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1(_NTLMPayloadPacket): + _NTLM_PAYLOAD_FIELD_NAME = "Data" + fields_desc = [ + LEIntField("NameOffset", 0), + LEShortEnumField( + "ValueType", + 0, + { + 0x0001: "CLAIM_SECURITY_ATTRIBUTE_TYPE_INT64", + 0x0002: "CLAIM_SECURITY_ATTRIBUTE_TYPE_UINT64", + 0x0003: "CLAIM_SECURITY_ATTRIBUTE_TYPE_STRING", + 0x0005: "CLAIM_SECURITY_ATTRIBUTE_TYPE_SID", + 0x0006: "CLAIM_SECURITY_ATTRIBUTE_TYPE_BOOLEAN", + 0x0010: "CLAIM_SECURITY_ATTRIBUTE_TYPE_OCTET_STRING", + }, + ), + LEShortField("Reserved", 0), + FlagsField( + "Flags", + 0, + -32, + { + 0x0001: "CLAIM_SECURITY_ATTRIBUTE_NON_INHERITABLE", + 0x0002: "CLAIM_SECURITY_ATTRIBUTE_VALUE_CASE_SENSITIVE", + 0x0004: "CLAIM_SECURITY_ATTRIBUTE_USE_FOR_DENY_ONLY", + 0x0008: "CLAIM_SECURITY_ATTRIBUTE_DISABLED_BY_DEFAULT", + 0x0010: "CLAIM_SECURITY_ATTRIBUTE_DISABLED", + 0x0020: "CLAIM_SECURITY_ATTRIBUTE_MANDATORY", + }, + ), + LEIntField("ValueCount", 0), + FieldListField( + "ValueOffsets", [], LEIntField("", 0), count_from=lambda pkt: pkt.ValueCount + ), + _NTLMPayloadField( + "Data", + lambda pkt: 16 + pkt.ValueCount * 4, + [ + ConditionalField( + StrFieldUtf16("Name", b""), + lambda pkt: pkt.NameOffset, + ), + # TODO: Values + ], + offset_name="Offset", + ), + ] + + +# [MS-DTYP] sect 2.4.4.15 + + +class WINNT_SYSTEM_RESOURCE_ATTRIBUTE_ACE(Packet): + fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc + [ + PacketField( + "AttributeData", + CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1(), + CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1, + ) + ] + + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_RESOURCE_ATTRIBUTE_ACE, AceType=0x12) + +# [MS-DTYP] sect 2.4.4.16 + + +class WINNT_SYSTEM_SCOPED_POLICY_ID_ACE(Packet): + fields_desc = WINNT_ACCESS_ALLOWED_ACE.fields_desc + +bind_layers(WINNT_ACE_HEADER, WINNT_SYSTEM_SCOPED_POLICY_ID_ACE, AceType=0x13) # [MS-DTYP] sect 2.4.5 @@ -1212,28 +1521,28 @@ class SECURITY_DESCRIPTOR(_NTLMPayloadPacket): 0x00, -16, [ - "OwnerDefaulted", - "GroupDefaulted", - "DACLPresent", - "DACLDefaulted", - "SACLPresent", - "SACLDefaulted", - "DACLTrusted", - "ServerSecurity", - "DACLComputer", - "SACLComputer", - "DACLAutoInheriter", - "SACLAutoInherited", - "DACLProtected", - "SACLProtected", - "RMControlValid", - "SelfRelative", + "OWNER_DEFAULTED", + "GROUP_DEFAULTED", + "DACL_PRESENT", + "DACL_DEFAULTED", + "SACL_PRESENT", + "SACL_DEFAULTED", + "DACL_TRUSTED", + "SERVER_SECURITY", + "DACL_COMPUTED", + "SACL_COMPUTED", + "DACL_AUTO_INHERITED", + "SACL_AUTO_INHERITED", + "DACL_PROTECTED", + "SACL_PROTECTED", + "RM_CONTROL_VALID", + "SELF_RELATIVE", ], ), LEIntField("OwnerSidOffset", 0), LEIntField("GroupSidOffset", 0), - LEIntField("SaclOffset", 0), - LEIntField("DaclOffset", 0), + LEIntField("SACLOffset", 0), + LEIntField("DACLOffset", 0), _NTLMPayloadField( "Data", OFFSET, @@ -1247,12 +1556,12 @@ class SECURITY_DESCRIPTOR(_NTLMPayloadPacket): lambda pkt: pkt.GroupSidOffset, ), ConditionalField( - PacketField("Sacl", WINNT_ACL(), WINNT_ACL), - lambda pkt: pkt.Control.SACLPresent, + PacketField("SACL", WINNT_ACL(), WINNT_ACL), + lambda pkt: pkt.Control.SACL_PRESENT, ), ConditionalField( - PacketField("Dacl", WINNT_ACL(), WINNT_ACL), - lambda pkt: pkt.Control.DACLPresent, + PacketField("DACL", WINNT_ACL(), WINNT_ACL), + lambda pkt: pkt.Control.DACL_PRESENT, ), ], offset_name="Offset", diff --git a/test/scapy/layers/kerberos.uts b/test/scapy/layers/kerberos.uts index ab37998483d..c7b9325e9ea 100644 --- a/test/scapy/layers/kerberos.uts +++ b/test/scapy/layers/kerberos.uts @@ -1294,7 +1294,7 @@ from scapy.layers.spnego import SPNEGOSSP client = SPNEGOSSP([ KerberosSSP( - UPN="User1@domain.local", + UPN="User1@DOMAIN.LOCAL", SPN="cifs/dc1", ST=KRB_Ticket(bytes.fromhex("618204a13082049da003020105a10e1b0c444f4d41494e2e4c4f43414ca2163014a003020103a10d300b1b04636966731b03646331a382046c30820468a003020112a10302010da282045a04820456671f6131b38ee6e682d62cb937b8b79c589753182f8dbcb14a91b031052a3c20f7b4c89bf9a41fe9960d112acc73f6bd6527dfe70700a3d3c2e72b4ba6705dfc040fd56f9d7cd60b580ebecec2bfb240baac619690dbd9301ed98cac037cfdff8ff96ac98358969f3532f9c6adc076d136a0ef96ebddef293df879bb42adfbf7670434f340ad673e0303ae186e1a510d7f50dbfee9ebab323c715d6b27a67ffec60dba9f7475e5dbf88eee1fcc95b7d467ab2b4ecef893a92a25c80b8480ac8c12bc10741523a2738a3d7c3d2c438235111188968486cab2934b32cad1b6b4b2cbf343b25d41ad463c0513cf21cf9f77f072f4a49d8042947064e3375a1ae76c355fd48d5fc163cf7f865af91bcb788cffe2e9e1a30a7e3f91be8fb55b0a8b8c0b600ef3e0e88feaad4fbf4fffe76c9302ee2acfa3b64ca28cd006fd4af9c27d2eb45e47e582b87e632aa23475caeb0e3e9d777339f5cb94abc19ebd080ffc78181bf81ff227182de422937675546633bd6ab688258a94d132fb590f8152d3f19bd55a1f336fb7c382140987ac2389134d8033882f923d3d5324a3e9f5437bd70f095e6bb00ee68d8f21912b19b27924c61b4e3bfe68411f9f220de8dace00e767b662313706730d4dc8539b309fc75e6ca4cae470cdf12cc3cfb191486e3e5eb8c80723b2b0473b07e4ea4d385487dd303df2db8d31f8c90d53c4adcf39ad78cf6c85fbb87b4c4ee531a42c2133df2b0362132374df995420e4b2a6d1e19d7879d518652d5101a316962b27b3884cb67d07572f96b9668ca42cca7311a7152ac7c6d492009192fa4a707989e43b2a10f19e535e7cf8afdaf63ce9a2a85ce1bd17d81cfe76d2ce5759a7fdbeff6fb279b8620bc2c5183b24be831c7ee157114f2700da210b36edb7bda7d91a32f7940bef431c76571cd44499f779cc4ebe829fb34eeaa1e442240d5962bdbcbc16c962974b546e9cee380dda49f651acc5c58acae4ad06d57e4b91d8c5557365e8ddda7ee9550963d70d4f56b44fc5a26e29b36cb21d11221825b5a2217cb1f1454d34d94a855cb860f2fe43681e3d302e7e124273dd18b04fddf660b8858e1e78d022cc03f467f3cf1a6e5df53bb831794542b1d08e38d3bfb0bf2e5ba6f75a0f77d56bf2924b144fff3c87ec7a57bff345ee8a4496676d38c9453c38e64521db2de6d6452aa8f3da1675134e8d90cbb0d274ce6189563fd9a56e56a800661e787b083950623035ddeeb2fb84f6fb2507f2c157e74e81a81970e11475ce926e393a55b06b77c444dbd23688e8a77c7f30337874fef787a187fcafd73a5a4837c8e3e60712308597ff72ea2edae69c9402ad7ae81abb3e9100f0c87b99b2564246bd56af8e6d0ecf2928e5151218f7e627c565e15540666f4f7c0e937c2d0e84782fcb1b535e596f6c4e0aed7c1d350e169d045f2eaaa4bb2f94cd149576f835e5eecb4418677d0444e51fafcbed2afac50b1d320bc223d2623601aee6df6a363a24294bfb3b00f2668dfc404e9fa17fe936e6620756a6918f7de2de343f380fab83fde911124be508")), KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("4aad1c4c7b5bf02bfd061cfaebf0188d6c4f4642d569ca4ab536cb68adcb0e68")), @@ -1451,7 +1451,7 @@ except ValueError: = Create client and server KerberosSSP (raw) client = KerberosSSP( - UPN="User1@domain.local", + UPN="User1@DOMAIN.LOCAL", SPN="cifs/dc1", ST=KRB_Ticket(bytes.fromhex("618204a13082049da003020105a10e1b0c444f4d41494e2e4c4f43414ca2163014a003020103a10d300b1b04636966731b03646331a382046c30820468a003020112a10302010da282045a04820456671f6131b38ee6e682d62cb937b8b79c589753182f8dbcb14a91b031052a3c20f7b4c89bf9a41fe9960d112acc73f6bd6527dfe70700a3d3c2e72b4ba6705dfc040fd56f9d7cd60b580ebecec2bfb240baac619690dbd9301ed98cac037cfdff8ff96ac98358969f3532f9c6adc076d136a0ef96ebddef293df879bb42adfbf7670434f340ad673e0303ae186e1a510d7f50dbfee9ebab323c715d6b27a67ffec60dba9f7475e5dbf88eee1fcc95b7d467ab2b4ecef893a92a25c80b8480ac8c12bc10741523a2738a3d7c3d2c438235111188968486cab2934b32cad1b6b4b2cbf343b25d41ad463c0513cf21cf9f77f072f4a49d8042947064e3375a1ae76c355fd48d5fc163cf7f865af91bcb788cffe2e9e1a30a7e3f91be8fb55b0a8b8c0b600ef3e0e88feaad4fbf4fffe76c9302ee2acfa3b64ca28cd006fd4af9c27d2eb45e47e582b87e632aa23475caeb0e3e9d777339f5cb94abc19ebd080ffc78181bf81ff227182de422937675546633bd6ab688258a94d132fb590f8152d3f19bd55a1f336fb7c382140987ac2389134d8033882f923d3d5324a3e9f5437bd70f095e6bb00ee68d8f21912b19b27924c61b4e3bfe68411f9f220de8dace00e767b662313706730d4dc8539b309fc75e6ca4cae470cdf12cc3cfb191486e3e5eb8c80723b2b0473b07e4ea4d385487dd303df2db8d31f8c90d53c4adcf39ad78cf6c85fbb87b4c4ee531a42c2133df2b0362132374df995420e4b2a6d1e19d7879d518652d5101a316962b27b3884cb67d07572f96b9668ca42cca7311a7152ac7c6d492009192fa4a707989e43b2a10f19e535e7cf8afdaf63ce9a2a85ce1bd17d81cfe76d2ce5759a7fdbeff6fb279b8620bc2c5183b24be831c7ee157114f2700da210b36edb7bda7d91a32f7940bef431c76571cd44499f779cc4ebe829fb34eeaa1e442240d5962bdbcbc16c962974b546e9cee380dda49f651acc5c58acae4ad06d57e4b91d8c5557365e8ddda7ee9550963d70d4f56b44fc5a26e29b36cb21d11221825b5a2217cb1f1454d34d94a855cb860f2fe43681e3d302e7e124273dd18b04fddf660b8858e1e78d022cc03f467f3cf1a6e5df53bb831794542b1d08e38d3bfb0bf2e5ba6f75a0f77d56bf2924b144fff3c87ec7a57bff345ee8a4496676d38c9453c38e64521db2de6d6452aa8f3da1675134e8d90cbb0d274ce6189563fd9a56e56a800661e787b083950623035ddeeb2fb84f6fb2507f2c157e74e81a81970e11475ce926e393a55b06b77c444dbd23688e8a77c7f30337874fef787a187fcafd73a5a4837c8e3e60712308597ff72ea2edae69c9402ad7ae81abb3e9100f0c87b99b2564246bd56af8e6d0ecf2928e5151218f7e627c565e15540666f4f7c0e937c2d0e84782fcb1b535e596f6c4e0aed7c1d350e169d045f2eaaa4bb2f94cd149576f835e5eecb4418677d0444e51fafcbed2afac50b1d320bc223d2623601aee6df6a363a24294bfb3b00f2668dfc404e9fa17fe936e6620756a6918f7de2de343f380fab83fde911124be508")), KEY=Key(EncryptionType.AES256_CTS_HMAC_SHA1_96, key=bytes.fromhex("4aad1c4c7b5bf02bfd061cfaebf0188d6c4f4642d569ca4ab536cb68adcb0e68")), diff --git a/test/scapy/layers/ldap.uts b/test/scapy/layers/ldap.uts index 91e5f5a7ade..86e452e94ce 100644 --- a/test/scapy/layers/ldap.uts +++ b/test/scapy/layers/ldap.uts @@ -104,7 +104,7 @@ assert raw(pkt2) == pkt.original = Basic CLDAP dissection & build test pkt = Ether(b'RT\x00\xbc\xe0=RT\x00y\xb1F\x08\x00E\x00\x00\xa5\x01\x1a\x00\x00\x80\x11\xc3H\xc0\xa8z\x91\xc0\xa8z\x03\xf1!\x01\x85\x00\x91o&0\x84\x00\x00\x00\x83\x02\x01\x01c\x84\x00\x00\x00z\x04\x00\n\x01\x00\n\x01\x00\x02\x01\x00\x02\x01\x00\x01\x01\x00\xa0\x84\x00\x00\x00S\xa3\x84\x00\x00\x00"\x04\tDnsDomain\x04\x15s4.howto.abartlet.net\xa3\x84\x00\x00\x00\x12\x04\x04Host\x04\nWINDOWS7-3\xa3\x84\x00\x00\x00\r\x04\x05NtVer\x04\x04\x16\x00\x00\x000\x84\x00\x00\x00\n\x04\x08Netlogon') -assert pkt.protocolOp.filter.filter.getfieldval("and_")[2].filter.attributeType == b"NtVer" +assert pkt.protocolOp.filter.filter.vals[2].filter.attributeType == b"NtVer" assert pkt.protocolOp.attributes[0].type == b"Netlogon" assert raw(pkt[CLDAP]) == b'0k\x02\x01\x01cf\x04\x00\n\x01\x00\n\x01\x00\x02\x01\x00\x02\x01\x00\x01\x01\x00\xa0G\xa3"\x04\tDnsDomain\x04\x15s4.howto.abartlet.net\xa3\x12\x04\x04Host\x04\nWINDOWS7-3\xa3\r\x04\x05NtVer\x04\x04\x16\x00\x00\x000\n\x04\x08Netlogon' @@ -215,4 +215,3 @@ pkt = NETLOGON(b'\x13\x00\\\x00\\\x00D\x00C\x001\x00\x00\x00\x00\x00D\x00O\x00M\ assert pkt.NtVersion == 1 assert pkt.UnicodeLogonServer == r"\\DC1" assert pkt.UnicodeDomainName == "DOMAIN" - diff --git a/test/scapy/layers/ldapopenldap.uts b/test/scapy/layers/ldapopenldap.uts new file mode 100644 index 00000000000..aabc2363841 --- /dev/null +++ b/test/scapy/layers/ldapopenldap.uts @@ -0,0 +1,32 @@ +% Tests that need a local instance of OpenLDAP to run + ++ Functional test against OpenLDAP +~ linux ci_only + += (OpenLDAP) connect to server, bind + +cli = LDAP_Client() +cli.connect("127.0.0.1") +cli.bind(LDAP_BIND_MECHS.SIMPLE, simple_username="cn=admin,dc=scapy,dc=net", simple_password="Bonjour1") +cli.close() + += (OpenLDAP) connect to server, bind, search + +cli = LDAP_Client() +cli.connect("127.0.0.1") +cli.bind(LDAP_BIND_MECHS.SIMPLE, simple_username="cn=admin,dc=scapy,dc=net", simple_password="Bonjour1") +res = cli.search("dc=scapy,dc=net", "(&(givenName=Another)(sn=Test))", scope=2) +cli.close() + +assert res == { + 'uid=another,ou=People,dc=scapy,dc=net': { + 'objectClass': ['top', + 'person', + 'inetOrgPerson'], + 'cn': ['Another Test'], + 'uid': ['another'], + 'sn': ['Test'], + 'givenName': ['Another'], + 'userPassword': ['testing'] + } +} diff --git a/test/scapy/layers/smb2.uts b/test/scapy/layers/smb2.uts index f15db4edd6f..7bd4969e206 100644 --- a/test/scapy/layers/smb2.uts +++ b/test/scapy/layers/smb2.uts @@ -532,7 +532,7 @@ sd = SECURITY_DESCRIPTOR(qr.Output) assert sd.OwnerSid.summary() == 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464' assert sd.GroupSid.summary() == 'S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464' -assert sd.Dacl.toSDDL() == [ +assert sd.DACL.toSDDL() == [ '(A;OI+CI;;;;S-1-15-2-1)', '(A;OI+CI+IO;;;;S-1-3-0)', '(A;OI+CI;;;;S-1-1-0)', diff --git a/test/scapy/layers/tls/tlsclientserver.uts b/test/scapy/layers/tls/tlsclientserver.uts index 7b1ba524a87..dedaba5def0 100644 --- a/test/scapy/layers/tls/tlsclientserver.uts +++ b/test/scapy/layers/tls/tlsclientserver.uts @@ -465,7 +465,7 @@ def run_tls_native_test_server(post_handshake_auth=False, def ssl_server(): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.settimeout(10) + server.settimeout(1) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0.0.0", 59000)) server.listen(5) @@ -486,6 +486,10 @@ def run_tls_native_test_server(post_handshake_auth=False, assert resp == bytes(REQS[1]) ssl_client_socket.send(bytes(RESPS[1])) # close socket + try: + ssl_client_socket.shutdown(socket.SHUT_RDWR) + finally: + ssl_client_socket.close() try: server.shutdown(socket.SHUT_RDWR) finally: @@ -522,7 +526,9 @@ def test_tls_client_native(post_handshake_auth=False, a.send(REQS[1]) pkt = a.sr1(REQS[1], timeout=1, verbose=0) assert pkt.load == b"Welcome" - # Wait + # Close + a.close() + # Wait for server to close server.join(3) assert not server.is_alive()