Coverage for /private/tmp/im/impacket/impacket/ldap/ldap.py : 52%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # Authors: Alberto Solino (@agsolino) # Kacper Nowak (@kacpern) # # Description: # RFC 4511 Minimalistic implementation. We don't need much functionality yet # If we need more complex use cases we might opt to use a third party implementation # Keep in mind the APIs are still unstable, might require to re-write your scripts # as we change them. # Adding [MS-ADTS] specific functionality # # ToDo: # [x] Implement Paging Search, especially important for big requests #
KNOWN_CONTROLS, CONTROL_PAGEDRESULTS, NOTIFICATION_DISCONNECT, KNOWN_NOTIFICATIONS, BindRequest, SearchRequest, \ SearchResultDone, LDAPMessage
except: LOG.critical("pyOpenSSL is not installed, can't continue") raise
'LDAPConnection', 'LDAPFilterSyntaxError', 'LDAPFilterInvalidException', 'LDAPSessionError', 'LDAPSearchError', 'Control', 'SimplePagedResultsControl', 'ResultCode', 'Scope', 'DerefAliases', 'Operation', 'CONTROL_PAGEDRESULTS', 'KNOWN_CONTROLS', 'NOTIFICATION_DISCONNECT', 'KNOWN_NOTIFICATIONS', ]
# https://tools.ietf.org/search/rfc4515#section-3
""" LDAPConnection class
:param string url: :param string baseDN: :param string dstIp:
:return: a LDAP instance, if not raises a LDAPSessionError exception """
elif url.startswith('ldaps://'): self._dstPort = 636 self._SSL = True self._dstHost = url[8:] elif url.startswith('gc://'): self._dstPort = 3268 self._SSL = False self._dstHost = url[5:] else: raise LDAPSessionError(errorString="Unknown URL prefix: '%s'" % url)
# Try to connect targetHost = self._dstIp else:
except socket.error as e: raise socket.error('Connection error (%s:%d)' % (targetHost, 88), e)
else: # Switching to TLS now ctx = SSL.Context(SSL.TLSv1_METHOD) # ctx.set_cipher_list('RC4') self._socket = SSL.Connection(ctx, self._socket) self._socket.connect(sa) self._socket.do_handshake()
TGS=None, useCache=True): """ logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported.
:param string user: username :param string password: password for the user :param string domain: domain where the account is valid for (required) :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) :param struct TGT: If there's a TGT available, send the structure here and it will be used :param struct TGS: same for TGS. See smb3.py for the format :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False
:return: True, raises a LDAPSessionError if error. """
lmhash = '0' + lmhash nthash = '0' + nthash except TypeError: pass
# Importing down here so pyasn1 is not required if kerberos is not used.
useCache = False
# No cache present else: # retrieve domain information from CCache file if needed if domain == '': domain = ccache.principal.realm['data'].decode('utf-8') LOG.debug('Domain retrieved from CCache: %s' % domain)
LOG.debug('Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) principal = 'ldap/%s@%s' % (self._dstHost.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is None: # Let's try for the TGT and go from there principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) creds = ccache.getCredential(principal) if creds is not None: TGT = creds.toTGT() LOG.debug('Using TGT from cache') else: LOG.debug('No valid credentials found in cache') else: TGS = creds.toTGS(principal) LOG.debug('Using TGS from cache')
# retrieve user information from CCache file if needed if user == '' and creds is not None: user = creds['client'].prettyPrint().split(b'@')[0] LOG.debug('Username retrieved from CCache: %s' % user) elif user == '' and len(ccache.principal.components) > 0: user = ccache.principal.components[0]['data'] LOG.debug('Username retrieved from CCache: %s' % user)
# First of all, we need to get a TGT for the user aesKey, kdcHost) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] sessionKey = TGT['sessionKey']
sessionKey) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] sessionKey = TGS['sessionKey']
# Let's build a NegTokenInit with a Kerberos REQ_AP
# Kerberos
# Let's extract the ticket from the TGS
# Now let's build the AP_REQ
# Key Usage 11 # AP-REQ Authenticator (includes application authenticator # subkey), encrypted with the application session key # (Section 5.5.1)
# Done with the Kerberos saga, now let's get into LDAP
raise LDAPSessionError( errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), response['bindResponse']['diagnosticMessage']) )
""" logins into the target system
:param string user: username :param string password: password for the user :param string domain: domain where the account is valid for :param string lmhash: LMHASH used to authenticate using hashes (password is not used) :param string nthash: NTHASH used to authenticate using hashes (password is not used) :param string authenticationChoice: type of authentication protocol to use (default NTLM)
:return: True, raises a LDAPSessionError if error. """
if '.' in domain: bindRequest['name'] = user + '@' + domain elif domain: bindRequest['name'] = domain + '\\' + user else: bindRequest['name'] = user bindRequest['authentication']['simple'] = password response = self.sendReceive(bindRequest)[0]['protocolOp'] # Deal with NTLM Authentication lmhash = '0' + lmhash nthash = '0' + nthash except TypeError: pass
# NTLM Negotiate
# NTLM Challenge
# NTLM Auth else: raise LDAPSessionError(errorString="Unknown authenticationChoice: '%s'" % authenticationChoice)
raise LDAPSessionError( errorString='Error in bindRequest -> %s: %s' % (response['bindResponse']['resultCode'].prettyPrint(), response['bindResponse']['diagnosticMessage']) )
searchFilter='(objectClass=*)', attributes=None, searchControls=None, perRecordCallback=None): attributes = []
# We keep asking records until we get a SearchResultDone packet and all controls are handled else: raise LDAPSearchError( error=int(searchResult['resultCode']), errorString='Error in searchRequest -> %s: %s' % (searchResult['resultCode'].prettyPrint(), searchResult['diagnosticMessage']), answers=answers ) else: else: perRecordCallback(searchResult)
for requestControl in requestControls: if responseControls is not None: for responseControl in responseControls: if requestControl['controlType'] == CONTROL_PAGEDRESULTS: if responseControl['controlType'] == CONTROL_PAGEDRESULTS: if hasattr(responseControl, 'getCookie') is not True: responseControl = decoder.decode(encoder.encode(responseControl), asn1Spec=KNOWN_CONTROLS[CONTROL_PAGEDRESULTS]())[0] if responseControl.getCookie(): done = False requestControl.setCookie(responseControl.getCookie()) break else: # handle different controls here pass
if self._socket is not None: self._socket.close()
message['controls'].setComponents(*controls)
# We need more data else: name = message['protocolOp']['extendedResp']['responseName'] or message['responseName'] notification = KNOWN_NOTIFICATIONS.get(name, "Unsolicited Notification '%s'" % name) if name == NOTIFICATION_DISCONNECT: # Server has disconnected self.close() raise LDAPSessionError( error=int(message['protocolOp']['extendedResp']['resultCode']), errorString='%s -> %s: %s' % (notification, message['protocolOp']['extendedResp']['resultCode'].prettyPrint(), message['protocolOp']['extendedResp']['diagnosticMessage']) )
raise LDAPFilterSyntaxError("unexpected token: '%s'" % filterList[-1])
except IndexError: raise LDAPFilterSyntaxError('EOL while parsing search filter') filterList.append(c) raise LDAPFilterSyntaxError("unexpected token: '%s'" % c)
except IndexError: raise LDAPFilterSyntaxError('EOL while parsing search filter')
filters = [] while True: try: filters.append(self._consumeCompositeFilter(filterList)) except LDAPFilterSyntaxError: break
try: c = filterList.pop() except IndexError: raise LDAPFilterSyntaxError('EOL while parsing search filter') if c != ')': # filter must end with a ')' filterList.append(c) raise LDAPFilterSyntaxError("unexpected token: '%s'" % c)
return self._compileCompositeFilter(operator, filters)
except IndexError: raise LDAPFilterSyntaxError('EOL while parsing search filter') filterList.append(c) raise LDAPFilterSyntaxError("unexpected token: '%s'" % c)
except IndexError: raise LDAPFilterSyntaxError('EOL while parsing search filter') filterList.append(c) raise LDAPFilterSyntaxError("unexpected token: '('") else:
# https://tools.ietf.org/search/rfc4515#section-3 except ValueError: raise LDAPFilterInvalidException("invalid filter: '(%s)'" % filterStr)
def _compileCompositeFilter(operator, filters): searchFilter = Filter() if operator == '!': if len(filters) != 1: raise LDAPFilterInvalidException("'not' filter must have exactly one element") searchFilter['not'].setComponents(*filters) elif operator == '&': if len(filters) == 0: raise LDAPFilterInvalidException("'and' filter must have at least one element") searchFilter['and'].setComponents(*filters) elif operator == '|': if len(filters) == 0: raise LDAPFilterInvalidException("'or' filter must have at least one element") searchFilter['or'].setComponents(*filters)
return searchFilter
def _compileSimpleFilter(attribute, operator, value): match = RE_EX_ATTRIBUTE_1.match(attribute) or RE_EX_ATTRIBUTE_2.match(attribute) if not match: raise LDAPFilterInvalidException("invalid filter attribute: '%s'" % attribute) attribute, dn, matchingRule = match.groups() if attribute: searchFilter['extensibleMatch']['type'] = attribute if dn: searchFilter['extensibleMatch']['dnAttributes'] = bool(dn) if matchingRule: searchFilter['extensibleMatch']['matchingRule'] = matchingRule searchFilter['extensibleMatch']['matchValue'] = value else: raise LDAPFilterInvalidException("invalid filter attribute: '%s'" % attribute) assertions = value.split('*') choice = searchFilter['substrings']['substrings'].getComponentType() substrings = [] if assertions[0]: substrings.append(choice.clone().setComponentByName('initial', assertions[0])) for assertion in assertions[1:-1]: substrings.append(choice.clone().setComponentByName('any', assertion)) if assertions[-1]: substrings.append(choice.clone().setComponentByName('final', assertions[-1])) searchFilter['substrings']['type'] = attribute searchFilter['substrings']['substrings'].setComponents(*substrings) elif operator == '~=': searchFilter['approxMatch'].setComponents(attribute, value) elif operator == '>=': searchFilter['greaterOrEqual'].setComponents(attribute, value) elif operator == '<=': searchFilter['lessOrEqual'].setComponents(attribute, value) else: raise LDAPFilterInvalidException("invalid filter '(%s%s%s)'" % (attribute, operator, value))
""" This is the exception every client should catch """
Exception.__init__(self) self.error = error self.packet = packet self.errorString = errorString
return self.error
return self.packet
return self.errorString
return self.errorString
LDAPSessionError.__init__(self, error, packet, errorString) if answers is None: answers = [] self.answers = answers
return self.answers |