-
Notifications
You must be signed in to change notification settings - Fork 299
Development(中文)
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使用它托管进程
WatchAD支持自定义编写添加检测模块,下面用 敏感用户组修改
作为示例来讲解如何编写自定义模块。
在目录 {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_LEVEL
简单解释一下上面的代码,
-
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
: 危害等级
-
-
run
: 运行的主入口,参数log是当前需要分析日志,因为我们指定了 4728, 4732, 4756三种日志,所以这里出现的日志也只会有这三种类型。-
self.init(log=log)
:这行代码必须在该函数最开始的地方,用于初始化当前的检测模块环境,每一个新的日志,都会重新运行一遍run
函数。 -
返回值:如果没有任何异常,返回空即可。如果发现了威胁,返回
self._generate_alert_doc()
告警文档,引擎会自动执行接下来的入库合并等操作。
-