195 lines
6.1 KiB
JavaScript
195 lines
6.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import axios from 'axios';
|
|
import mysql from 'mysql2/promise';
|
|
import dotenv from 'dotenv';
|
|
import readline from 'readline';
|
|
|
|
dotenv.config({ path: '.env.local' });
|
|
|
|
const DB = await mysql.createConnection({
|
|
host: process.env.DB_HOST,
|
|
port: process.env.DB_PORT || 3306,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD,
|
|
database: process.env.DB_NAME,
|
|
});
|
|
|
|
function formatDate(isoString) {
|
|
if (!isoString) return null;
|
|
const date = new Date(isoString);
|
|
return date.toISOString().slice(0, 19).replace('T', ' ');
|
|
}
|
|
|
|
function getPreviousMonthID() {
|
|
const now = new Date();
|
|
now.setMonth(now.getMonth() - 1);
|
|
const year = now.getFullYear();
|
|
const month = now.toLocaleString('en-US', { month: 'short' }); // e.g. "Mar"
|
|
return `${year}-${month}`;
|
|
}
|
|
|
|
function getLocalizedText(entry, fallback = '') {
|
|
if (!entry || typeof entry !== 'object') return fallback;
|
|
|
|
// Try to get English (US) first
|
|
if (entry['en-US']?.Value) return entry['en-US'].Value;
|
|
|
|
// Fallback: get first entry with a Value field
|
|
for (const key in entry) {
|
|
if (entry[key]?.Value) {
|
|
return entry[key].Value;
|
|
}
|
|
}
|
|
|
|
return fallback;
|
|
}
|
|
|
|
|
|
function extractDescription(notes = [], fallback = '') {
|
|
const note = notes.find(n =>
|
|
n.Title === 'Description' &&
|
|
n.Type === 2 &&
|
|
typeof n.Value === 'string' &&
|
|
n.Value.trim().length > 0
|
|
);
|
|
return note?.Value || fallback;
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractAffectedProducts(vuln) {
|
|
const fixed = vuln.ProductStatuses?.Fixed ?? [];
|
|
const known = vuln.ProductStatuses?.KnownAffected ?? [];
|
|
const underInvestigation = vuln.ProductStatuses?.UnderInvestigation ?? [];
|
|
return [...new Set([...fixed, ...known, ...underInvestigation])].join(', ');
|
|
}
|
|
|
|
async function fetchCVRFDoc(cvrfId) {
|
|
const url = `https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/${cvrfId}`;
|
|
try {
|
|
const res = await axios.get(url, {
|
|
headers: { 'Accept': 'application/json' }
|
|
});
|
|
return res.data;
|
|
} catch (err) {
|
|
console.error(`❌ Failed to fetch CVRF for ${cvrfId}:`, err.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function storeVulnerability(v, published, modified) {
|
|
const cve = v.CVE;
|
|
console.log(`⏳ Saving ${cve}...`);
|
|
const title = getLocalizedText(v.Title, '');
|
|
const description = extractDescription(v.Notes, title);
|
|
const affectedProducts = extractAffectedProducts(v);
|
|
const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || '';
|
|
const impact = v.Threats?.find(t => t.Type === 'Impact')?.Description?.Value || '';
|
|
const exploitability = v.Threats?.find(t => t.Type === 'Exploitability')?.Description?.Value || '';
|
|
const cvssSet = v.CVSSScoreSets?.[0];
|
|
const cvssScore = cvssSet?.BaseScore || null;
|
|
const cvssVector = cvssSet?.Vector || '';
|
|
const cweList = Array.isArray(v.CWE) ? v.CWE : [];
|
|
|
|
if (!title) {
|
|
console.warn(`⚠️ No title found for ${cve}`);
|
|
console.debug('Title raw:', JSON.stringify(v.Title, null, 2));
|
|
}
|
|
|
|
if (!description) {
|
|
console.warn(`⚠️ No description for ${cve}`);
|
|
console.debug('Notes raw:', JSON.stringify(v.Notes, null, 2));
|
|
}
|
|
|
|
|
|
try {
|
|
// Insert or update the main CVE entry (no cwe column now)
|
|
await DB.execute(
|
|
`INSERT INTO microsoft_cves (cve_id, title, severity, published_date, last_modified_date,
|
|
affected_products, description, exploitability, cvss_score, cvss_vector)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE last_modified_date = VALUES(last_modified_date),
|
|
affected_products = VALUES(affected_products),
|
|
severity = VALUES(severity),
|
|
description = VALUES(description),
|
|
exploitability = VALUES(exploitability),
|
|
cvss_score = VALUES(cvss_score),
|
|
cvss_vector = VALUES(cvss_vector)`,
|
|
[cve, title, severity, published, modified, affectedProducts, description, exploitability, cvssScore, cvssVector]
|
|
);
|
|
|
|
// Insert CWE entries and links
|
|
for (const { ID: cweId, Value: cweName } of cweList) {
|
|
if (!cweId) continue;
|
|
|
|
// Insert into cwe_entries if not already present
|
|
await DB.execute(
|
|
`INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`,
|
|
[cweId, cweName || null]
|
|
);
|
|
|
|
// Link CVE to CWE
|
|
await DB.execute(
|
|
`INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`,
|
|
[cve, cweId]
|
|
);
|
|
}
|
|
|
|
console.log(`✅ Saved ${cve} — ${severity}, ${cvssScore ?? '?'}`);
|
|
} catch (err) {
|
|
console.error(`❌ DB error for ${cve}:`, err.message);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function waitForKeypress() {
|
|
return new Promise((resolve) => {
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
|
|
rl.question('🔄 Press any key to continue to the next 10 CVEs...', () => {
|
|
rl.close();
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
async function importPreviousMonth() {
|
|
const cvrfId = getPreviousMonthID();
|
|
console.log(`📡 Fetching CVEs for ${cvrfId}...`);
|
|
|
|
const data = await fetchCVRFDoc(cvrfId);
|
|
if (!data) {
|
|
console.log("❌ No data returned from MSRC.");
|
|
return;
|
|
}
|
|
|
|
const published = formatDate(data.DocumentTracking?.InitialReleaseDate);
|
|
const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate);
|
|
|
|
const vulns = data.Vulnerability || [];
|
|
console.log(`📋 Found ${vulns.length} vulnerabilities to import.`);
|
|
|
|
const batchSize = 10;
|
|
|
|
for (let i = 0; i < vulns.length; i += batchSize) {
|
|
const batch = vulns.slice(i, i + batchSize);
|
|
console.log(`🔄 Processing batch ${i / batchSize + 1} (${batch.length} items)...`);
|
|
|
|
for (const v of batch) {
|
|
await storeVulnerability(v, published, modified);
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Finished importing ${vulns.length} CVEs for ${cvrfId}`);
|
|
await DB.end();
|
|
}
|
|
console.log("🚀 Starting MSRC sync now...");
|
|
await importPreviousMonth();
|
|
|