Skip to content

Commit 665eb1d

Browse files
Merge pull request #73 from alexandreborges/dev
Malwoverview 7.0
2 parents 515f875 + 80bd66e commit 665eb1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+648
-1849
lines changed

.malwapi.conf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ HAAPI =
77
[MALSHARE]
88
MALSHAREAPI =
99

10-
[HAUSSUBMIT]
11-
HAUSSUBMITAPI =
12-
1310
[POLYSWARM]
1411
POLYAPI =
1512

@@ -36,3 +33,6 @@ BAZAARAPI =
3633

3734
[THREATFOX]
3835
THREATFOXAPI =
36+
37+
[URLHAUS]
38+
URLHAUSAPI =

README.md

Lines changed: 237 additions & 239 deletions
Large diffs are not rendered by default.

malwoverview/malwoverview.py

Lines changed: 71 additions & 83 deletions
Large diffs are not rendered by default.

malwoverview/modules/inquest.py

Lines changed: 0 additions & 1384 deletions
This file was deleted.

malwoverview/modules/nist.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import requests
2+
import json
3+
import textwrap
4+
from datetime import datetime, timedelta
5+
from malwoverview.utils.colors import mycolors, printr
6+
import malwoverview.modules.configvars as cv
7+
8+
9+
class NISTExtractor():
10+
11+
base_url = 'https://services.nvd.nist.gov/rest/json/cves/2.0'
12+
13+
def __init__(self):
14+
self.session = requests.Session()
15+
self.session.headers.update({'User-Agent': 'MalwoOverview/1.0'})
16+
17+
def query_cve(self, query_type, query_value, results_per_page=100, start_index=0, last_n_years=None):
18+
19+
if not query_value:
20+
print(mycolors.foreground.red + "\nError: No query value provided.\n")
21+
return None
22+
if results_per_page > 2000:
23+
results_per_page = 2000
24+
elif results_per_page < 1:
25+
results_per_page = 1
26+
27+
params = {'resultsPerPage': results_per_page, 'startIndex': start_index}
28+
29+
try:
30+
if query_type == 1:
31+
if query_value.lower().startswith('cpe:'):
32+
params['cpeName'] = query_value
33+
query_desc = f"CPE Name: {query_value}"
34+
else:
35+
params['keywordSearch'] = query_value
36+
query_desc = f"Keyword Search: {query_value}"
37+
elif query_type == 2:
38+
params['cveId'] = query_value
39+
query_desc = f"CVE ID: {query_value}"
40+
elif query_type == 3:
41+
params['cvssV3Severity'] = query_value
42+
if last_n_years:
43+
query_desc = f"CVSS v3 Severity: {query_value} (last {last_n_years} years)"
44+
else:
45+
query_desc = f"CVSS v3 Severity: {query_value}"
46+
elif query_type == 4:
47+
params['keywordSearch'] = query_value
48+
if last_n_years:
49+
query_desc = f"Keyword Search: {query_value} (last {last_n_years} years)"
50+
else:
51+
query_desc = f"Keyword Search: {query_value}"
52+
elif query_type == 5:
53+
params['cweId'] = query_value
54+
if last_n_years:
55+
query_desc = f"CWE ID Search: {query_value} (last {last_n_years} years)"
56+
else:
57+
query_desc = f"CWE ID Search: {query_value}"
58+
else:
59+
print(mycolors.foreground.red + f"\nError: Unknown query type '{query_type}'.\n")
60+
return None
61+
62+
# First request: Get a small batch to determine total results (needed for smart start_index)
63+
response = self.session.get(self.base_url, params=params, timeout=30)
64+
response.raise_for_status()
65+
data = response.json()
66+
if 'vulnerabilities' not in data:
67+
print(mycolors.foreground.yellow + "\nWarning: Unexpected API response structure.\n")
68+
69+
total_results = data.get('totalResults', 0)
70+
71+
# Smart positioning for recent vulnerabilities (except Type 2 - specific CVE search)
72+
# Type 2 searches entire database to find specific CVE in any year
73+
if start_index == 0 and total_results > 0 and query_type in [1, 3, 4, 5]:
74+
calculated_start = max(0, int(total_results * 0.99))
75+
params['startIndex'] = calculated_start
76+
params['resultsPerPage'] = 2000
77+
78+
response = self.session.get(self.base_url, params=params, timeout=30)
79+
response.raise_for_status()
80+
data = response.json()
81+
82+
# Apply date filtering or sorting for recent vulnerability discovery
83+
if last_n_years and query_type in [1, 3, 4, 5]:
84+
data = self._filter_by_date(data, last_n_years)
85+
elif query_type in [1, 3, 4, 5]:
86+
# Filter to current year only by default, then sort
87+
data = self._filter_to_current_year(data)
88+
data = self._sort_by_date_descending(data)
89+
90+
return data
91+
92+
except requests.exceptions.Timeout:
93+
print(mycolors.foreground.red + "\nError: Request timed out.\n")
94+
return None
95+
except requests.exceptions.ConnectionError as e:
96+
print(mycolors.foreground.red + f"\nError: Connection error: {str(e)}\n")
97+
return None
98+
except requests.exceptions.HTTPError as e:
99+
print(mycolors.foreground.red + f"\nError: HTTP error: {str(e)}\n")
100+
return None
101+
except json.JSONDecodeError:
102+
print(mycolors.foreground.red + "\nError: Invalid JSON response.\n")
103+
return None
104+
except Exception as e:
105+
print(mycolors.foreground.red + f"\nError: {str(e)}\n")
106+
return None
107+
108+
def print_results(self, data, verbose=False, color_scheme=1, max_cves=None):
109+
110+
if not data or 'vulnerabilities' not in data:
111+
print(mycolors.foreground.red + "\nNo results found.\n")
112+
return
113+
vulnerabilities = data.get('vulnerabilities', [])
114+
115+
vulnerabilities_sorted = sorted(
116+
vulnerabilities,
117+
key=lambda x: x.get('cve', {}).get('published', '9999-01-01'),
118+
reverse=True
119+
)
120+
121+
if max_cves is not None:
122+
vulnerabilities_sorted = vulnerabilities_sorted[:max_cves]
123+
124+
if color_scheme == 0:
125+
cve_id_color = mycolors.foreground.red
126+
field_color = mycolors.foreground.blue
127+
else:
128+
cve_id_color = mycolors.foreground.yellow
129+
field_color = mycolors.foreground.lightcyan
130+
131+
print()
132+
133+
for idx, vuln in enumerate(vulnerabilities_sorted, 1):
134+
cve_data = vuln.get('cve', {})
135+
cve_id = cve_data.get('id', 'N/A')
136+
published = cve_data.get('published', 'N/A')
137+
last_modified = cve_data.get('lastModified', 'N/A')
138+
vuln_status = cve_data.get('vulnStatus', 'N/A')
139+
descriptions = cve_data.get('descriptions', [])
140+
description = 'N/A'
141+
for desc in descriptions:
142+
if desc.get('lang') == 'en':
143+
description = desc.get('value', 'N/A')
144+
break
145+
metrics = cve_data.get('metrics', {})
146+
cvss_v2 = metrics.get('cvssMetricV2', [])
147+
cvss_v3 = metrics.get('cvssMetricV31', []) or metrics.get('cvssMetricV3', [])
148+
149+
print(f"{cve_id_color}[{idx}] CVE ID: {cve_id}{mycolors.reset}")
150+
print(f" {field_color}Status:{mycolors.reset} {vuln_status}")
151+
print(f" {field_color}Published:{mycolors.reset} {published}")
152+
print(f" {field_color}Last Modified:{mycolors.reset} {last_modified}")
153+
if cvss_v2:
154+
for cv2 in cvss_v2:
155+
score = cv2.get('cvssData', {}).get('baseScore', 'N/A')
156+
severity = cv2.get('baseSeverity', 'N/A')
157+
print(f" {field_color}CVSS v2.0:{mycolors.reset} {score} ({severity})")
158+
159+
if cvss_v3:
160+
for cv3 in cvss_v3:
161+
score = cv3.get('cvssData', {}).get('baseScore', 'N/A')
162+
severity = cv3.get('baseSeverity', 'N/A')
163+
print(f" {field_color}CVSS v3.1:{mycolors.reset} {score} ({severity})")
164+
165+
print(f" {field_color}Description:{mycolors.reset}")
166+
wrapped_desc = textwrap.fill(description, width=75, initial_indent=' ', subsequent_indent=' ')
167+
try:
168+
print(wrapped_desc.encode('utf-8', 'replace').decode('utf-8'))
169+
except:
170+
print(wrapped_desc.encode('ascii', 'replace').decode('ascii'))
171+
print()
172+
if verbose:
173+
configurations = cve_data.get('configurations', [])
174+
if configurations:
175+
print(f" {field_color}Affected Products:{mycolors.reset}")
176+
for config in configurations[:3]:
177+
nodes = config.get('nodes', [])
178+
for node in nodes[:2]:
179+
cpe_matches = node.get('cpeMatch', [])
180+
for cpe in cpe_matches[:2]:
181+
criteria = cpe.get('criteria', 'N/A')
182+
if len(criteria) > 65:
183+
criteria = criteria[:62] + '...'
184+
print(f" - {criteria}")
185+
references = cve_data.get('references', [])
186+
if references:
187+
print(f" {field_color}References:{mycolors.reset}")
188+
for ref in references[:2]:
189+
url = ref.get('url', 'N/A')
190+
if len(url) > 65:
191+
url = url[:62] + '...'
192+
print(f" - {url}")
193+
194+
def _sort_by_date_descending(self, data):
195+
if not data or 'vulnerabilities' not in data:
196+
return data
197+
198+
def get_cve_year_and_id(vuln):
199+
"""Extract year from CVE ID for sorting by actual threat year"""
200+
cve_id = vuln.get('cve', {}).get('id', '')
201+
# CVE ID format: CVE-YYYY-NNNNN
202+
try:
203+
if cve_id.startswith('CVE-'):
204+
year = int(cve_id.split('-')[1])
205+
# Return tuple: (year descending, full CVE ID for secondary sort)
206+
return (year, cve_id)
207+
except (IndexError, ValueError):
208+
pass
209+
# Fallback for invalid CVE IDs
210+
return (0, cve_id)
211+
212+
sorted_vulns = sorted(
213+
data.get('vulnerabilities', []),
214+
key=get_cve_year_and_id,
215+
reverse=True
216+
)
217+
218+
data['vulnerabilities'] = sorted_vulns
219+
return data
220+
221+
def _filter_to_current_year(self, data):
222+
"""Filter vulnerabilities to include last 2 years by default (current + previous year)"""
223+
if not data or 'vulnerabilities' not in data:
224+
return data
225+
226+
current_year = datetime.now().year
227+
previous_year = current_year - 1
228+
filtered_vulns = []
229+
230+
for vuln in data.get('vulnerabilities', []):
231+
cve_id = vuln.get('cve', {}).get('id', '')
232+
# Extract year from CVE ID (CVE-YYYY-NNNNN)
233+
try:
234+
if cve_id.startswith('CVE-'):
235+
cve_year = int(cve_id.split('-')[1])
236+
if cve_year >= previous_year: # Include current + previous year
237+
filtered_vulns.append(vuln)
238+
except (ValueError, IndexError):
239+
pass
240+
241+
data['vulnerabilities'] = filtered_vulns
242+
return data
243+
244+
def _filter_by_date(self, data, last_n_years):
245+
if not data or 'vulnerabilities' not in data:
246+
return data
247+
248+
current_year = datetime.now().year
249+
cutoff_year = current_year - last_n_years
250+
filtered_vulns = []
251+
252+
for vuln in data.get('vulnerabilities', []):
253+
cve_id = vuln.get('cve', {}).get('id', '')
254+
# Extract year from CVE ID (CVE-YYYY-NNNNN)
255+
try:
256+
if cve_id.startswith('CVE-'):
257+
cve_year = int(cve_id.split('-')[1])
258+
if cve_year >= cutoff_year:
259+
filtered_vulns.append(vuln)
260+
else:
261+
filtered_vulns.append(vuln)
262+
except (ValueError, IndexError):
263+
filtered_vulns.append(vuln)
264+
265+
# Sort by CVE year descending
266+
def get_cve_year_and_id(vuln):
267+
cve_id = vuln.get('cve', {}).get('id', '')
268+
try:
269+
if cve_id.startswith('CVE-'):
270+
year = int(cve_id.split('-')[1])
271+
return (year, cve_id)
272+
except (IndexError, ValueError):
273+
pass
274+
return (0, cve_id)
275+
276+
filtered_vulns.sort(key=get_cve_year_and_id, reverse=True)
277+
278+
data['vulnerabilities'] = filtered_vulns
279+
data['resultsPerPage'] = len(filtered_vulns)
280+
return data
281+
282+
def get_query_type_description(self, query_type):
283+
types = {
284+
1: "CPE/Product Search",
285+
2: "CVE ID Search",
286+
3: "CVSS v3 Severity Search",
287+
4: "Keyword Search",
288+
5: "CWE ID Search"
289+
}
290+
return types.get(query_type, "Unknown Query Type")

0 commit comments

Comments
 (0)