Skip to content

New Analyzer & Responders Watcher #1353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions analyzers/Watcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# [Watcher](https://github.com/thalesgroup-cert/Watcher)

## Watcher Check Domain Analyzer

### Description
The Watcher Check Domain Analyzer is an Analyzer for TheHive/Cortex that checks if a given domain is already being monitored in the Watcher website monitoring system.

### Features
- **Check if a domain is monitored**: Verifies whether a specific domain is already being monitored in the Watcher system.

### Prerequisites
- Access to the Watcher API
- A valid API key for Watcher
- A functional instance of Cortex and TheHive

### Installation
- Add the configuration files for this analyzer to your Cortex configuration.

### Configuration
In Cortex, configure the following parameters for the Analyzer:

| Parameter | Description | Required | Default Value |
|--------------------|--------------------------------------------------------------------|----------|----------------|
| `watcher_url` | URL of Watcher (e.g. `https://example.watcher.local:9002`) | Yes | - |
| `watcher_api_key` | API key for authenticating | Yes | - |

### Usage
When a domain artifact is submitted to this analyzer, it will:
1. Query the Watcher API to check if the domain is already monitored.
2. Return a report with either the monitoring status of the domain or an indication that it is not yet monitored.

### Example JSON Response
#### Domain is already monitored
```json
{
"status": "Monitored",
"Message": "Domain 'example.com' is already monitored in Watcher.",
"ticket_id": "12345"
}
```

#### Domain is not monitored
```json
{
"status": "Not Monitored",
"Message": "Domain 'example.com' is not monitored in Watcher. You can add it using the Watcher responder."
}
```

### Template Setup in TheHive

To customize the display of analyzer results in TheHive, you can use **analyzer templates**.

Follow these steps to install the templates for the `Watcher_CheckDomain` analyzer:

1. Navigate to **TheHive** web interface
2. Go to **Admin** > **Entities Management** > **Analyzer templates**
3. Click on **Import templates**
4. Browse to the template directory
5. Select both `short.html` and `long.html` files
6. Click **Import**
7. Make sure the templates are correctly associated with the `Watcher_CheckDomain` analyzer

Once done, TheHive will use these templates to display the analyzer output with better readability and style.

### Author

