def _is_in_domain_list(self, domain: str) -> bool: """ 域名是否在已知需要监控的域列表中 """ domain = get_netbios_domain(domain) for each in main_config.domain_list: if domain == get_netbios_domain(each): return True return False
def ntds_settings_delete(self, log: Log): """ event_id 5141 配置名称空间内设置删除,目标非域控计算机 """ # 目标服务器为已知的域控计算机名 则忽略 patt = re.compile("^CN=NTDS Settings,CN=(.+?),.+", re.I) target_computer_name = patt.findall(log.object_info.dn) if not target_computer_name: return target_computer_name = target_computer_name[0] target_domain = log.event_data["DSName"] if target_computer_name in main_config.dc_name_list[get_netbios_domain( target_domain)]: return rule_list = [ "CN=NTDS Settings", "CN=Servers", "CN=Default-First-Site-Name", "CN=Sites", "CN=Configuration" ] if log.object_info.class_ == "nTDSDSA": for rule in rule_list: if rule.lower() not in log.object_info.dn.lower(): return # 全部命中 则告警 return {"alert_rule": SETTINGS_DELETE}
def run(self, log: Log): self.init(log=log) sid = log.subject_info.user_sid user_name = log.subject_info.user_name domain_name = log.subject_info.domain_name if domain_name.lower() == "window manager": return if not self._is_in_domain_list(domain_name): return if len(log.subject_info.user_sid.split("-")) == 4: return # 排除域控计算机账户的本地特权登录 if user_name.endswith("$"): domain = get_netbios_domain(domain_name) if user_name[:-1] in main_config.dc_name_list[domain]: return if self.account_info.check_target_is_admin_by_sid(sid=sid, domain=domain_name): return return self._generate_alert_doc()
def fake_dc_server_delete(self, log: Log): """ event_id 5141 配置名称空间内服务删除,目标非域控计算机 """ # 目标服务器为已知的域控计算机名 则忽略 patt = re.compile("^CN=(.+?),.+", re.I) target_computer_name = patt.findall(log.object_info.dn) if not target_computer_name: return target_computer_name = target_computer_name[0] target_domain = get_netbios_domain(log.event_data["DSName"]) if target_domain not in main_config.dc_name_list or target_computer_name in main_config.dc_name_list[target_domain]: return rule_list = ["CN=Servers", "CN=Default-First-Site-Name", "CN=Sites", "CN=Configuration"] if log.object_info.class_ == "server": for rule in rule_list: if rule.lower() not in log.object_info.dn.lower(): return # 四个规则全部命中 则告警 return { "alert_rule": SERVER_DELETE }
def init_sensitive_groups(domain): logger.info("init sensitive groups.") domain = get_netbios_domain(domain) ldap_search = LDAPSearch(domain) redis = RedisHelper() mongo = MongoHelper(uri=MongoConfig.uri, db=MongoConfig.db, collection=MongoConfig.settings_collection) sensitive_groups = [] for item in default_sensitive_groups(domain): if len(item["sid"]) > 0: sensitive_groups.append(item) else: entry = ldap_search.search_by_name(item["name"], attributes=["objectSid"]) if not entry or len( entry.entry_attributes_as_dict["objectSid"]) == 0: continue sid = entry.entry_attributes_as_dict["objectSid"][0] item["sid"] = sid sensitive_groups.append(item) logger.info(",".join(list(map(lambda x: x["name"], sensitive_groups)))) sensitive_entry = mongo.find_one({"name": "sensitive_entry"})["value"] sensitive_entry["group"] = sensitive_groups mongo.update_one({"name": "sensitive_entry"}, {"$set": { "value": sensitive_entry }}, upsert=True) redis.set_str_value("sensitive_entry" + REDIS_KEY_SUFFIX, simplejson.dumps(sensitive_entry))
def __init__(self, domain): self.domain = get_netbios_domain(domain) self.con = Connection( self._get_server(), user=main_config.ldap_account[self.domain]["user"], password=main_config.ldap_account[self.domain]["password"], auto_bind=True) self.domain_dn = main_config.ldap_account[self.domain]["dn"]
def scan(self): domain_list = main_config.domain_list dc_name_list_map = main_config.dc_name_list # 对所有的域进行检查 for domain in domain_list: # 对所有的域控进行检查 account = self._get_support_aes_account(domain) for dc_name in dc_name_list_map[get_netbios_domain(domain)]: self.check(domain, dc_name, account)
def run(self, log: Log): self.init(log=log) # 处于数据统计时间内,不检测 if datetime_now_obj() < main_config.learning_end_time: return if not log.source_info.ip_address: return if log.event_data["AuthenticationPackageName"] != "NTLM": return work_station = log.source_info.work_station_name netbios_name = get_netbios_domain(log.target_info.domain_name) if filter_domain(netbios_name): return # 为较小误报 目前只考虑来源主机为敏感主机的行为 if not self.account_info.computer_is_sensitive_by_name( work_station, domain=netbios_name): return ip_address = log.source_info.ip_address if ip_filter(ip_address): return # 根据主机名去查最近的认证IP last_ip = self.account_history.search_last_ip_by_workstation( work_station) if not last_ip or last_ip == "unknown" or last_ip == ip_address: return # 二次确认,如果上次认证IP与当前IP不相同,则对主机名进行解析,判断IP是否相等 resolver_ips = self._get_host_ip(log) if ip_address in resolver_ips: return if "V1" in log.event_data["LmPackageName"]: version = "v1" else: version = "v2" relay_workstation = self.account_history.get_last_workstation_by_ip( ip_address) return self._generate_alert_doc(relay_workstation=relay_workstation, ntlm_version=version, resolver_ips=resolver_ips, last_ip=last_ip)
def save_activity(self, domain, user_name, sid, dc_name, timestamp, data: dict): doc = { "domain": get_netbios_domain(domain), "user_name": user_name, "sid": sid, "activity_type": self.activity_type, "dc_name": dc_name, "@timestamp": timestamp, "data": data } index = ElasticConfig.user_activity_write_index_prefix + datetime_to_log_date( datetime_now_obj()) self.es.delay_index(body=doc, index=index, doc_type=ElasticConfig.user_activity_doc_type)
def computer_is_sensitive_by_name(self, name: str, domain: str) -> bool: """ 检查某个计算机是否为敏感 1. 域控服务器 2. 自定义敏感主机 """ domain = get_netbios_domain(domain) # 域控服务器 if name.upper() in main_config.dc_name_list[domain]: return True # 敏感计算机 sensitive_computers = list( map(lambda x: x["name"], main_config.sensitive_computers)) if name.upper() in sensitive_computers: return True return False
def init_ldap_settings(domain, server, user, password): netbios_domain = get_netbios_domain(domain) logger.info("init the ldap configuration.") if not server.startswith("ldap://"): server = "ldap://" + server mongo = MongoHelper(uri=MongoConfig.uri, db=MongoConfig.db, collection=MongoConfig.settings_collection) query = {"name": "ldap"} doc = { netbios_domain: { "server": server, "user": user, "password": password, "dn": get_dn_domain_name(domain) } } mongo.update_one(filter=query, doc={"$set": {"value": doc}}, upsert=True) redis = RedisHelper() redis.set_str_value("ldap" + REDIS_KEY_SUFFIX, simplejson.dumps(doc))
def check(self, domain, dc_name, account): nonce = getrandbits(31) current_time = time() etype = AES256 as_req = build_as_req(get_netbios_domain(domain), account, None, current_time, nonce, True, etype) kdc_dns = "{dc_name}.{domain}".format(dc_name=dc_name, domain=domain) try: sock = send_req(as_req, kdc_dns, ) data = recv_rep(sock) err_enc = decode(data, asn1Spec=KrbError())[0] print(err_enc['error-code']) if err_enc['error-code'] == KDC_ERR_ETYPE_NOSUPP: # TODO 发现万能钥匙 return except timeout: pass except ConnectionRefusedError: pass except Exception as e: traceback.print_exc()
def run(self, log: Log): if "AllowedToDelegateTo" not in log.event_data: return if log.event_data["AllowedToDelegateTo"] == "-": return allowed_to_list = _parse_to_list(log.event_data["AllowedToDelegateTo"]) netbios_name = get_netbios_domain(log.target_info.domain_name) record = self.delegation.find_constrained_delegation_by_sid( log.target_info.sid) # 更新记录 if record and record["delegation_type"] == CONSTRAINED_DELEGATION: if record["allowed_to"] == allowed_to_list: return else: self.delegation.update_delegation( sid=log.target_info.sid, delegation_type=CONSTRAINED_DELEGATION, allowed_to=allowed_to_list) else: self.delegation.new_delegation_record( user=User(log.target_info.__dict__), delegation_type=CONSTRAINED_DELEGATION, allowed_to=allowed_to_list) new_delegation_list = [] for dele in allowed_to_list: if dele not in record["allowed_to"]: new_delegation_list.append(dele) # 查找新增高危约束委派 high_risk_spn = self._check_high_risk_spn(new_delegation_list, netbios_name) if len(high_risk_spn) == 0: return return self._generate_alert_doc( allowed_to_delegate_to=allowed_to_list, new_delegation_list=new_delegation_list)
def replication_monitoring(self, log: Log): """ event_id 4928 源命名上下文创建,发起来源非域控计算机 """ patt = re.compile("^CN=NTDS Settings,CN=(.+?),.+", re.I) source_computer = patt.findall(log.event_data["SourceDRA"]) source_domain = get_domain_from_dn(log.event_data["SourceDRA"]) if not source_computer: return source_computer = source_computer[0] netbios_domain = get_netbios_domain(source_domain) if netbios_domain not in main_config.dc_name_list or source_computer in main_config.dc_name_list[netbios_domain]: return # 如果当前的源地址不在已知的DC列表中,则告警 return { "alert_rule": REPLICATION_MONITORING, "source_workstation": source_computer }
def spn_modify(self, log: Log): """ event_id 4742 非域控计算机修改SPN为异常值 """ # 目标服务器为已知的域控计算机名 则忽略 target_computer_name = log.target_info.user_name[:-1] target_domain = get_netbios_domain(log.target_info.domain_name) if target_domain not in main_config.dc_name_list or target_computer_name in main_config.dc_name_list[target_domain]: return spn_list = log.event_data["ServicePrincipalNames"].split("\n\t\t") for spn in spn_list: if spn.startswith("GC/"): return { "alert_rule": SPN_MODIFY, "modify_computer": target_computer_name } if spn.startswith("E3514235-4B06-11D1-AB04-00C04FC2DCD2/"): return { "alert_rule": SPN_MODIFY, "modify_computer": target_computer_name }
def get_all_dc_names(domain: str): """ 将DC列表入库 """ domain = get_netbios_domain(domain) logger.info("Search all domain controllers using LDAP.") dc_name_list = [] ldap_search = LDAPSearch(domain) dc_list = ldap_search.search_domain_controller() for each in dc_list: dc_name = str(each["cn"]) dc_name_list.append(dc_name) mongo = MongoHelper(MongoConfig.uri, MongoConfig.db, MongoConfig.settings_collection) doc = {domain: dc_name_list} logger.info(",".join(dc_name_list)) logger.info( "domain controller count: {count}".format(count=len(dc_name_list))) logger.info("Save all domain controllers to settings.") mongo.update_one({"name": "dc_name_list"}, {"$set": {"value": doc}}, True) redis = RedisHelper() redis.set_str_value("dc_name_list" + REDIS_KEY_SUFFIX, simplejson.dumps(doc))
def run(self, log: Log): self.init(log=log) if log.event_data[ "AttributeLDAPDisplayName"] != "msDS-AllowedToActOnBehalfOfOtherIdentity": return # 只检测敏感计算机 account = get_cn_from_dn(log.object_info.dn) domain = get_domain_from_dn(log.object_info.dn) if not self.account_info.computer_is_sensitive_by_name( account, domain=get_netbios_domain(domain)): return ldap = LDAPSearch(domain=domain) entry = ldap.search_by_cn( cn=account, attributes=["sid", "msDS-AllowedToActOnBehalfOfOtherIdentity"]) if entry is None: return entry_sid = str(entry["sid"]) sd = SR_SECURITY_DESCRIPTOR(entry.entry_attributes_as_dict[ "msDS-AllowedToActOnBehalfOfOtherIdentity"][0]) # 拥有特殊DACL权限的SID列表 ace_list = [] for ace in sd["Dacl"].aces: ace_list.append({ "type_name": ace["TypeName"], "sid": ace['Ace']['Sid'].formatCanonical() }) sid_list = list(map(lambda ace: ace["sid"], ace_list)) sid_list = sorted(list(set(sid_list))) target_account_info = User({ "user_name": account, "user_sid": entry_sid }) # 查询历史委派记录 record = self.delegation.find_res_constrained_delegation_by_name( name=account) # 不存在记录 则新建 并直接告警 if not record: self.delegation.new_delegation_record( user=target_account_info, delegation_type=RES_BASED_CONSTRAINED_DELEGATION, allowed_to=sid_list) return self._generate_alert_doc( target_computer=account, target_user_name=target_account_info.user_name, target_user_sid=target_account_info.user_sid, add_allowed_sid=sid_list, old_allowed_sid=[]) # 存在记录且不变,退出 if sid_list == record["allowed_to"]: return # 存在记录 对比历史的sid 无新增 更新记录 退出 new_sids = self._get_new_sid(new_list=sid_list, old_list=record["allowed_to"]) if len(new_sids) == 0: self.delegation.update_delegation( sid=entry_sid, delegation_type=RES_BASED_CONSTRAINED_DELEGATION, allowed_to=sid_list) return # 存在记录 有新增 更新记录 告警 if len(new_sids) > 0: self.delegation.update_delegation( sid=entry_sid, delegation_type=RES_BASED_CONSTRAINED_DELEGATION, allowed_to=sid_list) return self._generate_alert_doc( target_computer=account, target_user_name=target_account_info.user_name, target_user_sid=target_account_info.user_sid, add_allowed_sid=new_sids, old_allowed_sid=record["allowed_to"])