2024 Known Exploited Vulnerabilities Statistics (Year-to-Date)¶
Source
df = df_original.copy()
df['Date Added'] = pd.to_datetime(df['Date Added'])
df['CISA Date Added'] = df['CISA Date Added'].replace('none', pd.NaT)
df['CISA Date Added'] = pd.to_datetime(df['CISA Date Added'], format='%Y-%m-%d', errors='coerce')
# Get the current date and calculate the number of months passed in the year
current_date = df['Date Added'].max() # Use the latest date in 'Date Added'
months_passed = current_date.month if current_date.year == YEAR else 12 # Use full 12 months for past years
# VulnCheck KEV data for the specified year
df_vulncheck_year = df[df['Date Added'].dt.year == YEAR]
total_kevs_vulncheck = df_vulncheck_year['CVE'].nunique()
avg_kevs_per_month_vulncheck = total_kevs_vulncheck / months_passed
# CISA KEV data where 'CISA Date Added' is within the specified year
df_cisa_year = df[df['CISA Date Added'].dt.year == YEAR]
total_kevs_cisa = df_cisa_year['CVE'].nunique()
avg_kevs_per_month_cisa = total_kevs_cisa / months_passed
# Unique vendors and products for VulnCheck KEV
unique_vendors_vulncheck = df_vulncheck_year['Vendor'].nunique()
unique_products_vulncheck = df_vulncheck_year['Product'].nunique()
# Unique vendors and products for CISA KEV
unique_vendors_cisa = df_cisa_year['Vendor'].nunique()
unique_products_cisa = df_cisa_year['Product'].nunique()
stats_rows = [
("Total KEVs Added", total_kevs_vulncheck, total_kevs_cisa),
("Avg. KEVs per month", avg_kevs_per_month_vulncheck, avg_kevs_per_month_cisa),
("Unique Vendors", unique_vendors_vulncheck, unique_vendors_cisa),
("Unique Products", unique_products_vulncheck, unique_products_cisa),
]
stats_df = pd.DataFrame(
stats_rows,
columns=["Metric", "VulnCheck KEV", "CISA KEV"]
)
stats_df.loc[stats_df["Metric"].str.contains("Avg."), ["VulnCheck KEV", "CISA KEV"]] = stats_df.loc[
stats_df["Metric"].str.contains("Avg."), ["VulnCheck KEV", "CISA KEV"]
].astype(int)
# Convert all values except the Metric column to int
stats_df["VulnCheck KEV"] = stats_df["VulnCheck KEV"].astype(int)
stats_df["CISA KEV"] = stats_df["CISA KEV"].astype(int)
styled_stats_df = stats_df.style \
.set_properties(**{'text-align': 'center'}) \
.set_table_styles([{'selector': 'th', 'props': [('text-align', 'center')]}]) \
.set_table_attributes('style="width:100%; border-collapse: collapse;"') \
.hide(axis="index")
styled_stats_df
Loading...
2024 Known Exploited Vulnerabilities by Vendor and Product - Year-to-Date (Source:VulnCheck KEV)¶
Source
df = df_original.copy()
# Convert 'Date Added' to datetime format
df['Date Added'] = pd.to_datetime(df['Date Added'], errors='coerce')
# Filter for entries where 'Date Added' is in the current year
current_year = df[(df['Date Added'].dt.year == YEAR)]
# Count occurrences by Vendor and Product for the current year
vendor_product_counts = current_year.groupby(['Vendor', 'Product']).size().reset_index(name='Counts')
# Truncate vendor and product names to 15 characters for readability
vendor_product_counts['Vendor'] = vendor_product_counts['Vendor'].str.slice(0, 15)
vendor_product_counts['Product'] = vendor_product_counts['Product'].str.slice(0, 15)
# Create a treemap with both Vendor and Product levels
fig = px.treemap(vendor_product_counts, path=['Vendor', 'Product'], values='Counts',
color='Counts', color_continuous_scale='Viridis')
# Customize the title and layout for dark mode with specific width and height
fig.update_layout(
title=f"Known Exploited Vulnerabilities by Vendor and Product - {YEAR} (Source: VulnCheck KEV)",
title_font=dict(size=20, color='white'),
title_x=0.5,
paper_bgcolor='black',
plot_bgcolor='black',
margin=dict(t=50, l=25, r=25, b=25),
width=1800,
height=1000
)
# Remove color bar by setting coloraxis_showscale to False
fig.update_coloraxes(showscale=False)
# Use default font for labels inside the treemap (Arial) by removing custom font settings
fig.update_traces(
texttemplate='%{label}<br> %{value}',
textfont_size=16,
textfont_color='white'
)
# Display the figure
fig.show()
Loading...
2024 Known Exploited Vulnerabilities by Vendor and Product - Year-to-Date (Source:CISA KEV)¶
Source
df = df_original.copy()
# Step 2: Convert 'CISA Date Added' to datetime format with a specified format
# Replace '%Y-%m-%d' with the actual format of your dates if different
df['CISA Date Added'] = pd.to_datetime(df['CISA Date Added'], format='%Y-%m-%d', errors='coerce')
# Step 3: Drop rows where 'CISA Date Added' could not be parsed
df.dropna(subset=['CISA Date Added'], inplace=True)
# Step 4: Filter for entries where 'CISA Date Added' is from this year
filtered_data = df[df['CISA Date Added'].dt.year == YEAR]
# Step 5: Count occurrences by Vendor and Product for entries with CISA Date Added in this year
vendor_product_counts = filtered_data.groupby(['Vendor', 'Product']).size().reset_index(name='Counts')
# Truncate vendor and product names to 15 characters for readability
vendor_product_counts['Vendor'] = vendor_product_counts['Vendor'].str.slice(0, 15)
vendor_product_counts['Product'] = vendor_product_counts['Product'].str.slice(0, 15)
# Create a treemap with both Vendor and Product levels
fig = px.treemap(vendor_product_counts, path=['Vendor', 'Product'], values='Counts',
color='Counts', color_continuous_scale='Viridis')
# Customize the title and layout for dark mode with specific width and height
fig.update_layout(
title=f"Known Exploited Vulnerabilities by Vendor and Product - {YEAR} CISA Entries (Source: CISA KEV)",
title_font=dict(size=20, color='white'),
title_x=0.5,
paper_bgcolor='black',
plot_bgcolor='black',
margin=dict(t=50, l=25, r=25, b=25),
width=1800,
height=1000
)
# Remove color bar by setting coloraxis_showscale to False
fig.update_coloraxes(showscale=False)
# Use default font for labels inside the treemap (Arial) by removing custom font settings
fig.update_traces(
texttemplate='%{label}<br> %{value}',
textfont_size=16, # Font size for readability
textfont_color='white' # Font color
)
# Display the figure
fig.show()
Loading...
2024 Known Exploited Vulnerabilities by Day - Year-to-Date (Source:Vulncheck KEV)¶
Source
df = df_original.copy()
# Title text
title_text = f"First Seen Exploitation Evidence per Unique CVE in {YEAR} (Source: VulnCheck KEV)"
# Step 1: Convert 'Date Added' to datetime format, handling errors
df['Date Added'] = pd.to_datetime(df['Date Added'], errors='coerce')
# Step 2: Drop rows where 'Date Added' could not be parsed
df = df.dropna(subset=['Date Added'])
# Step 3: Filter for entries where 'Date Added' is from this year
df_filtered = df[df['Date Added'].dt.year.isin([YEAR])]
# Step 4: Count occurrences of each 'Date Added' date
date_counts = df_filtered['Date Added'].value_counts().sort_index()
# Step 5: Convert the counts to a DataFrame for plotting
date_counts_df = date_counts.to_frame(name='Counts')
# Step 6: Set a continuous date range from Jan 1 to Dev 31
full_date_range = pd.date_range(start=f"{YEAR}-01-01", end=f"{YEAR}-12-31")
# Step 7: Reindex the DataFrame with the full date range, filling missing dates with zero counts
date_counts_df = date_counts_df.reindex(full_date_range, fill_value=0)
# Step 8: Plot the calendar heatmap for each year from this year using calplot
fig, ax = calplot.calplot(
date_counts_df['Counts'],
cmap='viridis',
vmin=0,
vmax=date_counts_df['Counts'].max(),
colorbar=False,
dropzero=True,
edgecolor="black",
textcolor="white",
textformat='{:.0f}',
figsize=(25, 10),
yearascending=False,
yearlabel_kws={'fontname':'sans-serif'}
)
# Set the figure and axes background to black
fig.patch.set_facecolor('black')
for a in ax:
a.set_facecolor('black')
# Modify the month and day labels to be white
for a in ax:
# Set month labels to white
for label in a.get_xticklabels():
label.set_color("white")
label.set_fontsize(12)
label.set_fontfamily('sans-serif')
# Set day labels to white
for label in a.get_yticklabels():
label.set_color("white")
label.set_fontsize(10)
label.set_fontfamily('sans-serif')
# Manually add black borders around each day cell
for a in ax:
for collection in a.collections:
collection.set_edgecolor("black")
collection.set_linewidth(0.5)
plt.show()