**Thales Group CERT** - [thalesgroup-cert on GitHub](https://github.com/thalesgroup-cert)
**Ygal NEZRI** - [@ygalnezri](https://github.com/ygalnezri)
26 changes: 26 additions & 0 deletions analyzers/Watcher/Watcher_CheckDomain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "Watcher_CheckDomain",
"version": "1.3",
"author": "THA-CERT // YNE",
"url": "-",
"license": "AGPL-V3",
"description": "Checks if a domain is monitored in Watcher.",
"dataTypeList": ["domain"],
"command": "Watcher/watcher.py",
"baseConfig": "Watcher",
"configurationItems": [
{
"name": "watcher_url",
"description": "URL of Watcher.",
"type": "string",
"required": true
},
{
"name": "watcher_api_key",
"description": "API key used for authenticating requests to Watcher.",
"type": "string",
"required": true
}
]
}

2 changes: 2 additions & 0 deletions analyzers/Watcher/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests
149 changes: 149 additions & 0 deletions analyzers/Watcher/watcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#!/usr/bin/env python3

# Author: THA-CERT // YNE

import requests
import json
from cortexutils.analyzer import Analyzer


class Watcher_CheckDomain(Analyzer):

def __init__(self):
super(Watcher_CheckDomain, self).__init__()

# Load URL and API key from config
base_url = self.get_param("config.watcher_url", None, "Watcher URL is missing.")
self.watcher_url = f"{base_url.rstrip('/')}/api/site_monitoring/site/"
self.watcher_api_key = self.get_param("config.watcher_api_key", None, "Watcher API key is missing.")

# Set headers
self.headers = {
"Authorization": f"Token {self.watcher_api_key}",
"Content-Type": "application/json"
}

def check_domain_status(self, domain):
"""Check if the domain is already being monitored in Watcher and return all relevant info."""
try:
response = requests.get(
self.watcher_url,
headers=self.headers,
verify=False
)
response.raise_for_status()
sites = response.json()

# Domain found
for site in sites:
site_domain = str(site.get("domain_name", "")).lower().lstrip("www.")
input_domain = domain.lower().lstrip("www.")

if site_domain == input_domain:
mx_list = []
mx_raw = site.get("MX_records")

# Process MX records if present
if mx_raw:
try:
entries = mx_raw if isinstance(mx_raw, list) else mx_raw.strip("[]").split(",")

for entry in entries:
if entry and str(entry).strip():
mx_clean = str(entry).split()[-1].strip(" .'\"]")
if mx_clean:
mx_list.append(mx_clean)
except Exception as e:
self.error(f"Failed to parse MX_records: {str(e)}")


return {
"status": "Monitored",
"Message": f"Domain '{domain}' is already monitored by Watcher.",
"Ticket ID": site.get("ticket_id") or "-",
"Ip": site.get("ip") or "-",
"Ip Second": site.get("ip_second") or "-",
"MX Records": mx_list or "-",
"Mail Server": site.get("mail_A_record_ip") or "-"
}

# Domain not found
return {
"status": "Not Monitored",
"Message": f"Domain '{domain}' is not monitored by Watcher."
}

except requests.exceptions.RequestException as e:
self.error(f"API request error while checking monitored domains: {str(e)}")
return {
"status": "Error",
"Message": f"Failed to query Watcher: {str(e)}"
}

def summary(self, raw):
"""Generate a summary for TheHive taxonomies."""
taxonomies = []
namespace = "Watcher"
predicate = "Check"
status = raw.get("status", "Not Monitored")

level = "safe" if status == "Monitored" else "info"
taxonomies.append(self.build_taxonomy(level, namespace, predicate, status))

return {"taxonomies": taxonomies}

def artifacts(self, raw):
"""Generate artifacts for TheHive."""
artifacts = []

if raw.get("status") != "Monitored":
return artifacts

# Add IPs
for field in ["Ip", "Ip Second", "Mail Server"]:
ip = raw.get(field)
if ip and ip != "-":
artifacts.append(self.build_artifact("ip", ip))

# Add MX Records
for mx in raw.get("MX Records", []):
if mx and mx != "-":
if "." in mx:
parts = mx.split('.')
if len(parts) > 2:
artifacts.append(self.build_artifact("fqdn", mx))
else:
artifacts.append(self.build_artifact("domain", mx))
else:
artifacts.append(self.build_artifact("other", mx))

return artifacts

def run(self):
try:
data = self.get_data()
if not data:
self.error("No data received from Cortex. Cannot proceed.")
return

if isinstance(data, str):
try:
if data.strip() and not data.startswith("{"):
data = json.loads(f'{{"data": "{data}"}}')
except json.JSONDecodeError as e:
self.error(f"Invalid JSON received from Cortex. Input received: {data}. Error: {str(e)}")
return

domain = data.get("data")
if not domain or not isinstance(domain, str):
self.error("Invalid input: Domain name is missing or not a string.")
return

result = self.check_domain_status(domain)
self.report(result)

except Exception as e:
self.error(f"Unexpected error: {str(e)}")

if __name__ == "__main__":
Watcher_CheckDomain().run()
55 changes: 55 additions & 0 deletions responders/Watcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# [Watcher](https://github.com/thalesgroup-cert/Watcher)

## Watcher Monitor Manager Responder

### Description
Watcher Monitor Manager is a Responder for TheHive/Cortex that allows adding or removing a domain from monitoring in the Watcher website monitoring module.

### Features
- **Add a domain to monitoring** (`WatcherAddDomain`)
- **Remove a domain from monitoring** (`WatcherRemoveDomain`)

### Prerequisites
- Access to the Watcher API
- A valid API key of Watcher
- A functional instance of Cortex and TheHive

### Installation
- Add the configuration files (`Watcher_Add_Domain.json` and `Watcher_Remove_Domain.json`) to the Cortex configurations.

### Configuration
In Cortex, configure the following parameters for the Responder:

| Parameter | Description | Required | Default Value |
|-------------------------|--------------------------------------------------------------------------|----------|----------------|
| `watcher_url` | URL of Watcher (e.g. `https://example.watcher.local:9002`) | Yes | - |
| `watcher_api_key` | API key for authentication | Yes | - |
| `the_hive_custom_field` | Name of the custom field (same as .env variable) | Yes | `watcher-id` |

### Usage
When an artifact of type `domain` is submitted to this Responder, it will:
1. Extract the Watcher ID from the `customFieldValues` of the alert or case.
2. Perform the requested action (`add` or `remove`) based on the specified service.
3. Return a report indicating the success or failure of the operation.

### Example JSON Response
#### Adding a Domain
```json
{
"Message": "Domain 'example.com' successfully added to monitoring with watcher-id: '12345'.",
"WatcherResponse": {"status": "success"}
}
```

#### Removing a Domain
```json
{
"Message": "Domain 'example.com' successfully removed from monitoring.",
"WatcherResponse": {"status": "success"}
}
```

### Author

**Thales Group CERT** - [thalesgroup-cert on GitHub](https://github.com/thalesgroup-cert)
**Ygal NEZRI** - [@ygalnezri](https://github.com/ygalnezri)
35 changes: 35 additions & 0 deletions responders/Watcher/Watcher_Add_Domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Watcher_Add_Domain",
"version": "1.2",
"author": "THA-CERT // YNE",
"url": "https://github.com/thalesgroup-cert/Watcher",
"license": "AGPL-V3",
"description": "Add a domain to monitoring in the Website Monitoring module on Watcher.",
"dataTypeList": ["thehive:case_artifact"],
"command": "Watcher/watcher.py",
"baseConfig": "Watcher",
"config": {
"service": "WatcherAddDomain"
},
"configurationItems": [
{
"name": "watcher_url",
"description": "URL of Watcher.",
"type": "string",
"required": true
},
{
"name": "watcher_api_key",
"description": "API key used for authenticating requests to Watcher.",
"type": "string",
"required": true
},
{
"name": "the_hive_custom_field",
"description": "Name of the custom field (same as .env variable).",
"type": "string",
"required": true,
"defaultValue": "watcher-id"
}
]
}
35 changes: 35 additions & 0 deletions responders/Watcher/Watcher_Remove_Domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "Watcher_Remove_Domain",
"version": "1.2",
"author": "THA-CERT // YNE",
"url": "https://github.com/thalesgroup-cert/Watcher",
"license": "AGPL-V3",
"description": "Removes a domain from monitoring in the Website Monitoring module on Watcher.",
"dataTypeList": ["thehive:case_artifact"],
"command": "Watcher/watcher.py",
"baseConfig": "Watcher",
"config": {
"service": "WatcherRemoveDomain"
},
"configurationItems": [
{
"name": "watcher_url",
"description": "URL of Watcher.",
"type": "string",
"required": true
},
{
"name": "watcher_api_key",
"description": "API key used for authenticating requests to Watcher.",
"type": "string",
"required": true
},
{
"name": "the_hive_custom_field",
"description": "Name of the custom field (same as .env variable).",
"type": "string",
"required": true,
"defaultValue": "watcher-id"
}
]
}
2 changes: 2 additions & 0 deletions responders/Watcher/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests
Loading