Skip to article frontmatterSkip to article content

2024 VulnCheck KEV

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()
<Figure size 2500x1000 with 1 Axes>

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()
<Figure size 2500x1000 with 1 Axes>

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()
<Figure size 1200x800 with 1 Axes>