Skip to content

Commit 9230e07

Browse files
Merge branch 'release/3.5.19'
2 parents 4137787 + 8a5e5b3 commit 9230e07

29 files changed

Lines changed: 1001 additions & 176 deletions

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# Changelog
22

3+
## [3.5.18](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.5.18) (2025-06-23)
4+
5+
[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.5.17...3.5.18)
6+
7+
**Closed issues:**
8+
9+
- \[FR\] BitcoinAbuse analyzer fails – upstream API migrated to ChainAbuse [\#1360](https://github.com/TheHive-Project/Cortex-Analyzers/issues/1360)
10+
11+
**Merged pull requests:**
12+
13+
- Migrates BitcoinAbuse analyzer to new ChainAbuse [\#1361](https://github.com/TheHive-Project/Cortex-Analyzers/pull/1361) ([nusantara-self](https://github.com/nusantara-self))
14+
315
## [3.5.17](https://github.com/TheHive-Project/Cortex-Analyzers/tree/3.5.17) (2025-06-13)
416

517
[Full Changelog](https://github.com/TheHive-Project/Cortex-Analyzers/compare/3.5.16...3.5.17)

analyzers/FoxIO/JA4_FoxIO.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "JA4_FoxIO",
3+
"version": "1.0",
4+
"author": "Florian Perret @cyber_pescadito",
5+
"url": "https://github.com/cyberpescadito/Cortex-Analyzers/tree/master/analyzers/JA4_FoxIO",
6+
"license": "AGPL-V3",
7+
"description": "JA4 Fingerprint analysis with FoxIO Database",
8+
"dataTypeList": ["user-agent", "ja4-fingerprint"],
9+
"command": "FoxIO/JA4_FoxIO.py",
10+
"baseConfig": "FoxIO",
11+
"configurationItems": [
12+
],
13+
"registration_required": false,
14+
"subscription_required": false,
15+
"free_subscription": true,
16+
"service_homepage": "https://ja4db.com/"
17+
}

analyzers/FoxIO/JA4_FoxIO.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env python3
2+
# Author: @cyber_pescadito
3+
from cortexutils.analyzer import Analyzer
4+
import requests
5+
import json
6+
7+
class JA4_FoxIO(Analyzer):
8+
def __init__(self):
9+
Analyzer.__init__(self)
10+
self.data = self.get_param('data', None, 'Data is missing')
11+
self.data_type = self.get_param('dataType', None, 'dataType is missing')
12+
13+
def run(self):
14+
Analyzer.run(self)
15+
try:
16+
db = requests.get('https://ja4db.com/api/read/')
17+
jsoned = json.loads(db.text)
18+
19+
report_content = []
20+
21+
if self.data_type == 'user-agent':
22+
for item in jsoned:
23+
user_agent_string = item.get('user_agent_string')
24+
if user_agent_string and self.data == user_agent_string:
25+
report_content.append(item)
26+
elif self.data_type == 'ja4-fingerprint':
27+
fingerprint_fields = [
28+
"ja4_fingerprint",
29+
"ja4_fingerprint_string",
30+
"ja4s_fingerprint",
31+
"ja4h_fingerprint",
32+
"ja4x_fingerprint",
33+
"ja4t_fingerprint",
34+
"ja4ts_fingerprint",
35+
"ja4tscan_fingerprint"
36+
]
37+
38+
for item in jsoned:
39+
if any(self.data == item.get(field) for field in fingerprint_fields):
40+
report_content.append(item)
41+
42+
self.report({"report": report_content})
43+
44+
45+
except Exception as e:
46+
self.error(str(e))
47+
48+
def summary(self, report_content):
49+
taxonomies = []
50+
level = 'info'
51+
namespace = "JA4"
52+
predicate = "Reports count"
53+
value = str(len(report_content['report']))
54+
55+
taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
56+
return {"taxonomies": taxonomies}
57+
58+
59+
if __name__ == "__main__":
60+
JA4_FoxIO().run()

analyzers/FoxIO/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cortexutils
2+
requests

analyzers/Gatewatcher_CTI/Gatewatcher_CTI.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Gatewatcher_CTI",
3-
"version": "1.0",
3+
"version": "2.0",
44
"author": "Gatewatcher",
55
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
66
"license": "AGPL-3.0",
@@ -9,7 +9,9 @@
99
"hash",
1010
"domain",
1111
"fqdn",
12-
"url"
12+
"url",
13+
"ip",
14+
"mail"
1315
],
1416
"command": "Gatewatcher_CTI/Gatewatcher_CTI.py",
1517
"baseConfig": "Gatewatcher_CTI",

analyzers/Gatewatcher_CTI/Gatewatcher_CTI.py

Lines changed: 92 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,29 @@
77
class GatewatcherCTI(Analyzer):
88
def __init__(self):
99
Analyzer.__init__(self)
10-
self.api_key = self.get_param(
11-
"config.apiKey", None, "Gatewatcher CTI API KEY is required"
12-
)
13-
self.extended_report = self.get_param(
14-
"config.extendedReport", None, "Please set the Extended Report option"
15-
)
16-
self.max_relations= self.get_param(
17-
"config.maxRelations", None
18-
)
10+
self.api_key = self.get_param("config.apiKey", None, "Gatewatcher CTI API KEY is required")
11+
self.extended_report = self.get_param("config.extendedReport", None, "Please set the Extended Report option")
12+
self.max_relations = self.get_param("config.maxRelations", None)
1913
self.observable_value = self.get_param("data", None, "Data is missing")
14+
self.data_type = self.get_param("dataType", None, "Data type is missing")
15+
self.base_url = "https://api.client.lastinfosec.com/v2/"
16+
self.headers = {"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0"}
2017

21-
def run(self):
22-
url = f"https://api.client.lastinfosec.com/v2/lis/search?api_key={self.api_key}"
23-
if not self.extended_report:
24-
url = f"{url}&extended_report=false"
25-
data = {"value" : self.observable_value}
26-
useragent = {
27-
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0"
28-
}
29-
response = requests.post(url, headers=useragent, json=data)
18+
def _IOCs_search(self):
19+
response = requests.post(
20+
url=f"{self.base_url}lis/search",
21+
headers=self.headers,
22+
params={"api_key": self.api_key, "extended_report": self.extended_report},
23+
json={"value": self.observable_value},
24+
)
3025
info = self.check_response(response)
3126

3227
additional = {}
3328
main = {}
34-
records = {"IOCs": [], "is_on_gw": True}
29+
records = {"IOCs": [], "IsOnGw": True}
3530

3631
if response.status_code == 422:
37-
records["is_on_gw"] = False
32+
records["IsOnGw"] = False
3833
else:
3934
relations = []
4035
for item in info["message"][0]["IOCs"]:
@@ -50,8 +45,9 @@ def run(self):
5045
has_max = False
5146
total_found_relations = 0
5247
for item in info["message"][0]["IOCs"]:
53-
if (total_found_relations == len(relations) or
54-
(has_max and total_found_relations >= self.max_relations)):
48+
if total_found_relations == len(relations) or (
49+
has_max and total_found_relations >= self.max_relations
50+
):
5551
break
5652

5753
if item["IocId"] in relations:
@@ -69,54 +65,100 @@ def run(self):
6965
main.update(additional)
7066
records["IOCs"].insert(0, main)
7167
if len(records["IOCs"]) == 1 and records["IOCs"][0]["Risk"].lower() == "unknown":
72-
records["is_on_gw"] = False
68+
records["IsOnGw"] = False
69+
return records
7370

71+
def _get_by_ip(self):
72+
response = requests.get(
73+
url=f"{self.base_url}lis/ip/get_by_ip/{self.observable_value}",
74+
headers=self.headers,
75+
params={"api_key": self.api_key, "headers": False},
76+
)
77+
info = self.check_response(response)
78+
if info.get("Score") == "Unknown":
79+
info["IsOnGw"] = False
80+
else:
81+
info["IsOnGw"] = True
82+
return info
83+
84+
def _get_by_email(self):
85+
# TODO : check why headers false is always on 'InProgress' status on Cortex
86+
response = requests.get(
87+
url=f"{self.base_url}lis/leaked_emails/get_by_email/{self.observable_value}",
88+
headers=self.headers,
89+
params={"api_key": self.api_key, "headers": True},
90+
)
91+
info = self.check_response(response)
92+
# get only data of the request
93+
data = info.get("message")
94+
if len(data) < 1:
95+
return {"Value": self.observable_value, "IsOnGw": False}
96+
else:
97+
result = data[0]
98+
result["IsOnGw"] = True
99+
# Return number of passwords
100+
if result.get("Passwords") is not None:
101+
result["totalPasswords"] = len(result["Passwords"])
102+
return result
103+
104+
def run(self):
105+
if self.data_type == "ip":
106+
records = self._get_by_ip()
107+
elif self.data_type == "mail":
108+
records = self._get_by_email()
109+
else:
110+
records = self._IOCs_search()
111+
records["DataType"] = self.data_type
74112
self.report(records)
75113

76114
def check_response(self, response):
77115
if response.status_code not in [200, 422]:
78116
try:
79117
result = response.json()
80-
if (
81-
"detail" in result
82-
and "details" in result["detail"]
83-
and "error" in result["detail"]["details"][0]
84-
):
85-
self.error(
86-
"Bad status: {0}. {1}".format(
87-
response.status_code,
88-
result["detail"]["details"][0]["error"],
89-
)
90-
)
118+
if "detail" in result and "details" in result["detail"] and "error" in result["detail"]["details"][0]:
119+
self.error(f'Bad status: {response.status_code}. {result["detail"]["details"][0]["error"]}')
91120
else:
92-
self.error("Bad status: {0}".format(response.status_code))
93-
except Exception as ex:
94-
self.error("Bad status: {0}".format(response.status_code))
121+
self.error(f"Bad status: {response.status_code}")
122+
except Exception:
123+
self.error(f"Bad status: {response.status_code}")
95124
else:
96125
try:
97126
result = response.json()
98127
return result
99128
except Exception as ex:
100-
self.error("Bad Response: {0}".format(ex))
129+
self.error(f"Bad Response: {ex}")
101130

102131
def summary(self, raw):
103132
taxonomies = []
104133
level = "info"
105134
namespace = "Gatewatcher CTI"
106135
predicate = "GetReport"
107136
value = "not found"
108-
data = next(
109-
(ioc for ioc in raw["IOCs"] if ioc["Value"] == self.observable_value), None
110-
)
111-
if data is not None:
112-
level = data["Risk"].lower()
113-
if level == "malicious":
114-
value = 100
115-
elif level == "high suspicious":
116-
value = 75
117-
elif level == "suspicious":
118-
value = 60
119-
137+
if self.data_type == "ip":
138+
score = raw.get("Score")
139+
if score == "Suspicious":
140+
level = "suspicious"
141+
value = 41
142+
elif score == "Low suspicious":
143+
value = 31
144+
elif score == "Past suspicious":
145+
value = 21
146+
elif self.data_type == "mail":
147+
# If no "creation date", api result is empty --> Email is not leaked
148+
if raw.get("CreationDate") is None:
149+
value = "not leaked"
150+
else:
151+
value = "leaked"
152+
else:
153+
data = next((ioc for ioc in raw["IOCs"] if ioc["Value"] == self.observable_value), None)
154+
if data is not None:
155+
level = data["Risk"].lower()
156+
if level == "malicious":
157+
value = 100
158+
elif level == "high suspicious":
159+
value = 75
160+
elif level == "suspicious":
161+
value = 60
120162
taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
121163
return {"taxonomies": taxonomies}
122164

analyzers/Gatewatcher_CTI/README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,46 @@ Gatewatcher is a European leader in advanced Threats detection, protecting criti
44
## Gatewatcher CTI
55
The Gatewatcher CTI (Cyber Threat Intelligence) offer is compatible with all cybersecurity solutions. It immediately enhances your detection with contextual information about internal and external cyber threats specifically targeting your business.
66

7-
## Cortex Integration
8-
This cortex analyzer allows you to search for an IOC (url, hash, host/domain) in the Gatewatcher CTI database
9-
107
## How to obtain credentials ?
118
If you want to try our freemium offer your can obtain your API key : https://info.gatewatcher.com/en/lp-free-ioc-analysis-api-key
129

13-
If you want more you can contact us : https://info.gatewatcher.com/fr/speed-meeting-lastinfosec
10+
# How the analyzer works ?
11+
Gatewatcher CTI analyzer allows you to get information about hashes,urls,domains,fqdn,ips or emails.
12+
- To enable Gatewatcher_CTI analyzer:
13+
- Navigate to "Organization" -> "Analyzers"
14+
- Refresh analyzers to ensure that you have the lastest version.
15+
- Search for "Gatewatcher_CTI".
16+
- Enable it and configure its parameters (LIS API key is required).
17+
18+
## Run on hashes/urls/domains/fqdns
19+
Search for an Indicator of Compromise (IoC: url, host/domain, hash) or vulnerability in the Gatewatcher CTI database.
20+
21+
- Short report
22+
23+
![alt text](assets/Gatewatcher_CTI_hash_short.png)
24+
25+
- Long report
26+
27+
![alt text](assets/Gatewatcher_CTI_hash_long.png)
28+
29+
## Run on IPs
30+
Retrieves metadata, security threat alerts, and a contextualized timeline of events associated with a specific IP address from the Gatewatcher CTI database.
31+
32+
- Short report
33+
34+
![alt text](assets/Gatewatcher_CTI_IP_reputation_short.png)
35+
36+
- Long report
37+
38+
![alt text](assets/Gatewatcher_CTI_IP_reputation_long.png)
39+
40+
## Run on emails
41+
Get all contextual informations (hash of password, url of connection...) for a targeted email address from the Gatewatcher CTI database.
42+
43+
- Short report
44+
45+
![alt text](assets/Gatewatcher_CTI_leaked_email_short.png)
1446

15-
## TheHive Integration
16-
With this cortex integration, we also provide you templates for TheHive available in the [thehive-templates](../../thehive-templates/Gatewatcher_CTI_1_0) directory.
47+
- Long report
1748

18-
![](assets/Gatewatcher_CTI_long.png)
49+
![alt text](assets/Gatewatcher_CTI_leaked_email_long.png)
46.9 KB
Loading
21.9 KB
Loading
115 KB
Loading

0 commit comments

Comments
 (0)