pymisp 2.5.8.1__py3-none-any.whl → 2.5.10__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.
Potentially problematic release.
This version of pymisp might be problematic. Click here for more details.
- pymisp/data/misp-objects/objects/network-connection/definition.json +1 -0
- pymisp/mispevent.py +4 -0
- pymisp/tools/emailobject.py +86 -28
- {pymisp-2.5.8.1.dist-info → pymisp-2.5.10.dist-info}/METADATA +2 -2
- {pymisp-2.5.8.1.dist-info → pymisp-2.5.10.dist-info}/RECORD +7 -7
- {pymisp-2.5.8.1.dist-info → pymisp-2.5.10.dist-info}/WHEEL +1 -1
- {pymisp-2.5.8.1.dist-info → pymisp-2.5.10.dist-info}/LICENSE +0 -0
pymisp/mispevent.py
CHANGED
|
@@ -1120,6 +1120,10 @@ class MISPObject(AnalystDataBehaviorMixin):
|
|
|
1120
1120
|
Helper for object_relation when multiple is True in the template.
|
|
1121
1121
|
It is the same as calling multiple times add_attribute with the same object_relation.
|
|
1122
1122
|
'''
|
|
1123
|
+
if not attributes:
|
|
1124
|
+
logger.info(f"No attributes provided for object relation '{object_relation}'; skipping attribute addition.")
|
|
1125
|
+
return []
|
|
1126
|
+
|
|
1123
1127
|
to_return = []
|
|
1124
1128
|
for attribute in attributes:
|
|
1125
1129
|
if isinstance(attribute, MISPAttribute):
|
pymisp/tools/emailobject.py
CHANGED
|
@@ -373,37 +373,95 @@ class EMailObject(AbstractMISPObjectGenerator):
|
|
|
373
373
|
# email object doesn't support display name for all email addrs
|
|
374
374
|
pass
|
|
375
375
|
|
|
376
|
+
def extract_matches(self, pattern: re.Pattern[str], text: str) -> list[tuple[str, ...]]:
|
|
377
|
+
"""Returns all regex matches for a given pattern in a text."""
|
|
378
|
+
return re.findall(pattern, text)
|
|
379
|
+
|
|
380
|
+
def add_ip_attribute(self, ip_candidate: str, received: str, seen_attributes: set[tuple[str, str]]) -> None:
|
|
381
|
+
"""Validates and adds an IP address to MISP if it's public and not already seen during extraction."""
|
|
382
|
+
try:
|
|
383
|
+
ip = ipaddress.ip_address(ip_candidate)
|
|
384
|
+
if not ip.is_private and ("received-header-ip", ip_candidate) not in seen_attributes:
|
|
385
|
+
self.add_attribute("received-header-ip", ip_candidate, comment=received)
|
|
386
|
+
seen_attributes.add(("received-header-ip", ip_candidate))
|
|
387
|
+
except ValueError:
|
|
388
|
+
pass # Invalid IPs are ignored
|
|
389
|
+
|
|
390
|
+
def add_hostname_attribute(self, hostname: str, received: str, seen_attributes: set[tuple[str, str]]) -> None:
|
|
391
|
+
"""Validates and adds a hostname to MISP if it contains a valid TLD-like format and is not already seen."""
|
|
392
|
+
if "." in hostname and not hostname.endswith(".") and len(hostname.split(".")[-1]) > 1:
|
|
393
|
+
if ("received-header-hostname", hostname) not in seen_attributes:
|
|
394
|
+
self.add_attribute("received-header-hostname", hostname, comment=received)
|
|
395
|
+
seen_attributes.add(("received-header-hostname", hostname))
|
|
396
|
+
|
|
397
|
+
def process_received_header(self, received: str, seen_attributes: set[tuple[str, str]]) -> None:
|
|
398
|
+
"""Processes a single 'Received' header and extracts hostnames and IPs."""
|
|
399
|
+
|
|
400
|
+
# Regex patterns
|
|
401
|
+
received_from_regex = re.compile(
|
|
402
|
+
r'from\s+([\w.-]+)' # Declared sending hostname
|
|
403
|
+
r'(?:\s+\(([^)]+)\))?' # Reverse DNS hostname inside parentheses
|
|
404
|
+
)
|
|
405
|
+
ipv4_regex = re.compile(
|
|
406
|
+
r'\[(?P<ipv4_brackets>(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
407
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
408
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
409
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\]' # IPv4 inside []
|
|
410
|
+
r'|\((?P<ipv4_parentheses>(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
411
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
412
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
413
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\)' # IPv4 inside ()
|
|
414
|
+
r'|(?<=\.\s)(?P<ipv4_after_domain>(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
415
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
416
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.'
|
|
417
|
+
r'(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9]))\b' # IPv4 appearing after a domain.
|
|
418
|
+
)
|
|
419
|
+
ipv6_regex = re.compile(
|
|
420
|
+
r'\b(?:[a-fA-F0-9]{1,4}:[a-fA-F0-9]{1,4}(?::[a-fA-F0-9]{1,4}){0,6})\b'
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# Extract hostnames
|
|
424
|
+
matches = self.extract_matches(received_from_regex, received)
|
|
425
|
+
for match in matches:
|
|
426
|
+
declared_sending_host = match[0].strip() if match[0] else None
|
|
427
|
+
reverse_dns_host = match[1].split()[0].strip("[]()").rstrip('.') if match[1] else None
|
|
428
|
+
|
|
429
|
+
if declared_sending_host:
|
|
430
|
+
clean_host = declared_sending_host.strip("[]()")
|
|
431
|
+
try:
|
|
432
|
+
ipaddress.ip_address(declared_sending_host)
|
|
433
|
+
self.add_ip_attribute(declared_sending_host, received, seen_attributes)
|
|
434
|
+
except ValueError:
|
|
435
|
+
self.add_hostname_attribute(declared_sending_host, received, seen_attributes)
|
|
436
|
+
|
|
437
|
+
if reverse_dns_host:
|
|
438
|
+
try:
|
|
439
|
+
ipaddress.ip_address(reverse_dns_host)
|
|
440
|
+
self.add_ip_attribute(reverse_dns_host, received, seen_attributes)
|
|
441
|
+
except ValueError:
|
|
442
|
+
self.add_hostname_attribute(reverse_dns_host, received, seen_attributes)
|
|
443
|
+
|
|
444
|
+
# Extract and add **only valid** IPv4 addresses
|
|
445
|
+
for ipv4_match in self.extract_matches(ipv4_regex, received):
|
|
446
|
+
ip_candidate = ipv4_match[0] or ipv4_match[1] or ipv4_match[2] # Select first non-empty match
|
|
447
|
+
if ip_candidate:
|
|
448
|
+
self.add_ip_attribute(ip_candidate, received, seen_attributes)
|
|
449
|
+
|
|
450
|
+
# Extract and add IPv6 addresses
|
|
451
|
+
for ipv6_match in self.extract_matches(ipv6_regex, received):
|
|
452
|
+
self.add_ip_attribute(ipv6_match, received, seen_attributes)
|
|
453
|
+
|
|
376
454
|
def __generate_received(self) -> None:
|
|
377
455
|
"""
|
|
378
|
-
|
|
456
|
+
Extracts public IP addresses and hostnames from "Received" email headers.
|
|
379
457
|
"""
|
|
380
|
-
received_items = self.email.get_all("received")
|
|
381
|
-
if received_items is None:
|
|
382
|
-
return
|
|
383
|
-
for received in received_items:
|
|
384
|
-
fromstr = re.split(r"\sby\s", received)[0].strip()
|
|
385
|
-
if fromstr.startswith('from') is not True:
|
|
386
|
-
continue
|
|
387
|
-
for i in ['(', ')', '[', ']']:
|
|
388
|
-
fromstr = fromstr.replace(i, " ")
|
|
389
|
-
tokens = fromstr.split(" ")
|
|
390
|
-
ip = None
|
|
391
|
-
for token in tokens:
|
|
392
|
-
try:
|
|
393
|
-
ip = ipaddress.ip_address(token)
|
|
394
|
-
break
|
|
395
|
-
except ValueError:
|
|
396
|
-
pass # token is not IP address
|
|
397
458
|
|
|
398
|
-
|
|
399
|
-
|
|
459
|
+
received_items = self.email.get_all("Received")
|
|
460
|
+
if not received_items:
|
|
461
|
+
return
|
|
400
462
|
|
|
401
|
-
|
|
463
|
+
# Track added attributes to prevent duplicates (store as (type, value) tuples)
|
|
464
|
+
seen_attributes: set[tuple[str, str]] = set()
|
|
402
465
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
received_from = re.findall(r'(?<=from\s)[\w\d\.\-]+\.\w{2,24}', str(received_items))
|
|
406
|
-
try:
|
|
407
|
-
[self.add_attribute("received-header-hostname", i) for i in received_from]
|
|
408
|
-
except Exception:
|
|
409
|
-
pass
|
|
466
|
+
for received in received_items:
|
|
467
|
+
self.process_received_header(received, seen_attributes)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pymisp
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.10
|
|
4
4
|
Summary: Python API for MISP.
|
|
5
5
|
License: BSD-2-Clause
|
|
6
6
|
Author: Raphaël Vinot
|
|
@@ -32,7 +32,7 @@ Requires-Dist: RTFDE (>=0.1.2) ; (python_version <= "3.9") and (extra == "email"
|
|
|
32
32
|
Requires-Dist: beautifulsoup4 (>=4.13.3) ; extra == "openioc"
|
|
33
33
|
Requires-Dist: deprecated (>=1.2.18)
|
|
34
34
|
Requires-Dist: docutils (>=0.21.2) ; (python_version >= "3.11") and (extra == "docs")
|
|
35
|
-
Requires-Dist: extract_msg (>=0.
|
|
35
|
+
Requires-Dist: extract_msg (>=0.54.0) ; extra == "email"
|
|
36
36
|
Requires-Dist: lief (>=0.16.4) ; extra == "fileobjects"
|
|
37
37
|
Requires-Dist: myst-parser (>=4.0.1) ; (python_version >= "3.11") and (extra == "docs")
|
|
38
38
|
Requires-Dist: oletools (>=0.60.2) ; extra == "email"
|
|
@@ -206,7 +206,7 @@ pymisp/data/misp-objects/objects/monetary-impact/definition.json,sha256=s44CoduM
|
|
|
206
206
|
pymisp/data/misp-objects/objects/mutex/definition.json,sha256=zqun14zDa2seXkX5BGtlL_0dkT7LqTTEDagh-1lXKVs,744
|
|
207
207
|
pymisp/data/misp-objects/objects/narrative/definition.json,sha256=VXEm_lcQgR7uFtMalrdbI73-ivv6HJHQVx6lPU0FYzA,2200
|
|
208
208
|
pymisp/data/misp-objects/objects/netflow/definition.json,sha256=pQ_meRpiPEchaTBNTBUyUT5zPmL7QNIQgLGKdd_KTqE,4103
|
|
209
|
-
pymisp/data/misp-objects/objects/network-connection/definition.json,sha256=
|
|
209
|
+
pymisp/data/misp-objects/objects/network-connection/definition.json,sha256=6rGG8ZhW3YxgGAV_l91GFpZXk4QpyJ7iuedH5FU38HE,4248
|
|
210
210
|
pymisp/data/misp-objects/objects/network-profile/definition.json,sha256=urPC6ysgZ5kaiB2L2ilL19iGmR2GNUzjO4pcUngQl5E,6175
|
|
211
211
|
pymisp/data/misp-objects/objects/network-socket/definition.json,sha256=qEE1yvRnrpylHut3jFDJnPWWfsz61ZJO0-Lp40WOSjM,6571
|
|
212
212
|
pymisp/data/misp-objects/objects/network-traffic/definition.json,sha256=jZSGhItwP-1Vxm7fv_IqbijXqnAvPFFKhjxolaDXudE,3144
|
|
@@ -367,7 +367,7 @@ pymisp/data/misp-objects/schema_relationships.json,sha256=MCusp9GAyuHTo3lLyBrsvl
|
|
|
367
367
|
pymisp/data/schema-lax.json,sha256=2QICdCbtfXRJkTVjwb7xjF3ypys2wOtrUyE1ZDz_qes,8561
|
|
368
368
|
pymisp/data/schema.json,sha256=79N2hObemthb_syUHksDqM4djFttsWZQDg1sTYZYxys,9178
|
|
369
369
|
pymisp/exceptions.py,sha256=IgGGadv5lnLAvO7Q6AjF0vEbjoWwwDWLYwMn-8pkU_k,1965
|
|
370
|
-
pymisp/mispevent.py,sha256=
|
|
370
|
+
pymisp/mispevent.py,sha256=mgIiXFj-RKJud2TBpqL8AefQOcYM2zxJsOqOmSDovPI,121525
|
|
371
371
|
pymisp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
372
372
|
pymisp/tools/__init__.py,sha256=_KCihYo82e8G5cHV321ak2sgbao2GyFjf4sSTMiN_IM,2233
|
|
373
373
|
pymisp/tools/_psl_faup.py,sha256=JyK8RQm8DPWvNuoF4rQpiE0rBm-Az-sr38Kl46dmWcs,7034
|
|
@@ -377,7 +377,7 @@ pymisp/tools/create_misp_object.py,sha256=PP78t4Gc7jiZtjt3MGC-0NuH976vSadSmhbaSk
|
|
|
377
377
|
pymisp/tools/csvloader.py,sha256=d-Ox4KEehuXi9YxPE3hhf62etaj7D0pUHr5Qy4rPoqo,2588
|
|
378
378
|
pymisp/tools/domainipobject.py,sha256=2w1ckOWPZvp9EW6TOAguT1Kwov72K1jJuJLqgU1whoo,847
|
|
379
379
|
pymisp/tools/elfobject.py,sha256=thylyAVcAdF31II8ykVzG75Fe4Fgokc9qR90g1ybI8s,4966
|
|
380
|
-
pymisp/tools/emailobject.py,sha256=
|
|
380
|
+
pymisp/tools/emailobject.py,sha256=sPgVAvQFyRiONMiXYDJNibSSMWsjX1df9J3EDZ5LDEE,22680
|
|
381
381
|
pymisp/tools/ext_lookups.py,sha256=acRbOVQftw7XpbjDZDrrdYzDmLDU4HmhoW48Og3UfaY,1022
|
|
382
382
|
pymisp/tools/fail2banobject.py,sha256=VWxK8qWVL0AqO_YZSKmsOcaEnG_5j0jOok7OfEXWfMQ,740
|
|
383
383
|
pymisp/tools/feed.py,sha256=eRG1D4fnG-2hZTFFy7SYUhGVozaAMVSiJXwxHoLP5Gg,700
|
|
@@ -398,7 +398,7 @@ pymisp/tools/update_objects.py,sha256=sp_XshzgtRjAU0Mqg8FgRTaokjVKLImyQ02xIcPSrH
|
|
|
398
398
|
pymisp/tools/urlobject.py,sha256=PIucy1356zaljUm1NbeKmEpHpAUK9yiK2lAugcMp2t8,2489
|
|
399
399
|
pymisp/tools/vehicleobject.py,sha256=bs7f4d47IBi2-VumssSM3HlqkH0viyHTLmIHQxe8Iz8,3687
|
|
400
400
|
pymisp/tools/vtreportobject.py,sha256=NsdYzgqm47dywYeW8UnWmEDeIsf07xZreD2iJzFm2wg,3217
|
|
401
|
-
pymisp-2.5.
|
|
402
|
-
pymisp-2.5.
|
|
403
|
-
pymisp-2.5.
|
|
404
|
-
pymisp-2.5.
|
|
401
|
+
pymisp-2.5.10.dist-info/LICENSE,sha256=1oPSVvs96qLjbJVi3mPn0yvWs-6aoIF6BNXi6pVlFmY,1615
|
|
402
|
+
pymisp-2.5.10.dist-info/METADATA,sha256=aakG8Az0H27y7lEAPrqMrPEden9Gr1y8K5DJ_S5huwY,8882
|
|
403
|
+
pymisp-2.5.10.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
404
|
+
pymisp-2.5.10.dist-info/RECORD,,
|
|
File without changes
|