一、前言介绍
WatchAD收集所有域控上的事件日志和kerberos流量,通过特征匹配、Kerberos协议分析、历史行为、敏感操作和蜜罐账户等方式来检测各种已知与未知威胁,功能覆盖了大部分目前的常见内网域渗透手法。该项目在360内部上线运行半年有余,发现多起威胁活动,取得了较好的效果。现决定开源系统中基于事件日志的检测部分。
目前支持的具体检测功能如下:
- 信息探测:使用SAMR查询敏感用户组、使用SAMR查询敏感用户、蜜罐账户的活动、PsLoggedOn信息收集
- 凭证盗取:Kerberoasting (流量)、AS-REP Roasting、远程Dump域控密码
- 横向移动:账户爆破、显式凭据远程登录、目标域控的远程代码执行、未知文件共享名、Kerberos票据加密方式降级(流量)、异常的Kerberos票据请求(流量)
- 权限提升:ACL修改、MS17-010攻击检测、新增组策略监控、NTLM 中继检测、基于资源的约束委派权限授予检测、攻击打印机服务 SpoolSample、未知权限提升、MS14-068攻击检测(流量)、Kerberos约束委派滥用(流量)
- 权限维持:AdminSDHolder对象修改、DCShadow攻击检测、DSRM密码重置、组策略委派权限授予检测、Kerberos约束委派权限授予检测、敏感用户组修改、域控新增系统服务、域控新增计划任务、SIDHistory属性修改、万能钥匙-主动检测、万能钥匙-被动检测(流量)、黄金票据(流量)
- 防御绕过:事件日志清空、事件日志服务被关闭
项目架构简图:
二、部署服务器端watachAD
[root@guest yum.repos.d]# yum -y update
[root@guest yum.repos.d]# yum install net-tools
[root@guest opt]# yum install git -y
[root@guest opt]# git clone https://github.com/0Kee-Team/WatchAD.git
[root@guest opt]# cd WatchAD/
[root@guest WatchAD]# yum install -y python36 #安装python3.6
[root@guest WatchAD]# yum -y install epel-release #添加源
[root@guest WatchAD]# yum install -y python36-setuptools #安装python tools插件
[root@guest WatchAD]# yum install -y python36-pip #安装pip3
[root@guest WatchAD]# pip3 install -r requirements.txt
[root@guest WatchAD]# yum -y install docker #使用yum安装dokcer
systemctl start docker.service #启动dokcer
systemctl enable docker.service #设置为开机自启动
[root@guest WatchAD]# curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # 下载docker-compose [root@guest WatchAD]#chmod +x /usr/local/bin/docker-compose # 添加可执行权限 [root@guest WatchAD]#docker-compose --versio # 查看docker-compose版本
[root@guest WatchAD]#docker-compose up #将在本地启动 rabbitmq、logstash、elasticsearch、redis、mongo服务,测试环境最好修改下dokcer-comse的docker-compose.yaml配置文件中所涉及到默认的用户名和密码 #生产环境部署:WatchAD依赖了rabbitmq、logstash、elasticsearch、redis和mongo,如果你想使用现有的存储服务和MQ等,请直接修改 {project_home}/settings/database_config.py 配置信息,数据管道Logstash的配置可参考 {project_home}/settings/logstash/logstash.conf ,实际的配置需要根据你的架构环境更改。
三、部署客服端watchAD agnet
winlogbeat.event_logs: - name: Security ignore_older: 1h output.logstash: hosts: ["本地IP地址:5044"]
3.安装和配置winlogbeat(在域控主机上安装)
前往下载对应版本的winlogbeat,建议版本为6.2,其它版本的字段可能有变动,存在不兼容的可能性。WatchAD要求下载6.2版本,其下载地址为:https://artifacts.elastic.co/downloads/beats/winlogbeat/winlogbeat-6.2.0-windows-x86_64.zip,
解压之后,使用刚才修改的配置文件 winlogbeat.yml 替换掉原本默认的配置文件 winlogbeat.yml.
接下来按照官网的教程正常安装即可(https://www.elastic.co/guide/en/beats/winlogbeat/current/winlogbeat-installation.html)。
(1).把下载的winlogbeat 6.2压缩包,解压到中C:Program Files
(2).将winlogbeat-<version>目录重命名为Winlogbeat
(3).打开安装目录下Winlogbeat目录下的winlogbeat.yml文件,把内容都删除了,然后复制测试服务器上项目watchAD下winlogbat.yml文件覆盖该文件目录下。
PS C:UsersAdministrator> cd "C:Program FilesWinlogbeat" PS C:Program FilesWinlogbeat> .install-service-winlogbeat.ps1
set-executionpolicy remotesigned
四、初始化watchAD引擎
1.wachchAD的帮助命令:
Usage: WatchAD.py <options> [settings] Options: -h, --help 显示帮助信息 --install 执行WatchAD初始化安装,在次之前请确保已完整环境安装和配置。 -d DOMAIN, --domain=DOMAIN A FQDN domain name of detection.AD服务器的域名 -s SERVER, --ldap-server=SERVER Server address for LDAP search. e.g: dc01.corp.com,服务器地址,如果域名解析了,可以域名,其实就是AD的全称主机名 -u USERNAME, --domain-user=USERNAME Username for LDAP search. e.g: CORPpeter,连接AD的账户,格式:域名\账户或者 域名账号 -p PASSWORD, --domain-passwd=PASSWORD Password for LDAP search.管理员密码 --check 检查各个数据库连接状态、消息队列状态 --start 启动检测引擎 --restart 重启检测引擎 --stop 停止引擎 (删除现有消息队列,防止数据量过大造成积压) --status 查看当前引擎状态
2.进行初始化安装
python3 WatchAD.py --install -d bks.com -s dc.bks.com -u bksadministrator -p *********
python3 WatchAD.py --start
五、部署Web监控端服务WatchAD-web
1.下载WatchAD-Web源码
[root@guest opt]# git clone https://github.com/0Kee-Team/WatchAD-Web.git
2.修改配置
修改连接数据库的配置:修改 WatchAD-web/Server/config/database_config.py文件中的数据库配置与WatchAD一致;
修改前端页面配置:把WatchAD-Web/frontend/.env.production`和`WatchAD-Web/frontend/.env.development此文件中的127.0.0.1改为WatchAD-Web所在服务器的IP。我的WatchAD和WatchAD-Web搭建在一个服务器了,所以IP一样。
3.进行编译
进到下载WatchAD-Web目录,执行:docker-compose build,如果上一步的配置有修改或者代码有变动,需要重新执行此命令,下一步的docker-compose up才会对其修改生效
docker-compose up -d
六、自定义修改
WatchAD 根目录 ├─libs 引用的部分外部库 ├─models 封装的数据对象 ├─modules 主模块目录 │ ├─alert 告警处理的相关代码 │ ├─detect 威胁检测代码 │ │ ├─event_log 基于事件日志的检测代码 │ │ │ ├─ ... ... 分类的检测代码 │ │ │ └─record 用于记录域内实体的各种活动,不告警 │ │ └─traffic_kerberos 基于kerberos流量的检测代码(本次不开源,移除) │ └─record_handle 分析时用到的其它信息操作文件 ├─scripts 安装、定时任务等用到的脚本 ├─settings 各种配置文件,获取配置信息的操作文件 ├─tools 工具函数 ├─start.py 检测引擎启动入口代码 ├─WatchAD.py 项目主程序入口代码,可进行安装、启动、停止等操作 └─supervisor.conf supervisor的配置文件,WatchAD使用它托管进程
2.自定义开发检测模块
WatchAD支持自定义编写添加检测模块,下面用 敏感用户组修改 作为示例来讲解如何编写自定义模块。
(1).新建文件
在目录 {project_home}/modules/detect/event_log下,按照威胁分类为了六个目录,record目录是用于记录域内实体的相关活动,和告警无关,暂时不用关注。
我们根据当前威胁类别,敏感用户组修改一般用于权限维持添加控制的账户,所以我们在 {project_home}/modules/detect/event_log/persistence目录下新建文件 ModifySensitiveGroup.py。
新建了文件之后,首先我们定义一下当前模块需要分析的事件日志ID列表、威胁类别代号、标题 和 简要描述模板。通过测试环境本地复现以及查阅相关文档 ,我们知道往安全组中添加用户会触发 4728、4732、4756三种事件,分别对应不同的范围。威胁类别代号和现有的不重复,且按照分类来定义,比如权限维持是以5开头。简要描述模板用于大致说明威胁活动的情况,其中用[]包裹起来的字段名对应后面的告警内容字段,在Web平台显示时会自动替换。
EVENT_ID = [4728, 4732, 4756] ALERT_CODE = "506" TITLE = "Modification of sensitive groups" DESC_TEMPLATE = "来自于 [source_ip]([source_workstation]) 使用身份 [source_user_name] 将目标用户 [target_user_name] 添加到了敏感组 [group_name] 中。"
接下来引入DetectBase类,并创建一个和文件名相同的类,继承DetectBase,实现 _generate_alert_doc 和 _get_level 方法。
from settings.config import main_config from models.Log import Log from modules.detect.DetectBase import DetectBase, HIGH_LEVEL from tools.common.common import get_cn_from_dn EVENT_ID = [4728, 4732, 4756] ALERT_CODE = "506" TITLE = "Modification of sensitive groups" DESC_TEMPLATE = "来自于 [source_ip]([source_workstation]) 使用身份 [source_user_name] 将目标用户 [target_user_name] 添加到了敏感组 [group_name] 中。" class ModifySensitiveGroup(DetectBase): def __init__(self): super().__init__(code=ALERT_CODE, title=TITLE, desc=DESC_TEMPLATE) def run(self, log: Log): # 初始化检测模块,必需 self.init(log=log) group_name = log.target_info.user_name # 动态配置的敏感组列表 sensitive_groups = list(map(lambda x: x["name"], main_config.sensitive_groups)) # 如果修改的组存在于敏感组中,则告警 if group_name in sensitive_groups: return self._generate_alert_doc() def _generate_alert_doc(self, **kwargs) -> dict: # 通过登录名和登录ID查找登录时的IP source_ip = self._get_source_ip_by_logon_id(self.log.subject_info.logon_id, self.log.subject_info.full_user_name) form_data = { # 建议必填字段内容 "source_ip": source_ip, "source_workstation": self._get_workstation_by_source_ip(source_ip), "source_user_name": self.log.subject_info.user_name, "group_name": self.log.target_info.user_name, "target_user_name": get_cn_from_dn(self.log.event_data["MemberName"]), # 以下字段内容可选 ... ... } doc = self._get_base_doc( level=self._get_level(), # 根据威胁活动代号和来源用户名唯一确定一个告警 unique_id=self._get_unique_id(self.code, self.log.subject_info.user_name), form_data=form_data ) return doc def _get_level(self) -> str: # 返回危害等级高 return HIGH_LEVE
简单解释一下上面的代码:
- models.Log :这是引擎简单封装的日志对象,将字典对象变成对象属性来访问,减少拼写错误,具体可查看Log类的代码内容。
- _get_level:顾名思义是返回当前威胁活动的危害等级,_generate_alert_doc 是返回告警内容的文档。
- _generate_alert_doc的内容比较固定:
- form_data :用于自定义保存当前威胁活动的相关信息,比如 来源IP(source_ip),来源主机名(source_workstation),来源用户名(source_user_name),目标用户名(target_user_name)等等,根据每种威胁活动会有区别,但表达相同含义的字段名必须一致,比如你不能填来源用户名的字段名为:
source_user。但有一些内容是必填的,可以通过以下思路去决定填写哪些字段:”哪个来源IP和主机,谁,做了什么,目标是谁。“,具体可参考其它检测模块的写法。 - unique_id: 用于合并重复的告警,一般是威胁活动代号 + 来源用户名或来源IP
- level: 危害等级
- form_data :用于自定义保存当前威胁活动的相关信息,比如 来源IP(source_ip),来源主机名(source_workstation),来源用户名(source_user_name),目标用户名(target_user_name)等等,根据每种威胁活动会有区别,但表达相同含义的字段名必须一致,比如你不能填来源用户名的字段名为:
- run: 运行的主入口,参数log是当前需要分析日志,因为我们指定了 4728, 4732, 4756三种日志,所以这里出现的日志也只会有这三种类型。
- self.init(log=log):这行代码必须在该函数最开始的地方,用于初始化当前的检测模块环境,每一个新的日志,都会重新运行一遍run函数。
- 返回值:如果没有任何异常,返回空即可。如果发现了威胁,返回 self._generate_alert_doc()告警文档,引擎会自动执行接下来的入库合并等操作。