mirror of
https://github.com/0xMarcio/cve.git
synced 2026-10-27 16:51:03 +00:00
Index nuclei templates as PoCs and take their ratings with them
A nuclei template is a runnable check for one CVE, which makes it a proof of concept whether or not anyone published a repository for it. Measured against the index: 3,952 CVEs gain a template link and 858 of those had no PoC at all, 584 of the additions falling in 2024 or later. That last figure is the point. ExploitDB carries more CVEs overall and Metasploit is better weaponised, but both skew historical; nuclei is where coverage of a recent CVE actually shows up first. Templates also carry the ratings the entries never had. Severity, CVSS with its vector, EPSS with its percentile and CWE now sit in nuclei.json for the site to read: 4,193 of 4,418 templates state a CVSS score and 4,274 an EPSS score. A result can finally say how bad a flaw is and how likely it is to be used, rather than only that somebody wrote code for it. Templates are kept in their own section and their own field. They are not somebody's repository and should not be ranked, starred or credited as one, but they do lead the PoC list, being the one link that runs as it stands. The index files are also revalidated rather than reused from cache. They are rebuilt hourly and a returning visitor was holding whatever it downloaded first, which cost a stale index here until the copy was thrown away by hand.
This commit is contained in:
1 parent
fca6754b6f
commit
35cb1e6962
3960 files changed
+51289
-25
No files matched your search
+32
-5
@@ -19,6 +19,7 @@ const state = {
|
||||
let dataset = [];
|
||||
let repoMeta = {};
|
||||
let kev = {};
|
||||
let ratings = {};
|
||||
let trending = [];
|
||||
let indexMeta = null;
|
||||
|
||||
@@ -288,6 +289,13 @@ const el = {
|
||||
};
|
||||
|
||||
function pocRow(url) {
|
||||
if (url.includes('/nuclei-templates/')) {
|
||||
const file = url.split('/').pop();
|
||||
return '<div class="poc-row"><span class="poc-name">' +
|
||||
'<span class="poc-tag" title="Runnable nuclei detection template">NUCLEI</span>' +
|
||||
`<a href="${escapeHTML(url)}" target="_blank" rel="noopener">${escapeHTML(file)}</a>` +
|
||||
'</span><span class="poc-stars"></span><span class="poc-age"></span></div>';
|
||||
}
|
||||
const parsed = repoFromUrl(url);
|
||||
const href = escapeHTML(url);
|
||||
// A gist or an advisory has no owner and no stars; the star and age columns
|
||||
@@ -333,7 +341,9 @@ function rankedLinks(entry) {
|
||||
});
|
||||
scored.sort((a, b) =>
|
||||
(b.dedicated - a.dedicated) || (b.stars - a.stars) || (a.index - b.index));
|
||||
entry._ranked = scored.map(item => item.url);
|
||||
// A nuclei template leads: of everything linked here it is the one entry that
|
||||
// runs as it stands, against a target, without reading somebody's code first.
|
||||
entry._ranked = [...(entry.nuclei || []), ...scored.map(item => item.url)];
|
||||
return entry._ranked;
|
||||
}
|
||||
|
||||
@@ -350,6 +360,18 @@ function resultRow(entry) {
|
||||
`${all ? '↑ show fewer' : '+ ' + formatCount(links.length - POC_PREVIEW) + ' more'}</button>`
|
||||
: '';
|
||||
|
||||
// Nuclei rates what it covers, so a row can say how bad the flaw is and how
|
||||
// likely it is to be exploited, not only that somebody wrote code for it.
|
||||
const rated = ratings[id] || {};
|
||||
const severityChip = rated.severity
|
||||
? `<span class="chip chip-sev is-${escapeHTML(rated.severity)}"${rated.cvss_vector ? ` title="${escapeHTML(rated.cvss_vector)}"` : ''}>` +
|
||||
`${escapeHTML(rated.severity.toUpperCase())}${rated.cvss ? ' ' + rated.cvss : ''}</span>`
|
||||
: '';
|
||||
const epssChip = rated.epss != null
|
||||
? `<span class="chip chip-epss" title="EPSS: estimated chance of exploitation in the next 30 days, ${Math.round((rated.epss_pct || 0) * 100)}th percentile">` +
|
||||
`EPSS ${(rated.epss * 100).toFixed(rated.epss >= 0.1 ? 0 : 1)}%</span>`
|
||||
: '';
|
||||
|
||||
const flagged = kev[id];
|
||||
const kevChip = flagged
|
||||
? `<span class="chip chip-kev" title="Listed in CISA's Known Exploited Vulnerabilities catalogue on ${escapeHTML(flagged[0])}${flagged[1] ? ', used in ransomware campaigns' : ''}">` +
|
||||
@@ -368,7 +390,7 @@ function resultRow(entry) {
|
||||
<a class="result-id" href="https://nvd.nist.gov/vuln/detail/${encodeURIComponent(id)}" target="_blank" rel="noopener">${escapeHTML(id)}</a>
|
||||
<div class="result-pocs">${formatCount(links.length)} linked PoC${links.length === 1 ? '' : 's'}</div>
|
||||
${dateHtml}
|
||||
<div class="chips">${kevChip}
|
||||
<div class="chips">${kevChip}${severityChip}${epssChip}
|
||||
<a class="chip" href="https://www.cve.org/CVERecord?id=${encodeURIComponent(id)}" target="_blank" rel="noopener">MITRE ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -606,7 +628,7 @@ async function loadJSON(url, options) {
|
||||
|
||||
// The repository metadata is optional: without it the PoC rows simply lose
|
||||
// their star and age columns, which is the documented fallback.
|
||||
loadJSON('/kev.json').then(data => {
|
||||
loadJSON('/kev.json', { cache: 'no-cache' }).then(data => {
|
||||
kev = data || {};
|
||||
paintHeroStats();
|
||||
// A search already on screen owns the view; only repaint the idle table.
|
||||
@@ -614,14 +636,19 @@ async function loadJSON(url, options) {
|
||||
if (state.query && state.ready) renderResults(null);
|
||||
}).catch(err => console.warn(err.message));
|
||||
|
||||
loadJSON('/repo_meta.json').then(data => {
|
||||
loadJSON('/nuclei.json', { cache: 'no-cache' }).then(data => {
|
||||
ratings = data || {};
|
||||
if (state.query && state.ready) renderResults(null);
|
||||
}).catch(err => console.warn(err.message));
|
||||
|
||||
loadJSON('/repo_meta.json', { cache: 'no-cache' }).then(data => {
|
||||
repoMeta = data || {};
|
||||
for (const entry of dataset) entry._ranked = null;
|
||||
if (state.query && state.ready) renderResults(null);
|
||||
}).catch(err => console.warn(err.message));
|
||||
|
||||
try {
|
||||
dataset = prepareDataset(await loadJSON('/CVE_list.json'));
|
||||
dataset = prepareDataset(await loadJSON('/CVE_list.json', { cache: 'no-cache' }));
|
||||
state.ready = true;
|
||||
render();
|
||||
} catch (err) {
|
||||
|
||||
@@ -206,6 +206,12 @@ a.chip:hover { color: var(--fg); border-color: var(--fg-muted); }
|
||||
background: rgba(248, 81, 73, .1);
|
||||
border-color: rgba(248, 81, 73, .4);
|
||||
}
|
||||
.chip-sev { font-weight: 500; letter-spacing: .08em; }
|
||||
.chip-sev.is-critical { color: #ff7b72; border-color: rgba(255, 123, 114, .45); background: rgba(255, 123, 114, .1); }
|
||||
.chip-sev.is-high { color: #f0883e; border-color: rgba(240, 136, 62, .45); background: rgba(240, 136, 62, .1); }
|
||||
.chip-sev.is-medium { color: var(--attention); border-color: rgba(227, 179, 65, .4); background: rgba(227, 179, 65, .1); }
|
||||
.chip-sev.is-low { color: var(--fg-muted); }
|
||||
.chip-epss { color: var(--fg-subtle); font-variant-numeric: tabular-nums; }
|
||||
|
||||
.result-body { padding: 16px; min-width: 0; }
|
||||
.result-desc {
|
||||
@@ -250,6 +256,16 @@ a.chip:hover { color: var(--fg); border-color: var(--fg-muted); }
|
||||
.poc-name a { color: var(--fg); font-weight: 500; }
|
||||
.poc-name a:hover { color: var(--accent); }
|
||||
.poc-name a.plain { color: var(--fg-muted); font-weight: 400; }
|
||||
.poc-tag {
|
||||
margin-right: 7px;
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
font: 600 9px/1 var(--mono);
|
||||
letter-spacing: .08em;
|
||||
color: #a5d6ff;
|
||||
background: rgba(56, 139, 253, .12);
|
||||
border: 1px solid rgba(56, 139, 253, .4);
|
||||
}
|
||||
.poc-name a.plain:hover { color: var(--accent); }
|
||||
.poc-stars { text-align: right; font: 400 12px/1 var(--mono); color: var(--fg-muted); }
|
||||
.poc-stars.is-popular { color: var(--attention); }
|
||||
|
||||
Reference in new issue
Block a user