2024 Known Exploited Vulnerabilities by Day - Year-to-Date (Source:CISA KEV)¶
Source
df = df_original.copy()
# Title text
title_text = f"CISA KEV Added Date per Unique CVE in {YEAR} (Source: CISA KEV)"
# Step 1: Convert 'CISA Date Added' to datetime format, handling errors
df['CISA Date Added'] = pd.to_datetime(df['CISA Date Added'], errors='coerce', format='mixed')
# Step 2: Drop rows where 'CISA Date Added' could not be parsed
df.dropna(subset=['CISA Date Added'], inplace=True)
# Step 3: Filter for entries where 'CISA Date Added' is from the current year
df_filtered = df[df['CISA Date Added'].dt.year == YEAR]
# Step 4: Count occurrences of each 'CISA Date Added' date
date_counts = df_filtered['CISA Date Added'].value_counts().sort_index()
# Step 5: Convert the counts to a DataFrame for plotting
date_counts_df = date_counts.to_frame(name='Counts')
# Step 6: Set a continuous date range from Jan 1 to Dev 31
full_date_range = pd.date_range(start=f"{YEAR}-01-01", end=f"{YEAR}-12-31")
# Step 7: Reindex the DataFrame with the full date range, filling missing dates with zero counts
date_counts_df = date_counts_df.reindex(full_date_range, fill_value=0)
fig, ax = calplot.calplot(
date_counts_df['Counts'],
cmap='viridis',
vmin=0,
vmax=date_counts_df['Counts'].max(),
colorbar=False,
dropzero=True,
edgecolor="black",
textcolor="white",
textformat='{:.0f}',
figsize=(25, 10),
yearascending=False,
yearlabel_kws={'fontname':'sans-serif'}
)
# Set the figure and axes background to black
fig.patch.set_facecolor('black')
for a in ax:
a.set_facecolor('black')
# Modify the month and day labels to be white
for a in ax:
# Set month labels to white
for label in a.get_xticklabels():
label.set_color("white")
label.set_fontsize(12)
label.set_fontfamily('sans-serif')
# Set day labels to white
for label in a.get_yticklabels():
label.set_color("white")
label.set_fontsize(10)
label.set_fontfamily('sans-serif')
# Manually add black borders around each day cell
for a in ax:
for collection in a.collections:
collection.set_edgecolor("black")
collection.set_linewidth(0.5)
plt.show()

