Skip to content
This repository has been archived by the owner on Jul 13, 2023. It is now read-only.

Development

9ian1i edited this page Oct 31, 2019 · 4 revisions

Project structure description

WatchAD  		    Root directory
├─libs                      Referenced libraries
├─models                    Data objects
├─modules                   Main modules
│  ├─alert        	    Alarm handle
│  ├─detect        	    Threat detections
│  │  ├─event_log           Detections based on event log
│  │  │  ├─ ... ...         Detection files
│  │  │  └─record           Record the activities of entries
│  │  └─traffic_kerberos    Detections based on kerberos traffic (not open source)
│  └─record_handle          Other Information Object Handle
├─scripts                   Scripts used for installation, crontab and etc.
├─settings                  Project profile
├─tools                     Tool functions
├─start.py                  Detection Engine Start
├─WatchAD.py                Project main program, installation, start, stop, etc.
└─supervisor.conf           Supervisor settings

Custom development detection module

WatchAD allows users to write their own add-on detection modules. The following is an example of the Modification of sensitive group module to explain how to write a custom module.

1. Create a file

In the directory {project_home}/modules/detect/event_log,there are six directories classified by threat types. The record directory, which is used to record related activities of entities in AD.

Depending on the current threat type,the Modification of sensitive group module,which is generally used to persistence and should be stored in the {project_home}/modules/detect/event_log/persistence directory. Now, let’s create a file called ModifySensitiveGroup.py in this directory.

2. Create a detection class

After creating the new file, we need to define the event log ID list, threat type code, title and description template that the current module needs to analyze. By testing the attack in the test environment and querying related documentation, we know that adding users to the security group triggers three events: 4728, 4732, and 4756.

Next, We need to determine the threat type code and it must not be duplicated with existing code. You can classify by threat type. For example, persistence starts with 5.

Finally, the description template is used to outline the status of threat activity. The field name wrapped by [] corresponds to the following alarm content field, and is automatically replaced when displayed on the web platform.

EVENT_ID = [4728, 4732, 4756]

ALERT_CODE = "506"
TITLE = "Modification of sensitive group"
DESC_TEMPLATE = "From [source_ip]([source_workstation]) added the target user [target_user_name] to the sensitive group [group_name] using the identity [source_user_name]"

After that, import DetectBase class, create a class with the same name as the file name, inherit DetectBase, implement the _generate_alert_doc and _get_level methods.

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 group"
DESC_TEMPLATE = "From [source_ip]([source_workstation]) added the target user [target_user_name] to the sensitive group [group_name] using the identity [source_user_name]"


class ModifySensitiveGroup(DetectBase):
    def __init__(self):
        super().__init__(code=ALERT_CODE, title=TITLE, desc=DESC_TEMPLATE)

    def run(self, log: Log):
        # init the module, necessary
        self.init(log=log)

        group_name = log.target_info.user_name
		
        # Dynamically configured list of sensitive groups
        sensitive_groups = list(map(lambda x: x["name"], main_config.sensitive_groups))
        # If the modified group exists in the sensitive group, the alarm is generated.
        if group_name in sensitive_groups:
            return self._generate_alert_doc()

    def _generate_alert_doc(self, **kwargs) -> dict:
        # Find the IP address at login by login name and login ID
        source_ip = self._get_source_ip_by_logon_id(self.log.subject_info.logon_id,
                                                    self.log.subject_info.full_user_name)
        form_data = {
            # Recommended necessary field
            "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"]),
            # The following fields are optional
            ... ...
        }
        doc = self._get_base_doc(
            level=self._get_level(),
            # Uniquely identify an alert based on threat activity code and source username
            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
    

Briefly explain the above code,

  • from models.Log import Log :This is a simple encapsulated log object that turns the dictionary object into an object property to access, reducing spelling errors. You can view the code content of the Log class.
  • _get_level:level of current threat activity,_generate_alert_doc return the document of the alarm content.
  • _generate_alert_doc
    • form_data :Used to customize save information about current threat activity,Such as source ip address(source_ip), source workstation name(source_workstation), source user name(source_user_name), target user name(target_user_name and so on. You can save different fields for each threat activity, but the field names that express the same meaning must be the same. For example, you can't set the field name of the source user name: source_user. However, some content is required. You can use the following ideas to decide which fields to set: "Who sourced from which IP and host, what did it do, who is the target", you can refer to the content of other detection modules.
    • unique_id: Used to merge duplicate alarms, usually threat activity code + source user name or source IP
    • level: threat level
  • run: Main function to run the module. The parameter log is the current need to analyze the log. Because we have specified 4728, 4732, 4756 three kinds of logs, so the logs appear here will only have these three types.
    • self.init(log=log):This line of code must be at the beginning of the function and for initializing the current detection module environment. Each new log will be re-run the run function.
    • return value:If there are no threat activities, return Null. If a threat is found, return the self._generate_alert_doc() alert document and the engine will automatically perform the next operations.