mirror of
https://github.com/0xMarcio/cve.git
synced 2026-02-12 14:32:50 +00:00
Add KEV/EPSS static site generator
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1 +1,9 @@
|
||||
data/
|
||||
.venv/
|
||||
docs/api/
|
||||
docs/cve/
|
||||
docs/epss/
|
||||
docs/kev/
|
||||
docs/diffs/
|
||||
docs/vendors/
|
||||
scripts/__pycache__/
|
||||
|
||||
15
docs/assets/site.js
Normal file
15
docs/assets/site.js
Normal file
@@ -0,0 +1,15 @@
|
||||
(function(){
|
||||
const filterInputs = document.querySelectorAll('[data-filter-table]');
|
||||
filterInputs.forEach(input => {
|
||||
const tableId = input.dataset.filterTable;
|
||||
const table = document.getElementById(tableId);
|
||||
if (!table) return;
|
||||
input.addEventListener('input', () => {
|
||||
const term = input.value.trim().toLowerCase();
|
||||
for (const row of table.querySelectorAll('tbody tr')) {
|
||||
const text = row.innerText.toLowerCase();
|
||||
row.style.display = text.includes(term) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
54
docs/assets/style.css
Normal file
54
docs/assets/style.css
Normal file
@@ -0,0 +1,54 @@
|
||||
:root {
|
||||
--bg: #0b0c10;
|
||||
--panel: #11131a;
|
||||
--text: #e5e8f0;
|
||||
--muted: #9aa3b5;
|
||||
--accent: #5ad4e6;
|
||||
--warn: #f6c177;
|
||||
--border: #1f2430;
|
||||
--shadow: 0 10px 30px rgba(0,0,0,0.35);
|
||||
font-family: "Inter", system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; background: var(--bg); color: var(--text); }
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.wrap { width: min(1100px, 95vw); margin: 0 auto; padding: 1.5rem 0; }
|
||||
.site-header { background: var(--panel); border-bottom: 1px solid var(--border); position: sticky; top:0; z-index:10; box-shadow: var(--shadow); }
|
||||
.site-header .wrap { display:flex; align-items:center; justify-content: space-between; padding: 0.9rem 0; }
|
||||
.brand a { font-weight: 700; letter-spacing: 0.5px; }
|
||||
nav a { margin-left: 1rem; color: var(--text); opacity: 0.85; }
|
||||
nav a:hover { opacity: 1; }
|
||||
|
||||
h1, h2, h3 { margin: 0 0 0.5rem; }
|
||||
section { margin-bottom: 2rem; }
|
||||
.lead { color: var(--muted); line-height: 1.5; }
|
||||
|
||||
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
|
||||
.card { background: var(--panel); padding: 1rem; border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow); }
|
||||
.card-title { font-weight: 700; margin-bottom: 0.2rem; }
|
||||
.card-meta { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.5rem; }
|
||||
.badge { display: inline-block; background: rgba(90,212,230,0.12); color: var(--accent); padding: 0.15rem 0.5rem; border-radius: 999px; font-size: 0.8rem; margin-right: 0.25rem; }
|
||||
|
||||
.filter { width: 100%; padding: 0.65rem 0.75rem; margin: 0 0 0.75rem; border-radius: 8px; border: 1px solid var(--border); background: #0f1320; color: var(--text); }
|
||||
|
||||
.table-responsive { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow); }
|
||||
.table-responsive table { width: 100%; border-collapse: collapse; }
|
||||
.table-responsive th, .table-responsive td { padding: 0.75rem 0.9rem; border-bottom: 1px solid var(--border); text-align: left; }
|
||||
.table-responsive th { background: #161a22; color: #d6dae6; font-size: 0.9rem; letter-spacing: 0.2px; }
|
||||
.table-responsive tr:last-child td { border-bottom: none; }
|
||||
|
||||
.pill-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.8rem 0 1rem; }
|
||||
.pill { padding: 0.35rem 0.65rem; border-radius: 999px; background: #1b202c; border: 1px solid var(--border); color: var(--text); font-size: 0.9rem; }
|
||||
.pill-warn { background: rgba(246,193,119,0.15); border-color: #f6c177; color: #f6c177; }
|
||||
|
||||
.site-footer { border-top: 1px solid var(--border); padding: 1rem 0; color: var(--muted); }
|
||||
.site-footer .wrap { display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.9rem; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
nav a { margin-left: 0.6rem; }
|
||||
.card-grid { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
|
||||
.table-responsive th, .table-responsive td { padding: 0.6rem; }
|
||||
}
|
||||
359
docs/index.html
359
docs/index.html
@@ -1,54 +1,317 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Search utility for POCs">
|
||||
<meta name="keywords" content="0xmarcio, hacking, pentesting">
|
||||
<meta name="author" content="0xMarcio">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title>0xMarcio - CVE POCs</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>CVE Intelligence</title>
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script defer src="/assets/site.js"></script>
|
||||
</head>
|
||||
<body class="color-no-search">
|
||||
<div class="container">
|
||||
<div class="search">
|
||||
<div class="header">
|
||||
<h1>CVE POCs</h1>
|
||||
</div>
|
||||
<div class="query">
|
||||
<form class="searchForm" action="#">
|
||||
<input type="text" class="search" placeholder="ENTER SEARCH TERM" autocomplete="false">
|
||||
</form>
|
||||
</div>
|
||||
<div class="results" style="display:none">
|
||||
<br>
|
||||
<div class="noResults">
|
||||
<h2>No Results Found</h2>
|
||||
</div>
|
||||
<div class="results-table">
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="wrap">
|
||||
<div class="brand"><a href="/">CVE Radar</a></div>
|
||||
<nav>
|
||||
<a href="/kev/">KEV</a>
|
||||
<a href="/epss/">EPSS</a>
|
||||
<a href="/diffs/">Diffs</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main class="wrap">
|
||||
<section>
|
||||
<h1>Top KEV by EPSS percentile</h1>
|
||||
<div class="card-grid">
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-9242.html">CVE-2025-9242</a></div>
|
||||
<div class="card-meta">EPSS 0.744 • 99th pct</div>
|
||||
<p>An Out-of-bounds Write vulnerability in WatchGuard Fireware OS may allow a remote unauthenticated attacker to execute arbitrary code. This vulnerability affects both the Mobile User VPN with IKEv2 and the Branch Offic...</p>
|
||||
<div class="badge">WatchGuard</div>
|
||||
<div class="badge">Firebox</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-7775.html">CVE-2025-7775</a></div>
|
||||
<div class="card-meta">EPSS 0.174 • 95th pct</div>
|
||||
<p>Memory overflow vulnerability leading to Remote Code Execution and/or Denial of Service in NetScaler ADC and NetScaler Gateway when NetScaler is configured as Gateway (VPN virtual server, ICA Proxy, CVPN, RDP Proxy) o...</p>
|
||||
<div class="badge">Citrix</div>
|
||||
<div class="badge">NetScaler</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-9377.html">CVE-2025-9377</a></div>
|
||||
<div class="card-meta">EPSS 0.146 • 94th pct</div>
|
||||
<p>The authenticated remote command execution (RCE) vulnerability exists in the Parental Control page on TP-Link Archer C7(EU) V2 and TL-WR841N/ND(MS) V9.This issue affects Archer C7(EU) V2: before 241108 and TL-WR841N/N...</p>
|
||||
<div class="badge">TP-Link</div>
|
||||
<div class="badge">Multiple Routers</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-8876.html">CVE-2025-8876</a></div>
|
||||
<div class="card-meta">EPSS 0.139 • 94th pct</div>
|
||||
<p>Improper Input Validation vulnerability in N-able N-central allows OS Command Injection.This issue affects N-central: before 2025.3.1.</p>
|
||||
<div class="badge">N-able</div>
|
||||
<div class="badge">N-Central</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-8875.html">CVE-2025-8875</a></div>
|
||||
<div class="card-meta">EPSS 0.051 • 89th pct</div>
|
||||
<p>Deserialization of Untrusted Data vulnerability in N-able N-central allows Local Execution of Code.This issue affects N-central: before 2025.3.1.</p>
|
||||
<div class="badge">N-able</div>
|
||||
<div class="badge">N-Central</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-8088.html">CVE-2025-8088</a></div>
|
||||
<div class="card-meta">EPSS 0.032 • 86th pct</div>
|
||||
<p>A path traversal vulnerability affecting the Windows version of WinRAR allows the attackers to execute arbitrary code by crafting malicious archive files. This vulnerability was exploited in the wild and was discovere...</p>
|
||||
<div class="badge">RARLAB</div>
|
||||
<div class="badge">WinRAR</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2002-0367.html">CVE-2002-0367</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>smss.exe debugging subsystem in Windows NT and Windows 2000 does not properly authenticate programs that connect to other programs, which allows local users to gain administrator or SYSTEM privileges by duplicating a...</p>
|
||||
<div class="badge">Microsoft</div>
|
||||
<div class="badge">Windows</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2004-0210.html">CVE-2004-0210</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>The POSIX component of Microsoft Windows NT and Windows 2000 allows local users to execute arbitrary code via certain parameters, possibly by modifying message length values and causing a buffer overflow.</p>
|
||||
<div class="badge">Microsoft</div>
|
||||
<div class="badge">Windows</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2004-1464.html">CVE-2004-1464</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>Cisco IOS 12.2(15) and earlier allows remote attackers to cause a denial of service (refused VTY (virtual terminal) connections), via a crafted TCP connection to the Telnet or reverse Telnet port.</p>
|
||||
<div class="badge">Cisco</div>
|
||||
<div class="badge">IOS</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2005-2773.html">CVE-2005-2773</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>HP OpenView Network Node Manager 6.2 through 7.50 allows remote attackers to execute arbitrary commands via shell metacharacters in the (1) node parameter to connectedNodes.ovpl, (2) cdpView.ovpl, (3) freeIPaddrs.ovpl...</p>
|
||||
<div class="badge">Hewlett Packard (HP)</div>
|
||||
<div class="badge">OpenView Network Node Manager</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2006-1547.html">CVE-2006-1547</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>ActionForm in Apache Software Foundation (ASF) Struts before 1.2.9 with BeanUtils 1.7 allows remote attackers to cause a denial of service via a multipart/form-data encoded form with a parameter name that references t...</p>
|
||||
<div class="badge">Apache</div>
|
||||
<div class="badge">Struts 1</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2006-2492.html">CVE-2006-2492</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>Buffer overflow in Microsoft Word in Office 2000 SP3, Office XP SP3, Office 2003 Sp1 and SP2, and Microsoft Works Suites through 2006, allows user-assisted attackers to execute arbitrary code via a malformed object po...</p>
|
||||
<div class="badge">Microsoft</div>
|
||||
<div class="badge">Word</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2007-0671.html">CVE-2007-0671</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>Unspecified vulnerability in Microsoft Excel 2000, XP, 2003, and 2004 for Mac, and possibly other Office products, allows remote user-assisted attackers to execute arbitrary code via unknown attack vectors, as demonst...</p>
|
||||
<div class="badge">Microsoft</div>
|
||||
<div class="badge">Office</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2007-3010.html">CVE-2007-3010</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>masterCGI in the Unified Maintenance Tool in Alcatel OmniPCX Enterprise Communication Server R7.1 and earlier allows remote attackers to execute arbitrary commands via shell metacharacters in the user parameter during...</p>
|
||||
<div class="badge">Alcatel</div>
|
||||
<div class="badge">OmniPCX Enterprise</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2007-5659.html">CVE-2007-5659</a></div>
|
||||
<div class="card-meta">EPSS 0.000 • 0th pct</div>
|
||||
<p>Multiple buffer overflows in Adobe Reader and Acrobat 8.1.1 and earlier allow remote attackers to execute arbitrary code via a PDF file with long arguments to unspecified JavaScript methods. NOTE: this issue might be...</p>
|
||||
<div class="badge">Adobe</div>
|
||||
<div class="badge">Acrobat and Reader</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<table class="results">
|
||||
<thead>
|
||||
<tr>
|
||||
<td width="15%">
|
||||
CVE
|
||||
</td>
|
||||
<td>
|
||||
Description / POC
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="results"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
© 0xMarcio 2025
|
||||
<br>
|
||||
Found a bug? File it or fix it <a href="https://github.com/0xMarcio/cve/issues">here</a>
|
||||
</div>
|
||||
</div>
|
||||
<script src="logic.js"></script>
|
||||
<section>
|
||||
<h1>High EPSS not in KEV</h1>
|
||||
<div class="card-grid">
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-9316.html">CVE-2025-9316</a></div>
|
||||
<div class="card-meta">EPSS 0.787 • 99th pct</div>
|
||||
<p>No description.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/CVE-2025-8943.html">CVE-2025-8943</a></div>
|
||||
<div class="card-meta">EPSS 0.658 • 98th pct</div>
|
||||
<p>The Custom MCPs feature is designed to execute OS commands, for instance, using tools like `npx` to spin up local MCP Servers. However, Flowise's inherent authentication and authorization model is minimal and lacks ro...</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>Trending PoCs</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1241</td>
|
||||
<td>2 hours ago </td>
|
||||
<td><a href="https://github.com/msanft/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
|
||||
<td>Explanation and full RCE PoC for CVE-2025-55182 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>775</td>
|
||||
<td>3 hours ago </td>
|
||||
<td><a href="https://github.com/ejpir/CVE-2025-55182-research" target="_blank">CVE-2025-55182-research</a></td>
|
||||
<td>CVE-2025-55182 POC </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>495</td>
|
||||
<td>8 days ago </td>
|
||||
<td><a href="https://github.com/WyAtu/CVE-2018-20250" target="_blank">CVE-2018-20250</a></td>
|
||||
<td>exp for https://research.checkpoint.com/extracting-code-execution-from-winrar </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>607</td>
|
||||
<td>20 hours ago </td>
|
||||
<td><a href="https://github.com/mverschu/CVE-2025-33073" target="_blank">CVE-2025-33073</a></td>
|
||||
<td>PoC Exploit for the NTLM reflection SMB flaw. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>496</td>
|
||||
<td>4 days ago </td>
|
||||
<td><a href="https://github.com/pr0v3rbs/CVE-2025-32463_chwoot" target="_blank">CVE-2025-32463_chwoot</a></td>
|
||||
<td>Escalation of Privilege to the root through sudo binary with chroot option. CVE-2025-32463 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>419</td>
|
||||
<td>5 hours ago </td>
|
||||
<td><a href="https://github.com/kh4sh3i/CVE-2025-32463" target="_blank">CVE-2025-32463</a></td>
|
||||
<td>Local Privilege Escalation to Root via Sudo chroot in Linux </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>305</td>
|
||||
<td>1 day ago </td>
|
||||
<td><a href="https://github.com/soltanali0/CVE-2025-53770-Exploit" target="_blank">CVE-2025-53770-Exploit</a></td>
|
||||
<td>SharePoint WebPart Injection Exploit Tool </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>289</td>
|
||||
<td>4 hours ago </td>
|
||||
<td><a href="https://github.com/emredavut/CVE-2025-55182" target="_blank">CVE-2025-55182</a></td>
|
||||
<td>RSC/Next.js RCE Vulnerability Detector & PoC Chrome Extension – CVE-2025-55182 & CVE-2025-66478 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>901</td>
|
||||
<td>1 hour ago </td>
|
||||
<td><a href="https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc" target="_blank">React2Shell-CVE-2025-55182-original-poc</a></td>
|
||||
<td>Original Proof-of-Concepts for React2Shell CVE-2025-55182 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>386</td>
|
||||
<td>4 days ago </td>
|
||||
<td><a href="https://github.com/0x6rss/CVE-2025-24071_PoC" target="_blank">CVE-2025-24071_PoC</a></td>
|
||||
<td>CVE-2025-24071: NTLM Hash Leak via RAR/ZIP Extraction and .library-ms File </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>207</td>
|
||||
<td>1 day ago </td>
|
||||
<td><a href="https://github.com/leesh3288/CVE-2025-32023" target="_blank">CVE-2025-32023</a></td>
|
||||
<td>PoC & Exploit for CVE-2025-32023 / PlaidCTF 2025 "Zerodeo" </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>396</td>
|
||||
<td>6 days ago </td>
|
||||
<td><a href="https://github.com/yuuouu/ColorOS-CVE-2025-10184" target="_blank">ColorOS-CVE-2025-10184</a></td>
|
||||
<td>ColorOS短信漏洞,以及用户自救方案 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>180</td>
|
||||
<td>6 days ago </td>
|
||||
<td><a href="https://github.com/absholi7ly/POC-CVE-2025-24813" target="_blank">POC-CVE-2025-24813</a></td>
|
||||
<td>his repository contains an automated Proof of Concept (PoC) script for exploiting **CVE-2025-24813**, a Remote Code Execution (RCE) vulnerability in Apache Tomcat. The vulnerability allows an attacker to upload a malicious serialized payload to the server, leading to arbitrary code execution via deserialization when specific conditions are met. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>256</td>
|
||||
<td>15 minutes ago </td>
|
||||
<td><a href="https://github.com/zack0x01/CVE-2025-55182-advanced-scanner-" target="_blank">CVE-2025-55182-advanced-scanner-</a></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>357</td>
|
||||
<td>1 hour ago </td>
|
||||
<td><a href="https://github.com/Malayke/Next.js-RSC-RCE-Scanner-CVE-2025-66478" target="_blank">Next.js-RSC-RCE-Scanner-CVE-2025-66478</a></td>
|
||||
<td>A command-line scanner for batch detection of Next.js application versions and determining if they are affected by CVE-2025-66478 vulnerability. </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>198</td>
|
||||
<td>4 days ago </td>
|
||||
<td><a href="https://github.com/ThumpBo/CVE-2025-30208-EXP" target="_blank">CVE-2025-30208-EXP</a></td>
|
||||
<td>CVE-2025-30208-EXP </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>73</td>
|
||||
<td>6 days ago </td>
|
||||
<td><a href="https://github.com/4daysday/cve-2025-8088" target="_blank">cve-2025-8088</a></td>
|
||||
<td>Path traversal tool based on cve-2025-8088 </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>163</td>
|
||||
<td>1 day ago </td>
|
||||
<td><a href="https://github.com/ZeroMemoryEx/CVE-2025-26125" target="_blank">CVE-2025-26125</a></td>
|
||||
<td>( 0day ) Local Privilege Escalation in IObit Malware Fighter </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>153</td>
|
||||
<td>8 days ago </td>
|
||||
<td><a href="https://github.com/hoefler02/CVE-2025-21756" target="_blank">CVE-2025-21756</a></td>
|
||||
<td>Exploit for CVE-2025-21756 for Linux kernel 6.6.75. My first linux kernel exploit! </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>136</td>
|
||||
<td>27 days ago </td>
|
||||
<td><a href="https://github.com/platsecurity/CVE-2025-32433" target="_blank">CVE-2025-32433</a></td>
|
||||
<td>CVE-2025-32433 https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2 </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>Changes since yesterday</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>Type</th><th>Count</th><th>Examples</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>New KEV entries</td>
|
||||
<td>75</td>
|
||||
<td>
|
||||
<a href="/cve/CVE-2025-9242.html">CVE-2025-9242</a>, <a href="/cve/CVE-2025-7775.html">CVE-2025-7775</a>, <a href="/cve/CVE-2025-9377.html">CVE-2025-9377</a>, <a href="/cve/CVE-2025-8876.html">CVE-2025-8876</a>, <a href="/cve/CVE-2025-8875.html">CVE-2025-8875</a> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New high EPSS</td>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<a href="/cve/CVE-2025-9316.html">CVE-2025-9316</a>, <a href="/cve/CVE-2025-8943.html">CVE-2025-8943</a> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Top EPSS movers</td>
|
||||
<td>0</td>
|
||||
<td>
|
||||
None </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<div class="wrap">
|
||||
<span>Updated 2025-12-17</span>
|
||||
<span>Data: CISA KEV, FIRST EPSS, community PoCs</span>
|
||||
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
requests
|
||||
jinja2
|
||||
python-dateutil
|
||||
12
scripts/README.md
Normal file
12
scripts/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Build pipeline
|
||||
|
||||
```
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python scripts/fetch_kev.py
|
||||
python scripts/fetch_epss.py
|
||||
python scripts/build_site.py
|
||||
```
|
||||
|
||||
Outputs land in `docs/` and JSON under `docs/api/v1/`. Snapshots live in `docs/api/v1/snapshots/` (last 14 days) and diffs under `docs/api/v1/diff/`.
|
||||
111
scripts/build_diffs.py
Normal file
111
scripts/build_diffs.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import date, datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from utils import API_DIR, SNAPSHOT_DIR, ensure_dirs, load_json, save_json
|
||||
|
||||
DEFAULT_LOOKBACK_DAYS = 14
|
||||
DEFAULT_HIGH_EPSS_THRESHOLD = 0.5
|
||||
DEFAULT_MAX_MOVERS = 50
|
||||
|
||||
|
||||
def parse_date(date_str: str) -> date:
|
||||
return datetime.strptime(date_str, "%Y-%m-%d").date()
|
||||
|
||||
|
||||
def load_snapshot(path: Path) -> Dict:
|
||||
return load_json(path, default={}) or {}
|
||||
|
||||
|
||||
def diff_lists(prev: List[Dict], curr: List[Dict], key: str = "cve") -> Dict[str, List[Dict]]:
|
||||
prev_ids = {item[key]: item for item in prev}
|
||||
curr_ids = {item[key]: item for item in curr}
|
||||
new_items = [curr_ids[cve] for cve in sorted(curr_ids.keys() - prev_ids.keys())]
|
||||
removed_items = [prev_ids[cve] for cve in sorted(prev_ids.keys() - curr_ids.keys())]
|
||||
return {"new": new_items, "removed": removed_items}
|
||||
|
||||
|
||||
def compute_epss_movers(prev_epss: Dict[str, Dict], curr_epss: Dict[str, Dict], max_items: int) -> List[Dict]:
|
||||
deltas = []
|
||||
for cve, curr in curr_epss.items():
|
||||
prev = prev_epss.get(cve)
|
||||
if not prev:
|
||||
continue
|
||||
delta = (curr.get("epss") or 0) - (prev.get("epss") or 0)
|
||||
if abs(delta) < 0.0001:
|
||||
continue
|
||||
deltas.append({"cve": cve, "delta": round(delta, 5), "epss": curr.get("epss"), "prev_epss": prev.get("epss")})
|
||||
deltas.sort(key=lambda row: (-row["delta"], row["cve"]))
|
||||
return deltas[:max_items]
|
||||
|
||||
|
||||
def build_diff(snapshots: List[Path], *, threshold: float, max_movers: int) -> Tuple[Dict, Path | None]:
|
||||
if not snapshots:
|
||||
return {}, None
|
||||
latest_path = snapshots[-1]
|
||||
latest = load_snapshot(latest_path)
|
||||
latest_date = latest.get("generated") or latest_path.stem
|
||||
|
||||
if len(snapshots) >= 2:
|
||||
prev = load_snapshot(snapshots[-2])
|
||||
kev_diff = diff_lists(prev.get("kev_top", []), latest.get("kev_top", []))
|
||||
high_epss_diff = diff_lists(prev.get("high_epss", []), latest.get("high_epss", []))
|
||||
else:
|
||||
prev = {}
|
||||
kev_diff = {"new": latest.get("kev_top", []), "removed": []}
|
||||
high_epss_diff = {"new": latest.get("high_epss", []), "removed": []}
|
||||
|
||||
prev_epss_lookup = {row["cve"]: row for row in (prev.get("high_epss", []) if prev else [])}
|
||||
curr_epss_lookup = {row["cve"]: row for row in latest.get("high_epss", [])}
|
||||
epss_movers = compute_epss_movers(prev_epss_lookup, curr_epss_lookup, max_movers)
|
||||
|
||||
diff_outputs = {
|
||||
"generated": latest_date,
|
||||
"new_kev_entries": kev_diff["new"],
|
||||
"removed_kev_entries": kev_diff["removed"],
|
||||
"new_high_epss": [row for row in high_epss_diff["new"] if (row.get("epss") or 0) >= threshold],
|
||||
"removed_high_epss": high_epss_diff["removed"],
|
||||
"epss_movers": epss_movers,
|
||||
}
|
||||
|
||||
target = API_DIR / "diff" / f"{latest_date}.json"
|
||||
ensure_dirs(target.parent)
|
||||
save_json(target, diff_outputs)
|
||||
# also write a stable latest pointer
|
||||
save_json(target.parent / "latest.json", diff_outputs)
|
||||
|
||||
return diff_outputs, target
|
||||
|
||||
|
||||
def prune_snapshots(snapshots: List[Path], *, lookback_days: int) -> None:
|
||||
cutoff = datetime.utcnow().date() - timedelta(days=lookback_days)
|
||||
for snap in snapshots:
|
||||
snap_date = parse_date(snap.stem)
|
||||
if snap_date < cutoff:
|
||||
snap.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build daily diff JSON from snapshots")
|
||||
parser.add_argument("--threshold", type=float, default=DEFAULT_HIGH_EPSS_THRESHOLD, help="High EPSs minimum threshold")
|
||||
parser.add_argument("--lookback", type=int, default=DEFAULT_LOOKBACK_DAYS, help="How many days of snapshots to keep")
|
||||
parser.add_argument("--max-movers", type=int, default=DEFAULT_MAX_MOVERS, help="Max EPSs movers to keep")
|
||||
args = parser.parse_args()
|
||||
|
||||
ensure_dirs(SNAPSHOT_DIR)
|
||||
snapshots = sorted(SNAPSHOT_DIR.glob("*.json"))
|
||||
diff, target = build_diff(snapshots, threshold=args.threshold, max_movers=args.max_movers)
|
||||
if target:
|
||||
print(f"Wrote diff to {target}")
|
||||
else:
|
||||
print("No snapshots available to diff")
|
||||
|
||||
prune_snapshots(snapshots, lookback_days=args.lookback)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
299
scripts/build_joined.py
Normal file
299
scripts/build_joined.py
Normal file
@@ -0,0 +1,299 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Set, Tuple
|
||||
|
||||
from utils import (
|
||||
API_DIR,
|
||||
DATA_DIR,
|
||||
DOCS_DIR,
|
||||
SNAPSHOT_DIR,
|
||||
load_json,
|
||||
save_json,
|
||||
today_str,
|
||||
ensure_dirs,
|
||||
load_poc_index,
|
||||
slugify,
|
||||
stable_unique,
|
||||
)
|
||||
|
||||
KEV_PATH = DATA_DIR / "kev.json"
|
||||
EPSS_PATH = DATA_DIR / "epss.json"
|
||||
|
||||
DEFAULT_TOP_KEV = 75
|
||||
DEFAULT_HIGH_EPSS_LIMIT = 250
|
||||
DEFAULT_HIGH_EPSS_THRESHOLD = 0.5
|
||||
|
||||
|
||||
def load_inputs(kev_path: Path, epss_path: Path) -> Tuple[Dict, Dict]:
|
||||
kev_data = load_json(kev_path, default={}) or {}
|
||||
epss_data = load_json(epss_path, default={}) or {}
|
||||
return kev_data, epss_data
|
||||
|
||||
|
||||
def enrich_kev(kev_items: List[Dict], epss_lookup: Dict[str, Dict], poc_index: Dict[str, Dict]) -> List[Dict]:
|
||||
enriched = []
|
||||
for entry in kev_items:
|
||||
cve = entry.get("cve") or entry.get("cveID") or ""
|
||||
if not cve:
|
||||
continue
|
||||
cve = cve.upper()
|
||||
epss_info = epss_lookup.get(cve, {})
|
||||
poc_count = len(poc_index.get(cve, {}).get("poc", []))
|
||||
enriched.append(
|
||||
{
|
||||
"cve": cve,
|
||||
"vendor": entry.get("vendor") or entry.get("vendorProject", ""),
|
||||
"product": entry.get("product", ""),
|
||||
"date_added": entry.get("date_added") or entry.get("dateAdded"),
|
||||
"due_date": entry.get("due_date") or entry.get("dueDate"),
|
||||
"short_description": entry.get("short_description") or entry.get("shortDescription", ""),
|
||||
"required_action": entry.get("required_action") or entry.get("requiredAction", ""),
|
||||
"notes": entry.get("notes", ""),
|
||||
"epss": epss_info.get("epss"),
|
||||
"percentile": epss_info.get("percentile"),
|
||||
"poc_count": poc_count,
|
||||
}
|
||||
)
|
||||
enriched.sort(key=lambda row: (-float(row.get("percentile") or 0), row["cve"]))
|
||||
return enriched
|
||||
|
||||
|
||||
def build_epss_lookup(epss_items: List[Dict]) -> Dict[str, Dict]:
|
||||
return {row.get("cve", "").upper(): row for row in epss_items if row.get("cve")}
|
||||
|
||||
|
||||
def build_high_epss_not_in_kev(
|
||||
epss_items: List[Dict],
|
||||
kev_set: Set[str],
|
||||
poc_index: Dict[str, Dict],
|
||||
*,
|
||||
threshold: float,
|
||||
limit: int,
|
||||
) -> List[Dict]:
|
||||
output: List[Dict] = []
|
||||
for row in epss_items:
|
||||
cve = row.get("cve", "").upper()
|
||||
if not cve or cve in kev_set:
|
||||
continue
|
||||
epss_score = row.get("epss") or 0.0
|
||||
if epss_score < threshold:
|
||||
continue
|
||||
poc_count = len(poc_index.get(cve, {}).get("poc", []))
|
||||
output.append(
|
||||
{
|
||||
"cve": cve,
|
||||
"epss": row.get("epss"),
|
||||
"percentile": row.get("percentile"),
|
||||
"poc_count": poc_count,
|
||||
}
|
||||
)
|
||||
if len(output) >= limit:
|
||||
break
|
||||
return output
|
||||
|
||||
|
||||
def build_cve_details(
|
||||
kev_enriched: Iterable[Dict],
|
||||
high_epss: Iterable[Dict],
|
||||
poc_index: Dict[str, Dict],
|
||||
) -> Dict[str, Dict]:
|
||||
details: Dict[str, Dict] = {}
|
||||
|
||||
def ensure_detail(cve: str) -> Dict:
|
||||
if cve not in details:
|
||||
data = poc_index.get(cve, {})
|
||||
details[cve] = {
|
||||
"cve": cve,
|
||||
"description": data.get("desc", ""),
|
||||
"poc_links": data.get("poc", []),
|
||||
"poc_count": len(data.get("poc", [])),
|
||||
"kev": None,
|
||||
"epss": None,
|
||||
"percentile": None,
|
||||
"vendor": None,
|
||||
"product": None,
|
||||
}
|
||||
return details[cve]
|
||||
|
||||
for entry in kev_enriched:
|
||||
cve = entry["cve"]
|
||||
detail = ensure_detail(cve)
|
||||
detail.update(
|
||||
{
|
||||
"kev": {
|
||||
"date_added": entry.get("date_added"),
|
||||
"due_date": entry.get("due_date"),
|
||||
"short_description": entry.get("short_description"),
|
||||
"required_action": entry.get("required_action"),
|
||||
"notes": entry.get("notes"),
|
||||
},
|
||||
"epss": entry.get("epss"),
|
||||
"percentile": entry.get("percentile"),
|
||||
"vendor": entry.get("vendor"),
|
||||
"product": entry.get("product"),
|
||||
}
|
||||
)
|
||||
|
||||
for entry in high_epss:
|
||||
cve = entry["cve"]
|
||||
detail = ensure_detail(cve)
|
||||
if detail.get("epss") is None:
|
||||
detail["epss"] = entry.get("epss")
|
||||
detail["percentile"] = entry.get("percentile")
|
||||
|
||||
return details
|
||||
|
||||
|
||||
def build_vendor_map(details: Dict[str, Dict]) -> Dict[str, Dict]:
|
||||
vendors: Dict[str, Dict] = {}
|
||||
for detail in details.values():
|
||||
vendor_name = detail.get("vendor")
|
||||
if not vendor_name:
|
||||
continue
|
||||
slug = slugify(vendor_name)
|
||||
entry = vendors.setdefault(slug, {"vendor": vendor_name, "cves": []})
|
||||
entry["cves"].append(detail["cve"])
|
||||
|
||||
for value in vendors.values():
|
||||
value["cves"].sort()
|
||||
return dict(sorted(vendors.items(), key=lambda kv: kv[0]))
|
||||
|
||||
|
||||
def truncate_description(text: str, limit: int = 220) -> str:
|
||||
if not text:
|
||||
return ""
|
||||
text = " ".join(text.split())
|
||||
return text if len(text) <= limit else text[: limit - 3].rstrip() + "..."
|
||||
|
||||
|
||||
def build_joined(
|
||||
kev_data: Dict,
|
||||
epss_data: Dict,
|
||||
poc_index: Dict[str, Dict],
|
||||
*,
|
||||
top_kev: int = DEFAULT_TOP_KEV,
|
||||
high_epss_threshold: float = DEFAULT_HIGH_EPSS_THRESHOLD,
|
||||
high_epss_limit: int = DEFAULT_HIGH_EPSS_LIMIT,
|
||||
extra_cves: Iterable[str] | None = None,
|
||||
) -> Dict:
|
||||
kev_items = kev_data.get("items") or []
|
||||
epss_items = epss_data.get("items") or []
|
||||
|
||||
epss_lookup = build_epss_lookup(epss_items)
|
||||
kev_enriched = enrich_kev(kev_items, epss_lookup, poc_index)
|
||||
kev_top = kev_enriched[:top_kev]
|
||||
|
||||
kev_set = {row["cve"] for row in kev_enriched}
|
||||
high_epss = build_high_epss_not_in_kev(epss_items, kev_set, poc_index, threshold=high_epss_threshold, limit=high_epss_limit)
|
||||
|
||||
details = build_cve_details(kev_top, high_epss, poc_index)
|
||||
|
||||
if extra_cves:
|
||||
extra_set = {cve.upper() for cve in extra_cves}
|
||||
epss_lookup = build_epss_lookup(epss_items)
|
||||
kev_lookup = {row["cve"]: row for row in kev_enriched}
|
||||
for cve in sorted(extra_set):
|
||||
if cve in details:
|
||||
continue
|
||||
epss_row = epss_lookup.get(cve, {})
|
||||
kev_row = kev_lookup.get(cve)
|
||||
details[cve] = {
|
||||
"cve": cve,
|
||||
"description": poc_index.get(cve, {}).get("desc", ""),
|
||||
"poc_links": poc_index.get(cve, {}).get("poc", []),
|
||||
"poc_count": len(poc_index.get(cve, {}).get("poc", [])),
|
||||
"kev": None,
|
||||
"epss": epss_row.get("epss"),
|
||||
"percentile": epss_row.get("percentile"),
|
||||
"vendor": None,
|
||||
"product": None,
|
||||
}
|
||||
if kev_row:
|
||||
details[cve]["kev"] = {
|
||||
"date_added": kev_row.get("date_added"),
|
||||
"due_date": kev_row.get("due_date"),
|
||||
"short_description": kev_row.get("short_description"),
|
||||
"required_action": kev_row.get("required_action"),
|
||||
"notes": kev_row.get("notes"),
|
||||
}
|
||||
details[cve]["vendor"] = kev_row.get("vendor")
|
||||
details[cve]["product"] = kev_row.get("product")
|
||||
|
||||
vendors = build_vendor_map(details)
|
||||
|
||||
# add display summary
|
||||
for collection in (kev_top, high_epss):
|
||||
for row in collection:
|
||||
desc = poc_index.get(row["cve"], {}).get("desc") or ""
|
||||
row["summary"] = truncate_description(desc)
|
||||
|
||||
joined = {
|
||||
"generated": today_str(),
|
||||
"kev_top": kev_top,
|
||||
"high_epss": high_epss,
|
||||
}
|
||||
|
||||
return {
|
||||
"joined": joined,
|
||||
"kev_enriched": kev_enriched,
|
||||
"epss_items": epss_items,
|
||||
"details": details,
|
||||
"vendors": vendors,
|
||||
}
|
||||
|
||||
|
||||
def write_api_outputs(payload: Dict, *, api_dir: Path = API_DIR) -> None:
|
||||
ensure_dirs(api_dir, api_dir / "cve", SNAPSHOT_DIR)
|
||||
joined = payload["joined"]
|
||||
save_json(api_dir / "kev.json", {"generated": joined["generated"], "items": payload["kev_enriched"]})
|
||||
save_json(
|
||||
api_dir / "epss_top.json",
|
||||
{
|
||||
"generated": joined["generated"],
|
||||
"items": payload["joined"]["high_epss"],
|
||||
},
|
||||
)
|
||||
save_json(api_dir / "joined_top.json", joined)
|
||||
|
||||
for cve, detail in payload["details"].items():
|
||||
save_json(api_dir / "cve" / f"{cve}.json", detail)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Join KEV and EPSS with PoC data")
|
||||
parser.add_argument("--kev", type=Path, default=KEV_PATH, help="Path to KEV JSON")
|
||||
parser.add_argument("--epss", type=Path, default=EPSS_PATH, help="Path to EPSS JSON")
|
||||
parser.add_argument("--top-kev", type=int, default=DEFAULT_TOP_KEV, help="How many KEV rows to surface on top list")
|
||||
parser.add_argument(
|
||||
"--high-epss-threshold",
|
||||
type=float,
|
||||
default=DEFAULT_HIGH_EPSS_THRESHOLD,
|
||||
help="Minimum EPSS to include when selecting high EPSs CVEs",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--high-epss-limit",
|
||||
type=int,
|
||||
default=DEFAULT_HIGH_EPSS_LIMIT,
|
||||
help="Maximum number of high EPSs CVEs to keep",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
poc_index = load_poc_index()
|
||||
kev_data, epss_data = load_inputs(args.kev, args.epss)
|
||||
payload = build_joined(
|
||||
kev_data,
|
||||
epss_data,
|
||||
poc_index,
|
||||
top_kev=args.top_kev,
|
||||
high_epss_threshold=args.high_epss_threshold,
|
||||
high_epss_limit=args.high_epss_limit,
|
||||
)
|
||||
write_api_outputs(payload)
|
||||
print("Generated joined JSON endpoints under docs/api/v1/")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
105
scripts/build_site.py
Normal file
105
scripts/build_site.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from utils import (
|
||||
API_DIR,
|
||||
DOCS_DIR,
|
||||
TEMPLATES_DIR,
|
||||
ensure_dirs,
|
||||
load_json,
|
||||
load_poc_index,
|
||||
parse_trending_from_readme,
|
||||
save_json,
|
||||
)
|
||||
|
||||
from build_joined import build_joined, write_api_outputs
|
||||
from build_diffs import build_diff, prune_snapshots
|
||||
|
||||
KEV_DATA = DOCS_DIR.parent / "data" / "kev.json"
|
||||
EPSS_DATA = DOCS_DIR.parent / "data" / "epss.json"
|
||||
README_PATH = DOCS_DIR.parent / "README.md"
|
||||
|
||||
|
||||
def build_env() -> Environment:
|
||||
loader = FileSystemLoader(str(TEMPLATES_DIR))
|
||||
env = Environment(loader=loader, autoescape=select_autoescape(["html", "xml"]))
|
||||
env.trim_blocks = True
|
||||
env.lstrip_blocks = True
|
||||
return env
|
||||
|
||||
|
||||
def render(env: Environment, template_name: str, context: Dict, output_path: Path) -> None:
|
||||
html = env.get_template(template_name).render(**context)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(html, encoding="utf-8")
|
||||
|
||||
|
||||
def load_joined() -> Dict:
|
||||
kev = load_json(KEV_DATA, default={})
|
||||
epss = load_json(EPSS_DATA, default={})
|
||||
poc_index = load_poc_index()
|
||||
payload = build_joined(kev, epss, poc_index)
|
||||
write_api_outputs(payload)
|
||||
return payload
|
||||
|
||||
|
||||
def write_snapshot(joined: Dict) -> Path:
|
||||
snapshot_path = API_DIR / "snapshots" / f"{joined['generated']}.json"
|
||||
ensure_dirs(snapshot_path.parent)
|
||||
save_json(snapshot_path, joined)
|
||||
return snapshot_path
|
||||
|
||||
|
||||
def build_pages(env: Environment, data: Dict, diff: Dict | None = None) -> None:
|
||||
joined = data["joined"]
|
||||
details = data["details"]
|
||||
vendors = data["vendors"]
|
||||
trending = parse_trending_from_readme(README_PATH)
|
||||
|
||||
common_ctx = {"generated": joined["generated"]}
|
||||
render(
|
||||
env,
|
||||
"index.html",
|
||||
{**common_ctx, "data": joined, "trending": trending, "diff": diff or {}},
|
||||
DOCS_DIR / "index.html",
|
||||
)
|
||||
render(env, "kev.html", {**common_ctx, "kev": data["kev_enriched"]}, DOCS_DIR / "kev" / "index.html")
|
||||
render(env, "epss.html", {**common_ctx, "epss": joined["high_epss"]}, DOCS_DIR / "epss" / "index.html")
|
||||
render(env, "diffs.html", {**common_ctx, "diff": diff or {}}, DOCS_DIR / "diffs" / "index.html")
|
||||
|
||||
for cve, detail in details.items():
|
||||
render(env, "cve.html", {**common_ctx, "cve": detail}, DOCS_DIR / "cve" / f"{cve}.html")
|
||||
|
||||
for slug, vendor in vendors.items():
|
||||
cve_details = [details[cve] for cve in vendor["cves"] if cve in details]
|
||||
render(env, "vendor.html", {**common_ctx, "vendor": vendor, "cves": cve_details}, DOCS_DIR / "vendors" / f"{slug}.html")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Build static site and JSON")
|
||||
args = parser.parse_args()
|
||||
|
||||
ensure_dirs(DOCS_DIR, DOCS_DIR / "cve", DOCS_DIR / "vendors", DOCS_DIR / "kev", DOCS_DIR / "epss", DOCS_DIR / "diffs")
|
||||
|
||||
env = build_env()
|
||||
data = load_joined()
|
||||
# snapshot + diff before rendering so dashboard can show it
|
||||
snapshot_path = write_snapshot(data["joined"])
|
||||
snapshots = sorted((API_DIR / "snapshots").glob("*.json"))
|
||||
diff, target = build_diff(snapshots, threshold=0.5, max_movers=50)
|
||||
prune_snapshots(snapshots, lookback_days=14)
|
||||
|
||||
build_pages(env, data, diff)
|
||||
|
||||
# build daily diff after snapshot is written
|
||||
print("Site generated under docs/")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
93
scripts/fetch_epss.py
Normal file
93
scripts/fetch_epss.py
Normal file
@@ -0,0 +1,93 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
import requests
|
||||
|
||||
from utils import DATA_DIR, maybe_float, save_json, today_str
|
||||
|
||||
API_URL = "https://api.first.org/data/v1/epss"
|
||||
DEFAULT_LIMIT = 2000
|
||||
DEFAULT_BATCH = 1000
|
||||
|
||||
|
||||
def fetch_batch(offset: int, limit: int) -> Dict:
|
||||
params = {
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
"sort": "epss",
|
||||
"order": "desc",
|
||||
}
|
||||
response = requests.get(API_URL, params=params, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def normalise_rows(raw_rows: List[Dict]) -> List[Dict]:
|
||||
normalised = []
|
||||
for row in raw_rows:
|
||||
cve = str(row.get("cve", "")).upper()
|
||||
if not cve:
|
||||
continue
|
||||
epss = maybe_float(row.get("epss"))
|
||||
pct = maybe_float(row.get("percentile"))
|
||||
normalised.append(
|
||||
{
|
||||
"cve": cve,
|
||||
"epss": epss,
|
||||
"percentile": pct,
|
||||
"date": row.get("date"),
|
||||
}
|
||||
)
|
||||
return normalised
|
||||
|
||||
|
||||
def fetch_epss(limit: int = DEFAULT_LIMIT, batch_size: int = DEFAULT_BATCH) -> Dict:
|
||||
rows: List[Dict] = []
|
||||
offset = 0
|
||||
while offset < limit:
|
||||
size = min(batch_size, limit - offset)
|
||||
payload = fetch_batch(offset, size)
|
||||
data_rows = payload.get("data") or []
|
||||
rows.extend(normalise_rows(data_rows))
|
||||
if len(data_rows) < size:
|
||||
break
|
||||
offset += size
|
||||
|
||||
rows.sort(key=lambda row: (-row.get("epss", 0.0), row["cve"]))
|
||||
return {
|
||||
"source": API_URL,
|
||||
"fetched": today_str(),
|
||||
"count": len(rows),
|
||||
"limit": limit,
|
||||
"items": rows,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Fetch EPSS top list")
|
||||
parser.add_argument("--limit", type=int, default=DEFAULT_LIMIT, help="Number of EPSS rows to fetch")
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
type=int,
|
||||
default=DEFAULT_BATCH,
|
||||
help="Batch size for paginated EPSS API calls",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
default=DATA_DIR / "epss.json",
|
||||
help="Where to store the downloaded EPSS JSON",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = fetch_epss(args.limit, args.batch_size)
|
||||
save_json(args.output, payload)
|
||||
print(f"Saved {payload['count']} EPSS rows to {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
60
scripts/fetch_kev.py
Normal file
60
scripts/fetch_kev.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from utils import DATA_DIR, fetch_json, save_json, today_str
|
||||
|
||||
DEFAULT_SOURCE = "https://raw.githubusercontent.com/cisagov/kev-data/main/known_exploited_vulnerabilities.json"
|
||||
|
||||
|
||||
def fetch_kev(source: str = DEFAULT_SOURCE) -> dict:
|
||||
data = fetch_json(source)
|
||||
items = data.get("vulnerabilities") or data.get("data") or data
|
||||
|
||||
normalised = []
|
||||
for entry in items:
|
||||
cve_id = (entry.get("cveID") or "").upper()
|
||||
if not cve_id:
|
||||
continue
|
||||
normalised.append(
|
||||
{
|
||||
"cve": cve_id,
|
||||
"vendor": entry.get("vendorProject", "").strip(),
|
||||
"product": entry.get("product", "").strip(),
|
||||
"date_added": entry.get("dateAdded"),
|
||||
"due_date": entry.get("dueDate"),
|
||||
"short_description": entry.get("shortDescription", "").strip(),
|
||||
"required_action": entry.get("requiredAction", "").strip(),
|
||||
"notes": entry.get("notes", "").strip(),
|
||||
}
|
||||
)
|
||||
|
||||
normalised.sort(key=lambda row: row["cve"])
|
||||
return {
|
||||
"source": source,
|
||||
"fetched": today_str(),
|
||||
"count": len(normalised),
|
||||
"items": normalised,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Fetch CISA KEV catalogue")
|
||||
parser.add_argument("--source", default=DEFAULT_SOURCE, help="KEV JSON source URL")
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=Path,
|
||||
default=DATA_DIR / "kev.json",
|
||||
help="Where to store the downloaded KEV JSON",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
payload = fetch_kev(args.source)
|
||||
save_json(args.output, payload)
|
||||
print(f"Saved {payload['count']} KEV entries to {args.output}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
201
scripts/utils.py
Normal file
201
scripts/utils.py
Normal file
@@ -0,0 +1,201 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DATA_DIR = ROOT / "data"
|
||||
DOCS_DIR = ROOT / "docs"
|
||||
API_DIR = DOCS_DIR / "api" / "v1"
|
||||
SNAPSHOT_DIR = API_DIR / "snapshots"
|
||||
TEMPLATES_DIR = ROOT / "templates"
|
||||
ASSETS_DIR = DOCS_DIR / "assets"
|
||||
|
||||
|
||||
def ensure_dirs(*paths: Path) -> None:
|
||||
for path in paths:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def load_json(path: Path, default=None):
|
||||
if not path.exists():
|
||||
return default
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def save_json(path: Path, data, *, sort_keys: bool = True) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with path.open("w", encoding="utf-8") as handle:
|
||||
json.dump(data, handle, ensure_ascii=False, indent=2, sort_keys=sort_keys)
|
||||
|
||||
|
||||
def fetch_json(url: str, *, timeout: int = 30, headers: Optional[Dict[str, str]] = None):
|
||||
response = requests.get(url, timeout=timeout, headers=headers or {})
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
|
||||
def today_str() -> str:
|
||||
return datetime.now(timezone.utc).date().isoformat()
|
||||
|
||||
|
||||
def slugify(text: str) -> str:
|
||||
cleaned = re.sub(r"[^A-Za-z0-9]+", "-", text.strip().lower())
|
||||
cleaned = cleaned.strip("-")
|
||||
return cleaned or "unknown"
|
||||
|
||||
|
||||
def stable_unique(items: Iterable[str]) -> List[str]:
|
||||
seen = set()
|
||||
output = []
|
||||
for item in items:
|
||||
if item and item not in seen:
|
||||
seen.add(item)
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
|
||||
def maybe_float(value: str | float | int | None) -> Optional[float]:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
# --- PoC data helpers ----------------------------------------------------
|
||||
|
||||
|
||||
CVE_SECTION_RE = re.compile(r"^CVE-\d{4}-\d{4,}$", re.IGNORECASE)
|
||||
|
||||
|
||||
def load_poc_index() -> Dict[str, Dict[str, object]]:
|
||||
"""Load CVE → {desc, poc} mapping from docs/CVE_list.json or markdown files."""
|
||||
cve_json = DOCS_DIR / "CVE_list.json"
|
||||
if cve_json.exists():
|
||||
data = load_json(cve_json, default=[]) or []
|
||||
mapping = {}
|
||||
for entry in data:
|
||||
cve = str(entry.get("cve", "")).upper()
|
||||
if not is_valid_cve(cve):
|
||||
continue
|
||||
mapping[cve] = {
|
||||
"desc": entry.get("desc", ""),
|
||||
"poc": stable_unique(entry.get("poc", []) or []),
|
||||
}
|
||||
return mapping
|
||||
|
||||
return build_poc_index_from_markdown()
|
||||
|
||||
|
||||
def build_poc_index_from_markdown() -> Dict[str, Dict[str, object]]:
|
||||
mapping: Dict[str, Dict[str, object]] = {}
|
||||
for md_path in sorted(ROOT.glob("[12][0-9][0-9][0-9]/CVE-*.md")):
|
||||
cve = md_path.stem.upper()
|
||||
if not is_valid_cve(cve):
|
||||
continue
|
||||
desc, poc_links = parse_cve_markdown(md_path)
|
||||
mapping[cve] = {"desc": desc, "poc": poc_links}
|
||||
return mapping
|
||||
|
||||
|
||||
def parse_cve_markdown(path: Path) -> Tuple[str, List[str]]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
sections = parse_sections(text)
|
||||
description = normalise_block(sections.get("### Description", ""))
|
||||
references = collect_links(sections.get("#### Reference", ""))
|
||||
github_links = collect_links(sections.get("#### Github", ""))
|
||||
poc_links = stable_unique([*references, *github_links])
|
||||
return description, poc_links
|
||||
|
||||
|
||||
def normalise_block(text: str) -> str:
|
||||
text = text.replace("\r\n", "\n")
|
||||
text = re.sub(r"\n{2,}", "\n", text.strip())
|
||||
lines = [line.lstrip("- ").rstrip() for line in text.split("\n")]
|
||||
return "\n".join(line for line in lines if line)
|
||||
|
||||
|
||||
def parse_sections(content: str) -> Dict[str, str]:
|
||||
sections: Dict[str, str] = {}
|
||||
current: Optional[str] = None
|
||||
buffer: List[str] = []
|
||||
|
||||
for raw_line in content.splitlines():
|
||||
line = raw_line.strip()
|
||||
if line.startswith("### ") or line.startswith("#### "):
|
||||
if current is not None:
|
||||
sections[current] = "\n".join(buffer).strip()
|
||||
current = line
|
||||
buffer = []
|
||||
else:
|
||||
buffer.append(raw_line)
|
||||
|
||||
if current is not None:
|
||||
sections[current] = "\n".join(buffer).strip()
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
def collect_links(block: str) -> List[str]:
|
||||
links: List[str] = []
|
||||
for raw in block.splitlines():
|
||||
entry = raw.strip()
|
||||
if not entry or "No PoCs" in entry:
|
||||
continue
|
||||
if entry.startswith("- "):
|
||||
entry = entry[2:].strip()
|
||||
if entry and entry not in links:
|
||||
links.append(entry)
|
||||
return links
|
||||
|
||||
|
||||
def is_valid_cve(cve_id: str) -> bool:
|
||||
parts = cve_id.split("-")
|
||||
if len(parts) != 3:
|
||||
return False
|
||||
year = parts[1]
|
||||
return year.isdigit() and parts[2].isdigit()
|
||||
|
||||
|
||||
# --- Trending PoCs -------------------------------------------------------
|
||||
|
||||
TREND_ROW_RE = re.compile(r"^\|\s*(?P<stars>\d+)\s*⭐\s*\|\s*(?P<updated>[^|]+)\|\s*\[(?P<name>[^\]]+)\]\((?P<url>[^)]+)\)\s*\|\s*(?P<desc>.*)\|$")
|
||||
|
||||
|
||||
def parse_trending_from_readme(readme_path: Path) -> List[Dict[str, str]]:
|
||||
if not readme_path.exists():
|
||||
return []
|
||||
results: List[Dict[str, str]] = []
|
||||
current_year: Optional[str] = None
|
||||
for line in readme_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith("## ") and line[3:].strip().isdigit():
|
||||
current_year = line[3:].strip()
|
||||
continue
|
||||
match = TREND_ROW_RE.match(line)
|
||||
if match and current_year:
|
||||
entry = match.groupdict()
|
||||
entry["year"] = current_year
|
||||
results.append(entry)
|
||||
# Keep deterministic order (README already ordered newest first)
|
||||
return results
|
||||
|
||||
|
||||
# --- Misc helpers --------------------------------------------------------
|
||||
|
||||
|
||||
def read_text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
|
||||
|
||||
def write_text(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
32
templates/base.html
Normal file
32
templates/base.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{ title or 'CVE Intelligence' }}</title>
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<script defer src="/assets/site.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
<div class="wrap">
|
||||
<div class="brand"><a href="/">CVE Radar</a></div>
|
||||
<nav>
|
||||
<a href="/kev/">KEV</a>
|
||||
<a href="/epss/">EPSS</a>
|
||||
<a href="/diffs/">Diffs</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main class="wrap">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<footer class="site-footer">
|
||||
<div class="wrap">
|
||||
<span>Updated {{ generated or '' }}</span>
|
||||
<span>Data: CISA KEV, FIRST EPSS, community PoCs</span>
|
||||
<span><a href="https://github.com/0xMarcio/cve">GitHub repo</a></span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
29
templates/cve.html
Normal file
29
templates/cve.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>{{ cve.cve }}</h1>
|
||||
<p class="lead">{{ cve.description or 'No description available.' }}</p>
|
||||
<div class="pill-row">
|
||||
{% if cve.kev %}<span class="pill pill-warn">In KEV</span>{% else %}<span class="pill">Not in KEV</span>{% endif %}
|
||||
{% if cve.epss is not none %}<span class="pill">EPSS {{ '%.3f'|format(cve.epss) }} ({{ '%2.0f'|format((cve.percentile or 0)*100) }}th)</span>{% endif %}
|
||||
{% if cve.kev and cve.kev.due_date %}<span class="pill">Due {{ cve.kev.due_date }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
{% if cve.poc_links %}
|
||||
<h2>Proof of Concepts</h2>
|
||||
<ul>
|
||||
{% for link in cve.poc_links %}
|
||||
<li><a href="{{ link }}" target="_blank" rel="noopener">{{ link }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if cve.kev %}
|
||||
<h2>KEV Details</h2>
|
||||
<ul>
|
||||
<li><strong>Date added:</strong> {{ cve.kev.date_added }}</li>
|
||||
{% if cve.kev.due_date %}<li><strong>Due date:</strong> {{ cve.kev.due_date }}</li>{% endif %}
|
||||
<li><strong>Required action:</strong> {{ cve.kev.required_action }}</li>
|
||||
{% if cve.kev.notes %}<li><strong>Notes:</strong> {{ cve.kev.notes }}</li>{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
54
templates/diffs.html
Normal file
54
templates/diffs.html
Normal file
@@ -0,0 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>Daily Diff</h1>
|
||||
<p>Comparing the latest snapshot to the previous one.</p>
|
||||
|
||||
<h2>New KEV Entries</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>CVE</th><th>Vendor</th><th>Product</th><th>Date Added</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in diff.new_kev_entries or [] %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a></td>
|
||||
<td>{{ row.vendor }}</td>
|
||||
<td>{{ row.product }}</td>
|
||||
<td>{{ row.date_added }}</td>
|
||||
</tr>
|
||||
{% else %}<tr><td colspan="4">No new KEV entries.</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>New High EPSS</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in diff.new_high_epss or [] %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a></td>
|
||||
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
|
||||
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
|
||||
</tr>
|
||||
{% else %}<tr><td colspan="3">No new high EPSS items.</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Biggest EPSS Movers</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>CVE</th><th>Δ EPSS</th><th>Current</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in diff.epss_movers or [] %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a></td>
|
||||
<td>{{ '%.3f'|format(row.delta) }}</td>
|
||||
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
|
||||
</tr>
|
||||
{% else %}<tr><td colspan="3">No movers yet.</td></tr>{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
20
templates/epss.html
Normal file
20
templates/epss.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>Top EPSS (not in KEV)</h1>
|
||||
<input type="search" placeholder="Filter CVE" data-filter-table="epss-table" class="filter" />
|
||||
<div class="table-responsive">
|
||||
<table class="list" id="epss-table">
|
||||
<thead><tr><th>CVE</th><th>EPSS</th><th>Percentile</th><th>PoCs</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in epss %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a></td>
|
||||
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
|
||||
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
|
||||
<td>{{ row.poc_count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
87
templates/index.html
Normal file
87
templates/index.html
Normal file
@@ -0,0 +1,87 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<section>
|
||||
<h1>Top KEV by EPSS percentile</h1>
|
||||
<div class="card-grid">
|
||||
{% for item in data.kev_top[:15] %}
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/{{ item.cve }}.html">{{ item.cve }}</a></div>
|
||||
<div class="card-meta">EPSS {{ '%.3f'|format(item.epss or 0) }} • {{ '%2.0f'|format((item.percentile or 0)*100) }}th pct</div>
|
||||
<p>{{ item.summary or 'No description.' }}</p>
|
||||
<div class="badge">{{ item.vendor or 'Unknown vendor' }}</div>
|
||||
<div class="badge">{{ item.product or 'Unknown product' }}</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>High EPSS not in KEV</h1>
|
||||
<div class="card-grid">
|
||||
{% for item in data.high_epss[:15] %}
|
||||
<article class="card">
|
||||
<div class="card-title"><a href="/cve/{{ item.cve }}.html">{{ item.cve }}</a></div>
|
||||
<div class="card-meta">EPSS {{ '%.3f'|format(item.epss or 0) }} • {{ '%2.0f'|format((item.percentile or 0)*100) }}th pct</div>
|
||||
<p>{{ item.summary or 'No description.' }}</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>Trending PoCs</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>Stars</th><th>Updated</th><th>Name</th><th>Description</th></tr></thead>
|
||||
<tbody>
|
||||
{% for row in trending[:20] %}
|
||||
<tr>
|
||||
<td>{{ row.stars }}</td>
|
||||
<td>{{ row.updated }}</td>
|
||||
<td><a href="{{ row.url }}" target="_blank">{{ row.name }}</a></td>
|
||||
<td>{{ row.desc }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h1>Changes since yesterday</h1>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>Type</th><th>Count</th><th>Examples</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>New KEV entries</td>
|
||||
<td>{{ (diff.new_kev_entries or [])|length }}</td>
|
||||
<td>
|
||||
{% for row in (diff.new_kev_entries or [])[:5] %}
|
||||
<a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% else %}None{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New high EPSS</td>
|
||||
<td>{{ (diff.new_high_epss or [])|length }}</td>
|
||||
<td>
|
||||
{% for row in (diff.new_high_epss or [])[:5] %}
|
||||
<a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a>{% if not loop.last %}, {% endif %}
|
||||
{% else %}None{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Top EPSS movers</td>
|
||||
<td>{{ (diff.epss_movers or [])|length }}</td>
|
||||
<td>
|
||||
{% for row in (diff.epss_movers or [])[:5] %}
|
||||
<a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a> ({{ '%.3f'|format(row.delta) }}){% if not loop.last %}, {% endif %}
|
||||
{% else %}None{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
25
templates/kev.html
Normal file
25
templates/kev.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>Known Exploited Vulnerabilities</h1>
|
||||
<input type="search" placeholder="Filter CVE, vendor, product" data-filter-table="kev-table" class="filter" />
|
||||
<div class="table-responsive">
|
||||
<table class="list" id="kev-table">
|
||||
<thead>
|
||||
<tr><th>CVE</th><th>Vendor</th><th>Product</th><th>EPSS</th><th>Percentile</th><th>Date Added</th><th>Due</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in kev %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ row.cve }}.html">{{ row.cve }}</a></td>
|
||||
<td>{{ row.vendor }}</td>
|
||||
<td>{{ row.product }}</td>
|
||||
<td>{{ '%.3f'|format(row.epss or 0) }}</td>
|
||||
<td>{{ '%2.0f'|format((row.percentile or 0)*100) }}th</td>
|
||||
<td>{{ row.date_added }}</td>
|
||||
<td>{{ row.due_date or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
20
templates/vendor.html
Normal file
20
templates/vendor.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<h1>{{ vendor.vendor }}</h1>
|
||||
<p>{{ cves|length }} CVEs</p>
|
||||
<div class="table-responsive">
|
||||
<table class="list">
|
||||
<thead><tr><th>CVE</th><th>EPSS</th><th>KEV</th><th>PoCs</th></tr></thead>
|
||||
<tbody>
|
||||
{% for detail in cves %}
|
||||
<tr>
|
||||
<td><a href="/cve/{{ detail.cve }}.html">{{ detail.cve }}</a></td>
|
||||
<td>{% if detail.epss is not none %}{{ '%.3f'|format(detail.epss) }}{% else %}—{% endif %}</td>
|
||||
<td>{{ 'Yes' if detail.kev else 'No' }}</td>
|
||||
<td>{{ detail.poc_count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user