pymisp 2.5.8__py3-none-any.whl → 2.5.9__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/github-repo/definition.json +142 -0
- pymisp/mispevent.py +4 -0
- pymisp/tools/emailobject.py +86 -28
- {pymisp-2.5.8.dist-info → pymisp-2.5.9.dist-info}/METADATA +2 -2
- {pymisp-2.5.8.dist-info → pymisp-2.5.9.dist-info}/RECORD +7 -6
- {pymisp-2.5.8.dist-info → pymisp-2.5.9.dist-info}/LICENSE +0 -0
- {pymisp-2.5.8.dist-info → pymisp-2.5.9.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"attributes": {
|
|
3
|
+
"archived": {
|
|
4
|
+
"description": "Is the repository archived?",
|
|
5
|
+
"disable_correlation": true,
|
|
6
|
+
"misp-attribute": "text",
|
|
7
|
+
"sane_default": [
|
|
8
|
+
"True",
|
|
9
|
+
"False"
|
|
10
|
+
],
|
|
11
|
+
"ui-priority": 1
|
|
12
|
+
},
|
|
13
|
+
"created-at": {
|
|
14
|
+
"description": "Date of the repository creation",
|
|
15
|
+
"misp-attribute": "datetime",
|
|
16
|
+
"ui-priority": 0
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"description": "Repository description",
|
|
20
|
+
"misp-attribute": "text",
|
|
21
|
+
"ui-priority": 1
|
|
22
|
+
},
|
|
23
|
+
"disabled": {
|
|
24
|
+
"description": "Is the repository disabled?",
|
|
25
|
+
"disable_correlation": true,
|
|
26
|
+
"misp-attribute": "text",
|
|
27
|
+
"sane_default": [
|
|
28
|
+
"True",
|
|
29
|
+
"False"
|
|
30
|
+
],
|
|
31
|
+
"ui-priority": 1
|
|
32
|
+
},
|
|
33
|
+
"fork": {
|
|
34
|
+
"description": "Is the repository a forked repository?",
|
|
35
|
+
"disable_correlation": true,
|
|
36
|
+
"misp-attribute": "text",
|
|
37
|
+
"sane_default": [
|
|
38
|
+
"True",
|
|
39
|
+
"False"
|
|
40
|
+
],
|
|
41
|
+
"ui-priority": 1
|
|
42
|
+
},
|
|
43
|
+
"forks-count": {
|
|
44
|
+
"description": "Number of forks",
|
|
45
|
+
"misp-attribute": "counter",
|
|
46
|
+
"ui-priority": 1
|
|
47
|
+
},
|
|
48
|
+
"full-name": {
|
|
49
|
+
"description": "Full name of the repository. [Username/Repository name]",
|
|
50
|
+
"misp-attribute": "text",
|
|
51
|
+
"ui-priority": 1
|
|
52
|
+
},
|
|
53
|
+
"has-downloads": {
|
|
54
|
+
"description": "Have the repository been downloaded?",
|
|
55
|
+
"disable_correlation": true,
|
|
56
|
+
"misp-attribute": "text",
|
|
57
|
+
"sane_default": [
|
|
58
|
+
"True",
|
|
59
|
+
"False"
|
|
60
|
+
],
|
|
61
|
+
"ui-priority": 1
|
|
62
|
+
},
|
|
63
|
+
"has-wiki": {
|
|
64
|
+
"description": "Does the repository have a wiki?",
|
|
65
|
+
"disable_correlation": true,
|
|
66
|
+
"misp-attribute": "text",
|
|
67
|
+
"sane_default": [
|
|
68
|
+
"True",
|
|
69
|
+
"False"
|
|
70
|
+
],
|
|
71
|
+
"ui-priority": 1
|
|
72
|
+
},
|
|
73
|
+
"id": {
|
|
74
|
+
"description": "Repository id",
|
|
75
|
+
"misp-attribute": "text",
|
|
76
|
+
"ui-priority": 1
|
|
77
|
+
},
|
|
78
|
+
"languages": {
|
|
79
|
+
"description": "Languages used in the repository",
|
|
80
|
+
"misp-attribute": "text",
|
|
81
|
+
"multiple": true,
|
|
82
|
+
"ui-priority": 1
|
|
83
|
+
},
|
|
84
|
+
"link": {
|
|
85
|
+
"description": "Link to the GitHub repository.",
|
|
86
|
+
"misp-attribute": "link",
|
|
87
|
+
"multiple": true,
|
|
88
|
+
"ui-priority": 1
|
|
89
|
+
},
|
|
90
|
+
"name": {
|
|
91
|
+
"description": "name of the repository. [Repository name]",
|
|
92
|
+
"misp-attribute": "text",
|
|
93
|
+
"ui-priority": 1
|
|
94
|
+
},
|
|
95
|
+
"open-issues": {
|
|
96
|
+
"description": "Number of open issues",
|
|
97
|
+
"misp-attribute": "counter",
|
|
98
|
+
"ui-priority": 1
|
|
99
|
+
},
|
|
100
|
+
"private": {
|
|
101
|
+
"description": "Is the repository private?",
|
|
102
|
+
"disable_correlation": true,
|
|
103
|
+
"misp-attribute": "text",
|
|
104
|
+
"sane_default": [
|
|
105
|
+
"True",
|
|
106
|
+
"False"
|
|
107
|
+
],
|
|
108
|
+
"ui-priority": 1
|
|
109
|
+
},
|
|
110
|
+
"pushed-at": {
|
|
111
|
+
"description": "Date of last push",
|
|
112
|
+
"misp-attribute": "datetime",
|
|
113
|
+
"ui-priority": 0
|
|
114
|
+
},
|
|
115
|
+
"topics": {
|
|
116
|
+
"description": "Topics linked to the repository",
|
|
117
|
+
"misp-attribute": "text",
|
|
118
|
+
"multiple": true,
|
|
119
|
+
"ui-priority": 1
|
|
120
|
+
},
|
|
121
|
+
"updated-at": {
|
|
122
|
+
"description": "Date of the last update",
|
|
123
|
+
"misp-attribute": "datetime",
|
|
124
|
+
"ui-priority": 0
|
|
125
|
+
},
|
|
126
|
+
"username": {
|
|
127
|
+
"description": "Owner of the repository. [Username]",
|
|
128
|
+
"misp-attribute": "text",
|
|
129
|
+
"ui-priority": 1
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"description": "GitHub repository",
|
|
133
|
+
"meta-category": "misc",
|
|
134
|
+
"name": "github-repo",
|
|
135
|
+
"requiredOneOf": [
|
|
136
|
+
"name",
|
|
137
|
+
"full-name",
|
|
138
|
+
"link"
|
|
139
|
+
],
|
|
140
|
+
"uuid": "d2e93321-3d0c-4215-88a7-62ccb56fef89",
|
|
141
|
+
"version": 2
|
|
142
|
+
}
|
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.warning(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.9
|
|
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"
|
|
@@ -152,6 +152,7 @@ pymisp/data/misp-objects/objects/game-cheat/definition.json,sha256=4xqSM9PzOzuWZ
|
|
|
152
152
|
pymisp/data/misp-objects/objects/generalizing-persuasion-framework/definition.json,sha256=6EFw1OW2Qzbp1tip2PgwYhjvqh2koo5Rl75h1TzNE-s,5590
|
|
153
153
|
pymisp/data/misp-objects/objects/geolocation/definition.json,sha256=mvbU1_yi-9m69SJQWn7fh5k1MLUFIagPU2Mfp4GpjP8,3308
|
|
154
154
|
pymisp/data/misp-objects/objects/git-vuln-finder/definition.json,sha256=_b_Ux9biIpYXK0gmCzGxmp0AHi1dGEaW3H_MiftHx3s,3644
|
|
155
|
+
pymisp/data/misp-objects/objects/github-repo/definition.json,sha256=zmGO6g5fRlvp419DKXo3HYQc3-i6_VqCGyIxnb4i4II,3483
|
|
155
156
|
pymisp/data/misp-objects/objects/github-user/definition.json,sha256=CdHNDa0oLpPB25h5S-7ybEb9MSx92KbqAT7DmNckeNM,3463
|
|
156
157
|
pymisp/data/misp-objects/objects/gitlab-user/definition.json,sha256=xCqY6NAG1DhtyHDCGVik6yXCGhPie4AfnXAvCk9z6qg,1188
|
|
157
158
|
pymisp/data/misp-objects/objects/google-safe-browsing/definition.json,sha256=Bxo1eu_EbY8Q1mMv0y0lDv9Rn0xDwmPtesuZ8jtk4Xc,739
|
|
@@ -366,7 +367,7 @@ pymisp/data/misp-objects/schema_relationships.json,sha256=MCusp9GAyuHTo3lLyBrsvl
|
|
|
366
367
|
pymisp/data/schema-lax.json,sha256=2QICdCbtfXRJkTVjwb7xjF3ypys2wOtrUyE1ZDz_qes,8561
|
|
367
368
|
pymisp/data/schema.json,sha256=79N2hObemthb_syUHksDqM4djFttsWZQDg1sTYZYxys,9178
|
|
368
369
|
pymisp/exceptions.py,sha256=IgGGadv5lnLAvO7Q6AjF0vEbjoWwwDWLYwMn-8pkU_k,1965
|
|
369
|
-
pymisp/mispevent.py,sha256=
|
|
370
|
+
pymisp/mispevent.py,sha256=G6TLW-laRQRAJPb47EwZEb7ehYBn0rH4VF9oRUfDPMo,121528
|
|
370
371
|
pymisp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
371
372
|
pymisp/tools/__init__.py,sha256=_KCihYo82e8G5cHV321ak2sgbao2GyFjf4sSTMiN_IM,2233
|
|
372
373
|
pymisp/tools/_psl_faup.py,sha256=JyK8RQm8DPWvNuoF4rQpiE0rBm-Az-sr38Kl46dmWcs,7034
|
|
@@ -376,7 +377,7 @@ pymisp/tools/create_misp_object.py,sha256=PP78t4Gc7jiZtjt3MGC-0NuH976vSadSmhbaSk
|
|
|
376
377
|
pymisp/tools/csvloader.py,sha256=d-Ox4KEehuXi9YxPE3hhf62etaj7D0pUHr5Qy4rPoqo,2588
|
|
377
378
|
pymisp/tools/domainipobject.py,sha256=2w1ckOWPZvp9EW6TOAguT1Kwov72K1jJuJLqgU1whoo,847
|
|
378
379
|
pymisp/tools/elfobject.py,sha256=thylyAVcAdF31II8ykVzG75Fe4Fgokc9qR90g1ybI8s,4966
|
|
379
|
-
pymisp/tools/emailobject.py,sha256=
|
|
380
|
+
pymisp/tools/emailobject.py,sha256=sPgVAvQFyRiONMiXYDJNibSSMWsjX1df9J3EDZ5LDEE,22680
|
|
380
381
|
pymisp/tools/ext_lookups.py,sha256=acRbOVQftw7XpbjDZDrrdYzDmLDU4HmhoW48Og3UfaY,1022
|
|
381
382
|
pymisp/tools/fail2banobject.py,sha256=VWxK8qWVL0AqO_YZSKmsOcaEnG_5j0jOok7OfEXWfMQ,740
|
|
382
383
|
pymisp/tools/feed.py,sha256=eRG1D4fnG-2hZTFFy7SYUhGVozaAMVSiJXwxHoLP5Gg,700
|
|
@@ -397,7 +398,7 @@ pymisp/tools/update_objects.py,sha256=sp_XshzgtRjAU0Mqg8FgRTaokjVKLImyQ02xIcPSrH
|
|
|
397
398
|
pymisp/tools/urlobject.py,sha256=PIucy1356zaljUm1NbeKmEpHpAUK9yiK2lAugcMp2t8,2489
|
|
398
399
|
pymisp/tools/vehicleobject.py,sha256=bs7f4d47IBi2-VumssSM3HlqkH0viyHTLmIHQxe8Iz8,3687
|
|
399
400
|
pymisp/tools/vtreportobject.py,sha256=NsdYzgqm47dywYeW8UnWmEDeIsf07xZreD2iJzFm2wg,3217
|
|
400
|
-
pymisp-2.5.
|
|
401
|
-
pymisp-2.5.
|
|
402
|
-
pymisp-2.5.
|
|
403
|
-
pymisp-2.5.
|
|
401
|
+
pymisp-2.5.9.dist-info/LICENSE,sha256=1oPSVvs96qLjbJVi3mPn0yvWs-6aoIF6BNXi6pVlFmY,1615
|
|
402
|
+
pymisp-2.5.9.dist-info/METADATA,sha256=YNrgux5KH0_ShGiW-FinobWdFpRqPeCEqonvdY2U2oQ,8881
|
|
403
|
+
pymisp-2.5.9.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
404
|
+
pymisp-2.5.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|