intelmq-extensions 1.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- intelmq_extensions/__init__.py +0 -0
- intelmq_extensions/bots/__init__.py +0 -0
- intelmq_extensions/bots/collectors/blackkite/__init__.py +0 -0
- intelmq_extensions/bots/collectors/blackkite/_client.py +167 -0
- intelmq_extensions/bots/collectors/blackkite/collector.py +182 -0
- intelmq_extensions/bots/collectors/disp/__init__.py +0 -0
- intelmq_extensions/bots/collectors/disp/_client.py +121 -0
- intelmq_extensions/bots/collectors/disp/collector.py +104 -0
- intelmq_extensions/bots/collectors/xmpp/__init__.py +0 -0
- intelmq_extensions/bots/collectors/xmpp/collector.py +210 -0
- intelmq_extensions/bots/experts/__init__.py +0 -0
- intelmq_extensions/bots/experts/certat_contact_intern/__init__.py +0 -0
- intelmq_extensions/bots/experts/certat_contact_intern/expert.py +139 -0
- intelmq_extensions/bots/experts/copy_extra/__init__.py +0 -0
- intelmq_extensions/bots/experts/copy_extra/expert.py +27 -0
- intelmq_extensions/bots/experts/event_group_splitter/__init__.py +0 -0
- intelmq_extensions/bots/experts/event_group_splitter/expert.py +117 -0
- intelmq_extensions/bots/experts/event_splitter/__init__.py +0 -0
- intelmq_extensions/bots/experts/event_splitter/expert.py +41 -0
- intelmq_extensions/bots/experts/squelcher/__init__.py +0 -0
- intelmq_extensions/bots/experts/squelcher/expert.py +316 -0
- intelmq_extensions/bots/experts/vulnerability_lookup/__init__.py +0 -0
- intelmq_extensions/bots/experts/vulnerability_lookup/expert.py +136 -0
- intelmq_extensions/bots/outputs/__init__.py +0 -0
- intelmq_extensions/bots/outputs/mattermost/__init__.py +0 -0
- intelmq_extensions/bots/outputs/mattermost/output.py +113 -0
- intelmq_extensions/bots/outputs/to_logs/__init__.py +0 -0
- intelmq_extensions/bots/outputs/to_logs/output.py +12 -0
- intelmq_extensions/bots/outputs/xmpp/__init__.py +0 -0
- intelmq_extensions/bots/outputs/xmpp/output.py +180 -0
- intelmq_extensions/bots/parsers/__init__.py +0 -0
- intelmq_extensions/bots/parsers/blackkite/__init__.py +0 -0
- intelmq_extensions/bots/parsers/blackkite/_transformers.py +202 -0
- intelmq_extensions/bots/parsers/blackkite/parser.py +65 -0
- intelmq_extensions/bots/parsers/disp/__init__.py +0 -0
- intelmq_extensions/bots/parsers/disp/parser.py +125 -0
- intelmq_extensions/bots/parsers/malwaredomains/__init__.py +0 -0
- intelmq_extensions/bots/parsers/malwaredomains/parser.py +63 -0
- intelmq_extensions/cli/__init__.py +0 -0
- intelmq_extensions/cli/create_reports.py +161 -0
- intelmq_extensions/cli/intelmqcli.py +657 -0
- intelmq_extensions/cli/lib.py +670 -0
- intelmq_extensions/cli/utils.py +12 -0
- intelmq_extensions/etc/harmonization.conf +434 -0
- intelmq_extensions/etc/squelcher.conf +52 -0
- intelmq_extensions/lib/__init__.py +0 -0
- intelmq_extensions/lib/api_helpers.py +105 -0
- intelmq_extensions/lib/blackkite.py +29 -0
- intelmq_extensions/tests/__init__.py +0 -0
- intelmq_extensions/tests/base.py +336 -0
- intelmq_extensions/tests/bots/__init__.py +0 -0
- intelmq_extensions/tests/bots/collectors/__init__.py +0 -0
- intelmq_extensions/tests/bots/collectors/blackkite/__init__.py +0 -0
- intelmq_extensions/tests/bots/collectors/blackkite/base.py +45 -0
- intelmq_extensions/tests/bots/collectors/blackkite/test_client.py +154 -0
- intelmq_extensions/tests/bots/collectors/blackkite/test_collector.py +287 -0
- intelmq_extensions/tests/bots/collectors/disp/__init__.py +0 -0
- intelmq_extensions/tests/bots/collectors/disp/base.py +147 -0
- intelmq_extensions/tests/bots/collectors/disp/test_client.py +134 -0
- intelmq_extensions/tests/bots/collectors/disp/test_collector.py +137 -0
- intelmq_extensions/tests/bots/collectors/xmpp/__init__.py +0 -0
- intelmq_extensions/tests/bots/collectors/xmpp/test_collector.py +10 -0
- intelmq_extensions/tests/bots/experts/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/certat_contact_intern/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/certat_contact_intern/test_expert.py +176 -0
- intelmq_extensions/tests/bots/experts/copy_extra/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/copy_extra/test_expert.py +42 -0
- intelmq_extensions/tests/bots/experts/event_group_splitter/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/event_group_splitter/test_expert.py +302 -0
- intelmq_extensions/tests/bots/experts/event_splitter/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/event_splitter/test_expert.py +101 -0
- intelmq_extensions/tests/bots/experts/squelcher/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/squelcher/test_expert.py +548 -0
- intelmq_extensions/tests/bots/experts/vulnerability_lookup/__init__.py +0 -0
- intelmq_extensions/tests/bots/experts/vulnerability_lookup/test_expert.py +203 -0
- intelmq_extensions/tests/bots/outputs/__init__.py +0 -0
- intelmq_extensions/tests/bots/outputs/mattermost/__init__.py +0 -0
- intelmq_extensions/tests/bots/outputs/mattermost/test_output.py +138 -0
- intelmq_extensions/tests/bots/outputs/xmpp/__init__.py +0 -0
- intelmq_extensions/tests/bots/outputs/xmpp/test_output.py +10 -0
- intelmq_extensions/tests/bots/parsers/__init__.py +0 -0
- intelmq_extensions/tests/bots/parsers/blackkite/__init__.py +0 -0
- intelmq_extensions/tests/bots/parsers/blackkite/data.py +69 -0
- intelmq_extensions/tests/bots/parsers/blackkite/test_parser.py +197 -0
- intelmq_extensions/tests/bots/parsers/disp/__init__.py +0 -0
- intelmq_extensions/tests/bots/parsers/disp/test_parser.py +282 -0
- intelmq_extensions/tests/bots/parsers/malwaredomains/__init__.py +0 -0
- intelmq_extensions/tests/bots/parsers/malwaredomains/test_parser.py +62 -0
- intelmq_extensions/tests/cli/__init__.py +0 -0
- intelmq_extensions/tests/cli/test_create_reports.py +97 -0
- intelmq_extensions/tests/cli/test_intelmqcli.py +158 -0
- intelmq_extensions/tests/lib/__init__.py +0 -0
- intelmq_extensions/tests/lib/base.py +81 -0
- intelmq_extensions/tests/lib/test_api_helpers.py +126 -0
- intelmq_extensions-1.8.1.dist-info/METADATA +60 -0
- intelmq_extensions-1.8.1.dist-info/RECORD +100 -0
- intelmq_extensions-1.8.1.dist-info/WHEEL +5 -0
- intelmq_extensions-1.8.1.dist-info/entry_points.txt +33 -0
- intelmq_extensions-1.8.1.dist-info/licenses/LICENSE +661 -0
- intelmq_extensions-1.8.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
XMPP Collector Bot
|
|
3
|
+
Connects to a XMPP Server and a Room and reads data from the room.
|
|
4
|
+
If no room is provided, which is equivalent to an empty string,
|
|
5
|
+
it only collects events which were sent to the xmpp user directly.
|
|
6
|
+
|
|
7
|
+
TLS is used by default.
|
|
8
|
+
|
|
9
|
+
Tested with Python >= 3.4
|
|
10
|
+
Tested with slixmpp >= 1.0.0-beta5
|
|
11
|
+
|
|
12
|
+
Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik
|
|
13
|
+
Software engineering by Intevation GmbH
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
ca_certs: string to a CA-bundle file or false/empty string for no checks
|
|
17
|
+
strip_message: boolean
|
|
18
|
+
xmpp_user: string
|
|
19
|
+
xmpp_server: string
|
|
20
|
+
xmpp_password: boolean
|
|
21
|
+
xmpp_room: string
|
|
22
|
+
xmpp_room_password: string
|
|
23
|
+
xmpp_room_nick: string
|
|
24
|
+
pass_full_xml: boolean
|
|
25
|
+
strip_message: boolean
|
|
26
|
+
xmpp_userlist: array
|
|
27
|
+
xmpp_whitelist_mode: boolean
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from intelmq.lib.bot import CollectorBot
|
|
31
|
+
from intelmq.lib.exceptions import MissingDependencyError
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
import slixmpp
|
|
35
|
+
|
|
36
|
+
class XMPPClient(slixmpp.ClientXMPP):
|
|
37
|
+
def __init__(self, jid, password, room, room_nick, room_password, logger):
|
|
38
|
+
slixmpp.ClientXMPP.__init__(self, jid, password)
|
|
39
|
+
|
|
40
|
+
self.logger = logger
|
|
41
|
+
self.logger.info("Initiated.")
|
|
42
|
+
self.xmpp_room = room
|
|
43
|
+
self.xmpp_room_nick = room_nick
|
|
44
|
+
self.xmpp_room_password = room_password
|
|
45
|
+
|
|
46
|
+
self.add_event_handler("session_start", self.session_start)
|
|
47
|
+
|
|
48
|
+
def session_start(self, event):
|
|
49
|
+
self.send_presence()
|
|
50
|
+
self.logger.debug("Session started.")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
self.get_roster()
|
|
54
|
+
except slixmpp.exceptions.IqError as err:
|
|
55
|
+
self.logger.error("There was an error getting the roster.")
|
|
56
|
+
self.logger.error(err.iq["error"]["condition"])
|
|
57
|
+
self.disconnect()
|
|
58
|
+
except slixmpp.exceptions.IqTimeout:
|
|
59
|
+
self.logger.error("Server is taking too long to respond.")
|
|
60
|
+
self.disconnect()
|
|
61
|
+
|
|
62
|
+
if (
|
|
63
|
+
self.xmpp_room
|
|
64
|
+
): # and self.plugin.get('xep_0045') # this check should also exist!
|
|
65
|
+
self.logger.debug("Joining room: %s.", self.xmpp_room)
|
|
66
|
+
pwd = self.xmpp_room_password if self.xmpp_room_password else ""
|
|
67
|
+
self.plugin["xep_0045"].joinMUC(
|
|
68
|
+
self.xmpp_room, self.xmpp_room_nick, password=pwd, wait=True
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
except ImportError:
|
|
72
|
+
slixmpp = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class XMPPCollectorBot(CollectorBot):
|
|
76
|
+
name = "XMPP"
|
|
77
|
+
xmpp = None
|
|
78
|
+
collector_empty_process = True
|
|
79
|
+
|
|
80
|
+
def init(self):
|
|
81
|
+
self.logger.warning(
|
|
82
|
+
"The output bot 'intelmq.bots.collectors.xmpp.output' "
|
|
83
|
+
"is deprecated. It will be removed in version 3.0."
|
|
84
|
+
"Please see https://github.com/certtools/intelmq/blob/"
|
|
85
|
+
"develop/NEWS.md#xmpp-bots for more details."
|
|
86
|
+
)
|
|
87
|
+
if slixmpp is None:
|
|
88
|
+
raise MissingDependencyError("slixmpp")
|
|
89
|
+
|
|
90
|
+
# Retrieve Parameters from configuration
|
|
91
|
+
xmpp_user = getattr(self.parameters, "xmpp_user", None)
|
|
92
|
+
xmpp_server = getattr(self.parameters, "xmpp_server", None)
|
|
93
|
+
xmpp_password = getattr(self.parameters, "xmpp_password", None)
|
|
94
|
+
|
|
95
|
+
if None in (xmpp_user, xmpp_server, xmpp_password):
|
|
96
|
+
raise ValueError("No User / Password provided.")
|
|
97
|
+
else:
|
|
98
|
+
xmpp_login = xmpp_user + "@" + xmpp_server
|
|
99
|
+
|
|
100
|
+
self.userlist = getattr(self.parameters, "xmpp_userlist", [])
|
|
101
|
+
# When configured in manager this is most likely a ,-separated string, we'd like an array
|
|
102
|
+
if type(self.userlist) is str:
|
|
103
|
+
self.userlist = [u.strip() for u in self.userlist.split(",")]
|
|
104
|
+
elif self.userlist is None: # if value is unset, set to empty list
|
|
105
|
+
self.userlist = []
|
|
106
|
+
|
|
107
|
+
self.whitelist_mode = getattr(self.parameters, "xmpp_whitelist_mode", False)
|
|
108
|
+
|
|
109
|
+
self.muc = getattr(self.parameters, "use_muc", None)
|
|
110
|
+
xmpp_room = getattr(self.parameters, "xmpp_room", None) if self.muc else None
|
|
111
|
+
xmpp_room_nick = (
|
|
112
|
+
getattr(self.parameters, "xmpp_room_nick", None) if self.muc else None
|
|
113
|
+
)
|
|
114
|
+
xmpp_room_password = (
|
|
115
|
+
getattr(self.parameters, "xmpp_room_password", None) if self.muc else None
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
self.pass_full_xml = getattr(self.parameters, "pass_full_xml", None)
|
|
119
|
+
self.strip_message = getattr(self.parameters, "strip_message", None)
|
|
120
|
+
|
|
121
|
+
ca_certs = getattr(self.parameters, "ca_certs", None)
|
|
122
|
+
|
|
123
|
+
if self.muc and not xmpp_room:
|
|
124
|
+
raise ValueError("No room provided.")
|
|
125
|
+
|
|
126
|
+
if self.muc:
|
|
127
|
+
if not xmpp_room_nick:
|
|
128
|
+
# create the room_nick from user and server
|
|
129
|
+
xmpp_room_nick = xmpp_login
|
|
130
|
+
|
|
131
|
+
self.xmpp = XMPPClient(
|
|
132
|
+
xmpp_login,
|
|
133
|
+
xmpp_password,
|
|
134
|
+
xmpp_room,
|
|
135
|
+
xmpp_room_nick,
|
|
136
|
+
xmpp_room_password,
|
|
137
|
+
self.logger,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if ca_certs:
|
|
141
|
+
# Set CA-Certificates
|
|
142
|
+
self.xmpp.ca_certs = ca_certs
|
|
143
|
+
|
|
144
|
+
if self.xmpp.connect(reattempt=False):
|
|
145
|
+
self.xmpp.process()
|
|
146
|
+
# Add Handlers and register Plugins
|
|
147
|
+
self.xmpp.register_plugin("xep_0030") # Service Discovery
|
|
148
|
+
self.xmpp.register_plugin("xep_0045") # Multi-User Chat
|
|
149
|
+
|
|
150
|
+
self.xmpp.add_event_handler("message", self.log_message)
|
|
151
|
+
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError("Could not connect to XMPP-Server.")
|
|
154
|
+
|
|
155
|
+
def process(self):
|
|
156
|
+
# Processing is done by function called from the eventhandler...
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
def shutdown(self):
|
|
160
|
+
if self.xmpp:
|
|
161
|
+
if self.xmpp.disconnect():
|
|
162
|
+
self.logger.info("Disconnected from XMPP Server.")
|
|
163
|
+
else:
|
|
164
|
+
self.logger.error("Could not disconnect from XMPP Server.")
|
|
165
|
+
else:
|
|
166
|
+
self.logger.info("There was no XMPPClient I could stop.")
|
|
167
|
+
|
|
168
|
+
def log_message(self, msg):
|
|
169
|
+
# If some exception happens here, the bot would silently fail.
|
|
170
|
+
# We want to know the reason, so we will log the exception manually.
|
|
171
|
+
try:
|
|
172
|
+
# Check if the message was sent by a users that is on
|
|
173
|
+
# the white or blacklist, determine if the message shall
|
|
174
|
+
# be processed.
|
|
175
|
+
if self.muc:
|
|
176
|
+
if msg["mucnick"] not in self.userlist and self.whitelist_mode:
|
|
177
|
+
# Whitelist-Case
|
|
178
|
+
return
|
|
179
|
+
elif msg["mucnick"] in self.userlist and not self.whitelist_mode:
|
|
180
|
+
# Blacklist Case
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
if self.pass_full_xml:
|
|
184
|
+
body = str(msg)
|
|
185
|
+
else:
|
|
186
|
+
if self.strip_message:
|
|
187
|
+
body = msg["body"].strip()
|
|
188
|
+
else:
|
|
189
|
+
body = msg["body"]
|
|
190
|
+
|
|
191
|
+
if len(body) > 400:
|
|
192
|
+
tmp_body = body[:397] + "..."
|
|
193
|
+
else:
|
|
194
|
+
tmp_body = body
|
|
195
|
+
|
|
196
|
+
self.logger.debug("Received Stanza: %r from %r.", tmp_body, msg["from"])
|
|
197
|
+
|
|
198
|
+
raw_msg = body
|
|
199
|
+
# Read msg-body and add as raw to a new report.
|
|
200
|
+
# now it's up to a parser to do the interpretation of the message.
|
|
201
|
+
if raw_msg:
|
|
202
|
+
report = self.new_report()
|
|
203
|
+
report.add("raw", raw_msg)
|
|
204
|
+
self.send_message(report)
|
|
205
|
+
except Exception:
|
|
206
|
+
self.logger.exception("Error during message handling.")
|
|
207
|
+
raise
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
BOT = XMPPCollectorBot
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
CERT.at geolocate the national CERT abuse service
|
|
4
|
+
"""
|
|
5
|
+
from intelmq.lib.bot import Bot
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import psycopg2
|
|
9
|
+
except ImportError:
|
|
10
|
+
psycopg2 = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CERTatContactExpertBot(Bot):
|
|
14
|
+
connect_timeout = 5
|
|
15
|
+
database: str = ""
|
|
16
|
+
user: str = ""
|
|
17
|
+
password: str = ""
|
|
18
|
+
host: str = ""
|
|
19
|
+
port: str = ""
|
|
20
|
+
sslmode: str = ""
|
|
21
|
+
autocommit: bool = True
|
|
22
|
+
table: str = "contacts"
|
|
23
|
+
column: str = ""
|
|
24
|
+
feed_code: str = ""
|
|
25
|
+
ascolumn: str = ""
|
|
26
|
+
overwrite: bool = False
|
|
27
|
+
|
|
28
|
+
def init(self):
|
|
29
|
+
self.logger.debug("Connecting to database.")
|
|
30
|
+
if psycopg2 is None:
|
|
31
|
+
self.logger.error("Could not import psycopg2. Please install it.")
|
|
32
|
+
self.stop()
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
self.con = psycopg2.connect(
|
|
36
|
+
database=self.database,
|
|
37
|
+
user=self.user,
|
|
38
|
+
password=self.password,
|
|
39
|
+
host=self.host,
|
|
40
|
+
port=self.port,
|
|
41
|
+
sslmode=self.sslmode,
|
|
42
|
+
connect_timeout=self.connect_timeout,
|
|
43
|
+
)
|
|
44
|
+
self.cur = self.con.cursor()
|
|
45
|
+
self.con.autocommit = self.autocommit
|
|
46
|
+
|
|
47
|
+
except Exception:
|
|
48
|
+
self.logger.exception("Failed to connect to database.")
|
|
49
|
+
self.stop()
|
|
50
|
+
self.logger.info("Connected to PostgreSQL.")
|
|
51
|
+
|
|
52
|
+
self.query = (
|
|
53
|
+
'SELECT "{column}", "can-see-tlp-amber_{feed_code}"'
|
|
54
|
+
' FROM "{table}" WHERE "{ascolumn}" = %s'
|
|
55
|
+
"".format(
|
|
56
|
+
table=self.table,
|
|
57
|
+
column=self.column,
|
|
58
|
+
feed_code=self.feed_code,
|
|
59
|
+
ascolumn=self.ascolumn,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def process(self):
|
|
64
|
+
event = self.receive_message()
|
|
65
|
+
default_destination_visible = (
|
|
66
|
+
True if event.get("feed.code") != self.feed_code else False
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
if "source.asn" not in event:
|
|
70
|
+
self.logger.info("source.asn not present in event. Skipping event.")
|
|
71
|
+
event.add(
|
|
72
|
+
"destination_visible",
|
|
73
|
+
default_destination_visible,
|
|
74
|
+
overwrite=self.overwrite,
|
|
75
|
+
)
|
|
76
|
+
self.send_message(event)
|
|
77
|
+
self.acknowledge_message()
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
if "source.abuse_contact" in event and not self.overwrite:
|
|
81
|
+
event.add(
|
|
82
|
+
"destination_visible",
|
|
83
|
+
default_destination_visible,
|
|
84
|
+
overwrite=self.overwrite,
|
|
85
|
+
)
|
|
86
|
+
self.send_message(event)
|
|
87
|
+
self.acknowledge_message()
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
self.logger.debug(
|
|
92
|
+
"Executing %r." % self.cur.mogrify(self.query, (event["source.asn"],))
|
|
93
|
+
)
|
|
94
|
+
self.cur.execute(self.query, (event["source.asn"],))
|
|
95
|
+
except (
|
|
96
|
+
psycopg2.InterfaceError,
|
|
97
|
+
psycopg2.InternalError,
|
|
98
|
+
psycopg2.OperationalError,
|
|
99
|
+
AttributeError,
|
|
100
|
+
):
|
|
101
|
+
self.logger.exception("Database connection problem, connecting again.")
|
|
102
|
+
self.init()
|
|
103
|
+
else:
|
|
104
|
+
if self.cur.rowcount > 1:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
"Lookup returned more than one result. Please inspect."
|
|
107
|
+
)
|
|
108
|
+
elif self.cur.rowcount == 1:
|
|
109
|
+
result = self.cur.fetchone()
|
|
110
|
+
self.logger.debug(
|
|
111
|
+
"Changing `source.abuse_contact` from %r to %r."
|
|
112
|
+
% (event.get("source.abuse_contact"), result[0])
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
event.add("source.abuse_contact", result[0], overwrite=self.overwrite)
|
|
116
|
+
|
|
117
|
+
if event["feed.code"] == self.feed_code:
|
|
118
|
+
if result[1]:
|
|
119
|
+
event.add("destination_visible", True, overwrite=self.overwrite)
|
|
120
|
+
else:
|
|
121
|
+
event.add(
|
|
122
|
+
"destination_visible", False, overwrite=self.overwrite
|
|
123
|
+
)
|
|
124
|
+
else:
|
|
125
|
+
event.add("destination_visible", True, overwrite=self.overwrite)
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
self.logger.debug("No contact found.")
|
|
129
|
+
event.add(
|
|
130
|
+
"destination_visible",
|
|
131
|
+
default_destination_visible,
|
|
132
|
+
overwrite=self.overwrite,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.send_message(event)
|
|
136
|
+
self.acknowledge_message()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
BOT = CERTatContactExpertBot
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Modify Expert bot let's you manipulate all fields with a config file.
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from intelmq.lib.bot import Bot
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CopyExtraExpertBot(Bot):
|
|
11
|
+
keys: list = []
|
|
12
|
+
|
|
13
|
+
def process(self):
|
|
14
|
+
event = self.receive_message()
|
|
15
|
+
|
|
16
|
+
if "extra" in event:
|
|
17
|
+
extra = json.loads(event["extra"])
|
|
18
|
+
|
|
19
|
+
share = {key: extra[key] for key in self.keys if key in extra}
|
|
20
|
+
if share:
|
|
21
|
+
event.add("shareable_extra_info", share, overwrite=False)
|
|
22
|
+
|
|
23
|
+
self.send_message(event)
|
|
24
|
+
self.acknowledge_message()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
BOT = CopyExtraExpertBot
|
|
File without changes
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Event Group Splitter can split events based on tag groups,
|
|
2
|
+
and automatically recognize new groups.
|
|
3
|
+
|
|
4
|
+
The bot is designed with an intention to handle ShadowServer Compromise Website report.
|
|
5
|
+
|
|
6
|
+
SPDX-FileCopyrightText: 2024 CERT.at GmbH <https://cert.at/>
|
|
7
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import itertools
|
|
11
|
+
import json
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
from intelmq.lib.bot import ExpertBot
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EventGroupSplitterExpertBot(ExpertBot):
|
|
18
|
+
look_in: str = "extra.tag"
|
|
19
|
+
copy_to: list[str] = ["classification.identifier", "extra.vulnerabilities"]
|
|
20
|
+
regex: str = "" # has to match one group meaning matched duplication value
|
|
21
|
+
ignore: list[str] = [] # tags to ignore
|
|
22
|
+
# path to a JSON file holding groups; the file will be updated with new groups
|
|
23
|
+
# the structure is a dict with keys as feed codes, and values as list of list of tags
|
|
24
|
+
# e.g.:
|
|
25
|
+
# {
|
|
26
|
+
# "feed-1": [["t1", "t2"], ["t1", "t3"]],
|
|
27
|
+
# "feed-2": [["t6"]]
|
|
28
|
+
# }
|
|
29
|
+
groups_file: str = None
|
|
30
|
+
# whether groups should be analysed separately for every feed or not;
|
|
31
|
+
# if not, the bot will not use "new group" path for events matching
|
|
32
|
+
# group already seen in any other feed
|
|
33
|
+
treat_feeds_separately: bool = True
|
|
34
|
+
|
|
35
|
+
def init(self):
|
|
36
|
+
self.matcher = re.compile(self.regex)
|
|
37
|
+
|
|
38
|
+
self._tag_groups: dict[str, list[list[str]]] = []
|
|
39
|
+
self._reload_groups()
|
|
40
|
+
|
|
41
|
+
def _reload_groups(self, dump_current=False):
|
|
42
|
+
if dump_current:
|
|
43
|
+
with open(self.groups_file, "w+") as f:
|
|
44
|
+
json.dump(
|
|
45
|
+
{
|
|
46
|
+
feed: [list(g) for g in groups]
|
|
47
|
+
for feed, groups in self._tag_groups.items()
|
|
48
|
+
},
|
|
49
|
+
f,
|
|
50
|
+
indent=4,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
with open(self.groups_file) as f:
|
|
54
|
+
new_groups = json.load(f)
|
|
55
|
+
|
|
56
|
+
self._tag_groups = {
|
|
57
|
+
feed: sorted(
|
|
58
|
+
(set(group) for group in groups), key=lambda g: len(g), reverse=True
|
|
59
|
+
)
|
|
60
|
+
for feed, groups in new_groups.items()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
def process(self):
|
|
64
|
+
event = self.receive_message()
|
|
65
|
+
|
|
66
|
+
lookup_data = event.get(self.look_in, "")
|
|
67
|
+
matches = self.matcher.findall(lookup_data)
|
|
68
|
+
if not matches:
|
|
69
|
+
self.send_message(event)
|
|
70
|
+
else:
|
|
71
|
+
matches = [m for m in matches if m not in self.ignore]
|
|
72
|
+
matches_set = set(matches)
|
|
73
|
+
feed = event.get("feed.code", "")
|
|
74
|
+
if self.treat_feeds_separately:
|
|
75
|
+
feed_groups = self._tag_groups.get(feed, [])
|
|
76
|
+
else:
|
|
77
|
+
feed_groups = itertools.chain(*self._tag_groups.values())
|
|
78
|
+
|
|
79
|
+
for group in feed_groups:
|
|
80
|
+
if not matches_set >= group:
|
|
81
|
+
# The group is not in event's tags
|
|
82
|
+
continue
|
|
83
|
+
self._generate_event(event, group)
|
|
84
|
+
|
|
85
|
+
# Tags can be duplicated in event. Respect it.
|
|
86
|
+
for tag in group:
|
|
87
|
+
matches.remove(tag)
|
|
88
|
+
matches_set = set(matches)
|
|
89
|
+
|
|
90
|
+
if not matches:
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
# Something is left - new group
|
|
94
|
+
if matches:
|
|
95
|
+
new_group = set(matches)
|
|
96
|
+
self.logger.info("New tag group was discovered: %s.", new_group)
|
|
97
|
+
self._generate_event(event, new_group, new_group=True)
|
|
98
|
+
|
|
99
|
+
if feed not in self._tag_groups:
|
|
100
|
+
self._tag_groups[feed] = list()
|
|
101
|
+
self._tag_groups[feed].append(new_group)
|
|
102
|
+
self._reload_groups(dump_current=True)
|
|
103
|
+
|
|
104
|
+
self.acknowledge_message()
|
|
105
|
+
|
|
106
|
+
def _generate_event(self, event, group: set, new_group: bool = False):
|
|
107
|
+
grouped_value = "-".join(sorted(group))
|
|
108
|
+
self.logger.debug("Found: %s.", group)
|
|
109
|
+
sub_event = self.new_event(event)
|
|
110
|
+
for key in self.copy_to:
|
|
111
|
+
sub_event.add(key, grouped_value, overwrite=True)
|
|
112
|
+
self.send_message(sub_event)
|
|
113
|
+
if new_group:
|
|
114
|
+
self.send_message(sub_event, path="new_tag_groups", path_permissive=True)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
BOT = EventGroupSplitterExpertBot
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Event Splitter can produce multiple events from a one, based
|
|
2
|
+
on event's tag.
|
|
3
|
+
|
|
4
|
+
Currently implemented with intention of splitting CVE events.
|
|
5
|
+
|
|
6
|
+
SPDX-FileCopyrightText: 2023 CERT.at GmbH <https://cert.at/>
|
|
7
|
+
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from intelmq.lib.bot import ExpertBot
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EventSplitterExpertBot(ExpertBot):
|
|
16
|
+
look_in: str = "extra.tag"
|
|
17
|
+
copy_to: list[str] = ["classification.identifier", "extra.vulnerabilities"]
|
|
18
|
+
regex: str = "" # has to match one group meaning matched duplication value
|
|
19
|
+
|
|
20
|
+
def init(self):
|
|
21
|
+
self.matcher = re.compile(self.regex)
|
|
22
|
+
|
|
23
|
+
def process(self):
|
|
24
|
+
event = self.receive_message()
|
|
25
|
+
|
|
26
|
+
lookup_data = event.get(self.look_in, "")
|
|
27
|
+
matches = self.matcher.findall(lookup_data)
|
|
28
|
+
if not matches:
|
|
29
|
+
self.send_message(event)
|
|
30
|
+
else:
|
|
31
|
+
for matched in matches:
|
|
32
|
+
self.logger.debug("Found: %s.", matched)
|
|
33
|
+
sub_event = self.new_event(event)
|
|
34
|
+
for key in self.copy_to:
|
|
35
|
+
sub_event.add(key, matched, overwrite=True)
|
|
36
|
+
self.send_message(sub_event)
|
|
37
|
+
|
|
38
|
+
self.acknowledge_message()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
BOT = EventSplitterExpertBot
|
|
File without changes
|