Initial Configuration¶
import os
import vulncheck_sdk
import matplotlib.pyplot as plt
import pandas as pd
import plotly.express as px
import calplot
from dotenv import load_dotenv
load_dotenv()
DEFAULT_HOST = "https://api.vulncheck.com"
DEFAULT_API = DEFAULT_HOST + "/v3"
TOKEN = os.environ["VULNCHECK_API_TOKEN"]
YEAR = 2025
# Configure the VulnCheck API client
configuration = vulncheck_sdk.Configuration(host=DEFAULT_API)
configuration.api_key["Bearer"] = TOKENPull Data¶
with vulncheck_sdk.ApiClient(configuration) as api_client:
indices_client = vulncheck_sdk.IndicesApi(api_client)
limit = 2000
# Initialize lists to store vendor and CVE data
ip = []
port = []
source_country = []
cve = []
signature = []
category = []
# Make the initial request to start pagination
api_response = indices_client.index_vulncheck_canaries3d_get(start_cursor="true", limit=limit)
# Process the first page of results
for entry in api_response.data:
# Directly access the first element of the list
ip.append(entry.src_ip)
port.append(entry.src_port)
source_country.append(entry.src_country)
cve.append(entry.cve)
signature.append(entry.signature)
category.append(entry.category)
# Continue fetching data while there's a next cursor
while api_response.meta.next_cursor is not None:
# Fetch the next page using the cursor
api_response = indices_client.index_vulncheck_canaries3d_get(
cursor=api_response.meta.next_cursor, limit=limit
)
# Append the new data from the next page
ip.append(entry.src_ip)
port.append(entry.src_port)
source_country.append(entry.src_country)
cve.append(entry.cve)
signature.append(entry.signature)
category.append(entry.category)
# Create a DataFrame from the accumulated data
df_original = pd.DataFrame({
'Source IP': ip,
'Source Port': port,
'Source Country': source_country,
'CVE': cve,
'Signature': signature,
'Category': category
})Canary Detections by Country¶
# Count source countries and create DataFrame
country_counts = df_original['Source Country'].fillna('Unknown').value_counts()
country_counts_df = (
country_counts.rename_axis('Source Country')
.reset_index(name='Count')
.sort_values('Count', ascending=False)
)
# Set up dark mode for the plot
plt.style.use('dark_background')
plt.figure(figsize=(12, 6))
# Create bar plot
plt.bar(range(len(country_counts_df.head(15))),
country_counts_df['Count'].head(15),
color='red')
# Customize x-axis with bold labels
plt.xticks(range(len(country_counts_df.head(15))),
country_counts_df['Source Country'].head(15),
rotation=45,
ha='right',
color='white',
fontweight='bold')
# Customize y-axis and labels
plt.ylabel('Detection Count', color='white', fontweight='bold')
for label in plt.gca().yaxis.get_ticklabels():
label.set_fontweight('bold')
plt.tick_params(axis='y', colors='white', labelsize=10)
# Add title
plt.title('Top 15 Source Countries by Detection Count',
color='white',
fontweight='bold',
pad=20)
# Set background color
plt.gca().set_facecolor('black')
# Adjust layout
plt.tight_layout()
plt.show()
Top 15 CVEs by Canary Exploitation Activity¶
# Normalize CVE column to lists, treating missing as 'Unknown'
def to_list(x):
if pd.isna(x):
return ['Unknown']
if isinstance(x, list):
return x if len(x) > 0 else ['Unknown']
if isinstance(x, str) and ',' in x:
return [item.strip() for item in x.split(',') if item.strip()] or ['Unknown']
return [x]
cve_series = df_original['CVE'].apply(to_list).explode()
cve_counts = cve_series.fillna('Unknown').value_counts()
# Create DataFrame with counts
cve_counts_df = (
cve_counts.rename_axis('CVE')
.reset_index(name='Count')
.sort_values('Count', ascending=False)
)
# Set up dark mode for the plot
plt.style.use('dark_background')
plt.figure(figsize=(14, 7))
# Create bar plot
plt.bar(range(len(cve_counts_df.head(15))),
cve_counts_df['Count'].head(15),
color='red')
# Customize x-axis with vertical bold labels
plt.xticks(range(len(cve_counts_df.head(15))),
cve_counts_df['CVE'].head(15),
rotation=90,
ha='center',
color='white',
fontweight='bold')
# Customize y-axis and labels
plt.ylabel('Detection Count', color='white', fontweight='bold')
for label in plt.gca().yaxis.get_ticklabels():
label.set_fontweight('bold')
plt.tick_params(axis='y', colors='white', labelsize=10)
# Add title
plt.title('Top 15 CVEs by Detection Count',
color='white',
fontweight='bold',
pad=20)
# Set background color
plt.gca().set_facecolor('black')
# Adjust layout to prevent label cutoff
plt.tight_layout()
plt.show()
Top Source IPs Detected by VulnCheck Canaries¶
The top 5 source IP addresses observed exploiting VulnCheck Canaries.
# Top 5 source IP addresses by occurrence (styled table similar to stats)
ip_counts = df_original['Source IP'].fillna('Unknown').value_counts()
ip_counts_df = (
ip_counts.rename_axis('Source IP')
.reset_index(name='Count')
.sort_values('Count', ascending=False)
)
top_n = 5
# Prepare the top-N DataFrame
top_df = ip_counts_df.head(top_n).copy()
top_df['Count'] = top_df['Count'].astype(int)
# Try to use pandas Styler (match the style used in the provided snippet). If unavailable, fall back to HTML.
from IPython.display import display, HTML
try:
styled = (
top_df.style
.set_properties(**{'text-align': 'center'})
.set_table_styles([{'selector': 'th', 'props': [('text-align', 'center')]}])
.set_table_attributes('style="width:50%; border-collapse: collapse;"')
.hide(axis='index')
.format({'Count': '{:,}'})
)
display(styled)
except Exception as e:
# Fallback: render HTML table without index and inject CSS
html_table = top_df.to_html(index=False, classes="styled-table", escape=False)
css = (
"<style>"
".styled-table{background-color:#000000;color:#ffffff;border-collapse:collapse;width:50%;font-family:Arial,Helvetica,sans-serif;}"
".styled-table th, .styled-table td{padding:8px 12px;border:none;text-align:center;vertical-align:middle;}"
".styled-table th{background-color:#0b0b0b;}"
"</style>"
)
html = css + html_table
display(HTML(html))Loading...