Exploitation Evidence Availability Before CISA KEV¶
Source
df = df_original.copy()
# Ensure 'Date Added' and 'CISA Date Added' are in datetime format
df['Date Added'] = pd.to_datetime(df['Date Added'], errors='coerce', format='mixed')
df['CISA Date Added'] = pd.to_datetime(df['CISA Date Added'], errors='coerce', format='mixed')
# Calculate 'Days Between' as the difference in days
df['Days Between'] = df.apply(
lambda row: (row['CISA Date Added'] - row['Date Added']).days if pd.notnull(row['CISA Date Added']) else "none",
axis=1
)
# Define the function to categorize the time intervals based on 'Days Between'
def categorize_time(days):
if days == "none":
return "none"
elif days >= 1460:
return '4+ years'
elif 730 <= days < 1460:
return '2+ years'
elif 365 <= days < 730:
return '1+ Years'
elif 31 <= days < 365:
return '1+ Months'
elif 7 <= days < 31:
return '1+ Weeks'
elif 1 <= days < 7:
return '1+ Days'
elif days == 0:
return 'Same Day'
else:
return "none" # Catch any unexpected cases
# Apply the categorization function to create the 'Time' column
df['Time'] = df['Days Between'].apply(categorize_time)
# Group by 'Time' and count occurrences to get the number of CVEs in each time bucket
df_time_counts = df.groupby('Time').size().reindex(
['4+ years', '2+ years', '1+ Years', '1+ Months', '1+ Weeks', '1+ Days', 'Same Day'], fill_value=0
).reset_index(name='CVEs')
# Calculate cumulative values and cumulative percentages for the waterfall chart
df_time_counts['Cumulative'] = df_time_counts['CVEs'].cumsum()
total_CVEs = df_time_counts['CVEs'].sum()
df_time_counts['Cumulative %'] = (df_time_counts['Cumulative'] / total_CVEs) * 100
# Define custom colors for dark mode
custom_color = '#440154'
text_color = 'white'
line_color = '#aaaaaa'
# Set dark background
plt.style.use('dark_background')
# Plotting the horizontal waterfall chart with cumulative percentages
fig, ax = plt.subplots(figsize=(12, 8), facecolor='black')
fig.patch.set_facecolor('black')
# Calculate bottom positions for the bars
bottom_positions = df_time_counts['Cumulative'].shift(1).fillna(0)
# Plotting each bar with a lighter color
ax.bar(df_time_counts['Time'], df_time_counts['CVEs'], bottom=bottom_positions, color=custom_color, edgecolor='lightgray')
# Add thicker horizontal lines at the tops of the bars to connect them
for i in range(1, len(df_time_counts)):
x1 = i - 1
x2 = i
y = bottom_positions.iloc[i-1] + df_time_counts['CVEs'].iloc[i-1]
# Draw the connecting horizontal line with increased thickness
ax.plot([x1, x2], [y, y], color='lightgray', linewidth=1)
# Annotate each bar with its value and cumulative percentage
for i, (time, cves, cum, cum_pct) in enumerate(zip(df_time_counts['Time'], df_time_counts['CVEs'], df_time_counts['Cumulative'], df_time_counts['Cumulative %'])):
ax.text(i, cum - cves / 2, f'{cves}', ha='center', va='center', color=text_color, fontsize=12, fontweight='bold')
ax.text(i, cum + 5, f'{cum_pct:.1f}%', ha='center', va='bottom', color=text_color, fontsize=12, fontweight='bold')
# Set title and labels in light color
ax.set_title('Exploitation Evidence Availability Before CISA KEV', fontsize=16, color=text_color)
ax.set_xlabel('Time Between Exploitation Evidence Availability and CISA KEV Inclusion', fontsize=14, color=text_color)
ax.set_ylabel('Number of CVEs / Coverage %', fontsize=14, color=text_color)
# Set x and y axis colors to light gray
ax.tick_params(colors=line_color)
ax.spines['bottom'].set_color(line_color)
ax.spines['left'].set_color(line_color)
ax.text(
1, -0.17,
'Source: VulnCheck KEV',
ha='right', va='top',
color=text_color,
fontsize=10,
fontweight='bold',
transform=ax.transAxes
)
plt.xticks(rotation=45, color=text_color)
plt.yticks(color=text_color)
plt.tight_layout()
plt.show()
