Import upstream version 0.4.7
Kali Janitor
1 year, 5 months ago
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: msldap |
2 | Version: 0.4.1 | |
2 | Version: 0.4.7 | |
3 | 3 | Summary: Python library to play with MS LDAP |
4 | 4 | Home-page: https://github.com/skelsec/msldap |
5 | 5 | Author: Tamas Jos |
60 | 60 | <protocol> sets the ldap protocol following values supported: |
61 | 61 | - ldap |
62 | 62 | - ldaps |
63 | - gc | |
64 | - gc_ssl | |
63 | 65 | |
64 | 66 | <auth> can be omitted if plaintext authentication is to be performed (in that case it default to ntlm-password), otherwise: |
65 | 67 | - ntlm-password |
109 | 111 | |
110 | 112 | # Kudos |
111 | 113 | Certificate services functionality was based on [certi](https://github.com/zer1t0/certi) created by @zer1t0 |
114 | AC-RN |
3 | 3 | # Tamas Jos (@skelsec) |
4 | 4 | # |
5 | 5 | |
6 | from codecs import lookup | |
7 | 6 | import copy |
8 | 7 | import asyncio |
9 | 8 | |
113 | 112 | self._serverinfo = res |
114 | 113 | self._tree = res['defaultNamingContext'] |
115 | 114 | self._ldapinfo, err = await self.get_ad_info() |
116 | self._domainsid_cache[self._ldapinfo.objectSid] = self._ldapinfo.name | |
115 | if self._con.is_anon is False: | |
116 | if err is not None: | |
117 | raise err | |
118 | self._domainsid_cache[self._ldapinfo.objectSid] = self._ldapinfo.name | |
119 | ||
117 | 120 | if self.keepalive is True: |
118 | 121 | self.__keepalive_task = asyncio.create_task(self.__keepalive()) |
119 | if err is not None: | |
120 | raise err | |
121 | 122 | return True, None |
122 | 123 | except Exception as e: |
123 | 124 | return False, e |
934 | 935 | 'servicePrincipalName': [('add', [spn])] |
935 | 936 | } |
936 | 937 | return await self._con.modify(user_dn, changes) |
938 | ||
939 | async def del_user_spn(self, user_dn, spn): | |
940 | """ | |
941 | Adds an SPN record to the user object. | |
942 | ||
943 | :param user_dn: The user's DN | |
944 | :type user_dn: str | |
945 | :param spn: The SPN to be added. It must follow the SPN string format specifications. | |
946 | :type spn: str | |
947 | :return: A tuple of (True, None) on success or (False, Exception) on error. | |
948 | :rtype: (:class:`bool`, :class:`Exception`) | |
949 | ||
950 | """ | |
951 | changes = { | |
952 | 'servicePrincipalName': [('delete', [spn])] | |
953 | } | |
954 | return await self._con.modify(user_dn, changes) | |
937 | 955 | |
938 | 956 | async def add_additional_hostname(self, user_dn, hostname): |
939 | 957 | """ |
1294 | 1312 | except Exception as e: |
1295 | 1313 | yield None, e |
1296 | 1314 | return |
1315 | ||
1316 | async def list_gmsa(self): | |
1317 | try: | |
1318 | ldap_filter = r'(objectClass=msDS-GroupManagedServiceAccount)' | |
1319 | async for entry, err in self.pagedsearch(ldap_filter, attributes = ['sAMAccountName','msDS-GroupMSAMembership', 'msDS-ManagedPassword']): | |
1320 | if err is not None: | |
1321 | yield None, err | |
1322 | return | |
1323 | yield entry['attributes'].get('sAMAccountName'), entry['attributes'].get('msDS-GroupMSAMembership'), entry['attributes'].get('msDS-ManagedPassword'), None | |
1324 | ||
1325 | except Exception as e: | |
1326 | yield None, None, None, e | |
1327 | return | |
1328 | ||
1297 | 1329 | |
1298 | 1330 | async def resolv_sd(self, sd): |
1299 | 1331 | "Resolves all SIDs found in security descriptor, returns lookup table" |
80 | 80 | raise NotImplementedError() |
81 | 81 | protocol = UniProto.CLIENT_UDP |
82 | 82 | port = 389 |
83 | elif schemes[0] == 'GC': | |
84 | protocol = UniProto.CLIENT_TCP | |
85 | port = 3268 | |
86 | elif schemes[0] == 'GC_SSL': | |
87 | protocol = UniProto.CLIENT_SSL_TCP | |
88 | port = 3269 | |
83 | 89 | else: |
84 | 90 | raise Exception('Unknown protocol! %s' % schemes[0]) |
85 | 91 |
17 | 17 | from asysocks.unicomm.common.target import UniProto |
18 | 18 | from msldap.commons.exceptions import LDAPBindException, LDAPAddException, LDAPModifyException, LDAPDeleteException |
19 | 19 | from hashlib import sha256 |
20 | from minikerberos.gssapi.channelbindings import ChannelBindingsStruct | |
21 | 20 | from asysocks.unicomm.client import UniClient |
22 | 21 | from asyauth.common.constants import asyauthProtocol |
23 | 22 | from asyauth.common.credentials import UniCredential |
23 | from asyauth.common.winapi.constants import ISC_REQ | |
24 | 24 | |
25 | 25 | class MSLDAPClientConnection: |
26 | 26 | def __init__(self, target:MSLDAPTarget, credential:UniCredential, auth=None): |
33 | 33 | |
34 | 34 | self.connected = False |
35 | 35 | self.bind_ok = False |
36 | self.is_anon = False | |
36 | 37 | self.__sign_messages = False |
37 | 38 | self.__encrypt_messages = False |
38 | 39 | self.network = None |
180 | 181 | # now processing channel binding options |
181 | 182 | if self.target.protocol == UniProto.CLIENT_SSL_TCP: |
182 | 183 | certdata = self.network.get_peer_certificate() |
183 | cb_struct = ChannelBindingsStruct() | |
184 | cb_struct.application_data = b'tls-server-end-point:' + sha256(certdata).digest() | |
185 | ||
186 | self.cb_data = cb_struct.to_bytes() | |
184 | self.cb_data = b'tls-server-end-point:' + sha256(certdata).digest() | |
187 | 185 | |
188 | 186 | self.handle_incoming_task = asyncio.create_task(self.__handle_incoming()) |
189 | 187 | logger.debug('Connection succsessful!') |
233 | 231 | logger.debug('BIND in progress...') |
234 | 232 | try: |
235 | 233 | if self.credential.protocol == asyauthProtocol.SICILY: |
236 | ||
237 | data, to_continue, err = await self.auth.authenticate(None, spn=self.target.to_target_string()) | |
234 | flags = ISC_REQ.CONNECTION|ISC_REQ.CONFIDENTIALITY|ISC_REQ.INTEGRITY | |
235 | if self.target.protocol == UniProto.CLIENT_SSL_TCP: | |
236 | flags = ISC_REQ.CONNECTION | |
237 | data, to_continue, err = await self.auth.authenticate(None, spn=self.target.to_target_string(), flags=flags, cb_data = self.cb_data) | |
238 | 238 | if err is not None: |
239 | 239 | return None, err |
240 | 240 | |
288 | 288 | res['protocolOp']['diagnosticMessage'] |
289 | 289 | ) |
290 | 290 | |
291 | data, to_continue, err = await self.auth.authenticate(res['protocolOp']['matchedDN'], spn=self.target.to_target_string()) | |
291 | data, to_continue, err = await self.auth.authenticate(res['protocolOp']['matchedDN'], spn=self.target.to_target_string(), cb_data = self.cb_data) | |
292 | 292 | if err is not None: |
293 | 293 | return None, err |
294 | 294 | |
329 | 329 | user = b'' |
330 | 330 | if self.auth.username != None: |
331 | 331 | user = self.auth.username.encode() |
332 | ||
332 | if user == b'': | |
333 | self.is_anon = True | |
334 | ||
333 | 335 | auth = { |
334 | 336 | 'simple' : pw |
335 | 337 | } |
363 | 365 | challenge = None |
364 | 366 | while True: |
365 | 367 | try: |
366 | data, to_continue, err = await self.auth.authenticate(challenge, cb_data = self.cb_data, spn=self.target.to_target_string()) | |
368 | flags = ISC_REQ.CONNECTION|ISC_REQ.CONFIDENTIALITY|ISC_REQ.INTEGRITY | |
369 | if self.target.protocol == UniProto.CLIENT_SSL_TCP: | |
370 | flags = ISC_REQ.CONNECTION | |
371 | ||
372 | data, to_continue, err = await self.auth.authenticate(challenge, cb_data = self.cb_data, spn=self.target.to_target_string(), flags=flags) | |
367 | 373 | if err is not None: |
368 | 374 | raise err |
369 | 375 | except Exception as e: |
394 | 400 | res = res.native |
395 | 401 | if res['protocolOp']['resultCode'] == 'success': |
396 | 402 | if 'serverSaslCreds' in res['protocolOp']: |
397 | data, _, err = await self.auth.authenticate(res['protocolOp']['serverSaslCreds'], cb_data = self.cb_data, spn=self.target.to_target_string()) | |
403 | data, _, err = await self.auth.authenticate(res['protocolOp']['serverSaslCreds'], cb_data = self.cb_data, spn=self.target.to_target_string(), flags=flags) | |
398 | 404 | if err is not None: |
399 | 405 | return False, err |
400 | 406 | |
401 | self.encryption_sequence_counter = self.auth.get_seq_number() | |
407 | if self.auth.encryption_needed() is True or self.auth.signing_needed() is True: | |
408 | self.encryption_sequence_counter = self.auth.get_seq_number() | |
402 | 409 | self.__bind_success() |
403 | 410 | |
404 | 411 | return True, None |
772 | 779 | #print('res') |
773 | 780 | #print(res) |
774 | 781 | return convert_attributes(res.native['protocolOp']['attributes']), None |
775 | ||
776 | ||
777 | ||
778 | ||
779 | ||
780 | ||
781 | ||
782 | ||
783 | ⏎ |
4 | 4 | # |
5 | 5 | |
6 | 6 | import asyncio |
7 | from os import terminal_size | |
8 | 7 | import traceback |
9 | 8 | import logging |
10 | 9 | import csv |
24 | 23 | from msldap.ldap_objects import MSADUser, MSADMachine, MSADUser_TSV_ATTRS |
25 | 24 | |
26 | 25 | from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR |
27 | from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK, AceFlags, ACE_OBJECT_PRESENCE | |
26 | from winacl.dtyp.ace import ACCESS_ALLOWED_OBJECT_ACE, ADS_ACCESS_MASK, AceFlags,\ | |
27 | ACE_OBJECT_PRESENCE, ACEType | |
28 | 28 | from winacl.dtyp.sid import SID |
29 | 29 | from winacl.dtyp.guid import GUID |
30 | 30 | |
31 | from msldap.ldap_objects.adcertificatetemplate import MSADCertificateTemplate, EX_RIGHT_CERTIFICATE_ENROLLMENT, CertificateNameFlag | |
31 | from msldap.ldap_objects.adcertificatetemplate import MSADCertificateTemplate,\ | |
32 | EX_RIGHT_CERTIFICATE_ENROLLMENT, CertificateNameFlag | |
32 | 33 | from msldap.wintypes.asn1.sdflagsrequest import SDFlagsRequest |
33 | 34 | |
34 | 35 | |
577 | 578 | traceback.print_exc() |
578 | 579 | return False |
579 | 580 | |
581 | async def do_delspn(self, user_dn, spn): | |
582 | """Removes an SPN entry to the users account""" | |
583 | try: | |
584 | _, err = await self.connection.del_user_spn(user_dn, spn) | |
585 | if err is not None: | |
586 | raise err | |
587 | print('SPN removed!') | |
588 | return True | |
589 | except: | |
590 | traceback.print_exc() | |
591 | return False | |
592 | ||
580 | 593 | async def do_addhostname(self, user_dn, hostname): |
581 | 594 | """Adds additional hostname to computer account""" |
582 | 595 | try: |
851 | 864 | except: |
852 | 865 | traceback.print_exc() |
853 | 866 | return False, None |
867 | ||
868 | async def do_gmsa(self): | |
869 | """Lists all managed service accounts (MSA). If user has permissions it retrieves the password as well""" | |
870 | try: | |
871 | print('---------------------------------------------') | |
872 | async for samaccountname, memberships, pwblob, err in self.connection.list_gmsa(): | |
873 | if err is not None: | |
874 | raise err | |
875 | print('Username: %s' % samaccountname) | |
876 | allowed_machines = [] | |
877 | if memberships is not None: | |
878 | for entry in memberships.Dacl.aces: | |
879 | if entry.AceType == ACEType.ACCESS_ALLOWED_ACE_TYPE: | |
880 | try: | |
881 | user = await self.do_sidresolv(entry.Sid, to_print=False) | |
882 | allowed_machines.append(user) | |
883 | except: | |
884 | allowed_machines.append(entry.Sid) | |
885 | for mname in allowed_machines: | |
886 | print('Allowed machine: %s' % mname) | |
887 | if pwblob is not None: | |
888 | print('Password: %s' % pwblob.CurrentPassword[:-2].hex()) | |
889 | print('Password -NT-: %s' % pwblob.nt_hash) | |
890 | else: | |
891 | print('Password: <EMPTY>') | |
892 | print('---------------------------------------------') | |
893 | ||
894 | except: | |
895 | traceback.print_exc() | |
896 | return False | |
854 | 897 | |
855 | 898 | async def do_test(self): |
856 | 899 | """testing, dontuse""" |
5 | 5 | from winacl.dtyp.security_descriptor import SECURITY_DESCRIPTOR |
6 | 6 | from msldap import logger |
7 | 7 | from msldap.protocol.messages import Attribute, Change, PartialAttribute |
8 | from msldap.wintypes.managedpassword import MSDS_MANAGEDPASSWORD_BLOB | |
8 | 9 | |
9 | 10 | MSLDAP_DT_WIN_EPOCH = datetime.datetime(1601, 1, 1) |
10 | 11 | |
43 | 44 | |
44 | 45 | def x2sid(x): |
45 | 46 | return str(SID.from_bytes(x[0])) |
47 | ||
48 | def x2gmsa(x): | |
49 | return MSDS_MANAGEDPASSWORD_BLOB.from_bytes(x[0]) | |
46 | 50 | |
47 | 51 | def list_x2sid(x): |
48 | 52 | t = [] |
301 | 305 | 'msPKI-Cert-Template-OID' : list_str_one, |
302 | 306 | 'msPKI-Certificate-Application-Policy' : list_str, |
303 | 307 | 'msPKI-RA-Application-Policies' : list_str, #I'm guessing here |
308 | 'msDS-ManagedPassword' : x2gmsa, | |
309 | 'msDS-GroupMSAMembership' : x2sd, | |
304 | 310 | } |
305 | 311 | |
306 | 312 | LDAP_ATTRIBUTE_TYPES_ENC = { |
0 | import io | |
1 | from unicrypto.hashlib import md4 | |
2 | ||
3 | class MSDS_MANAGEDPASSWORD_BLOB: | |
4 | def __init__(self): | |
5 | self.Version = None | |
6 | self.Reserved = None | |
7 | self.Length = None | |
8 | self.CurrentPasswordOffset = None | |
9 | self.PreviousPasswordOffset = None | |
10 | self.QueryPasswordIntervalOffset = None | |
11 | self.UnchangedPasswordIntervalOffset = None | |
12 | self.CurrentPassword = None | |
13 | self.PreviousPassword = None | |
14 | #('AlignmentPadding',':'), | |
15 | self.QueryPasswordInterval = None | |
16 | self.UnchangedPasswordInterval = None | |
17 | ||
18 | self.nt_hash = None | |
19 | ||
20 | @staticmethod | |
21 | def from_bytes(data:bytes): | |
22 | return MSDS_MANAGEDPASSWORD_BLOB.from_buffer(io.BytesIO(data)) | |
23 | ||
24 | @staticmethod | |
25 | def from_buffer(buff:io.BytesIO): | |
26 | blob = MSDS_MANAGEDPASSWORD_BLOB() | |
27 | blob.Version = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
28 | blob.Reserved = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
29 | blob.Length = int.from_bytes(buff.read(4), byteorder='little', signed=False) | |
30 | blob.CurrentPasswordOffset = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
31 | blob.PreviousPasswordOffset = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
32 | blob.QueryPasswordIntervalOffset = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
33 | blob.UnchangedPasswordIntervalOffset = int.from_bytes(buff.read(2), byteorder='little', signed=False) | |
34 | ||
35 | ppwo = blob.PreviousPasswordOffset | |
36 | if ppwo == 0: | |
37 | ppwo = blob.QueryPasswordIntervalOffset | |
38 | if blob.CurrentPasswordOffset >0: | |
39 | buff.seek(blob.CurrentPasswordOffset, 0) | |
40 | blob.CurrentPassword = buff.read(ppwo - blob.CurrentPasswordOffset) | |
41 | if blob.PreviousPasswordOffset >0: | |
42 | buff.seek(blob.PreviousPasswordOffset, 0) | |
43 | blob.PreviousPassword = buff.read(blob.QueryPasswordIntervalOffset - blob.CurrentPasswordOffset) | |
44 | if blob.QueryPasswordIntervalOffset >0: | |
45 | buff.seek(blob.QueryPasswordIntervalOffset, 0) | |
46 | blob.QueryPasswordInterval = buff.read(blob.UnchangedPasswordIntervalOffset - blob.UnchangedPasswordIntervalOffset) | |
47 | if blob.UnchangedPasswordIntervalOffset >0: | |
48 | buff.seek(blob.UnchangedPasswordIntervalOffset, 0) | |
49 | blob.UnchangedPasswordInterval = buff.read() | |
50 | ||
51 | ||
52 | blob.nt_hash = md4(blob.CurrentPassword[:-2]).hexdigest() | |
53 | return blob | |
54 | ||
55 | def __str__(self): | |
56 | t = '' | |
57 | for k in self.__dict__: | |
58 | t += '%s: %s\r\n' % (k, self.__dict__[k]) | |
59 | return t⏎ |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: msldap |
2 | Version: 0.4.1 | |
2 | Version: 0.4.7 | |
3 | 3 | Summary: Python library to play with MS LDAP |
4 | 4 | Home-page: https://github.com/skelsec/msldap |
5 | 5 | Author: Tamas Jos |
62 | 62 | msldap/protocol/ldap_filter/parser.py |
63 | 63 | msldap/protocol/ldap_filter/soundex.py |
64 | 64 | msldap/wintypes/__init__.py |
65 | msldap/wintypes/managedpassword.py | |
65 | 66 | msldap/wintypes/asn1/__init__.py |
66 | 67 | msldap/wintypes/asn1/sdflagsrequest.py⏎ |