Vulnerability Management
End-to-end vulnerability lifecycle from discovery through remediation and verification.
When to Use This Skill
Keywords: vulnerability management, CVE, CVSS, remediation, patching, risk prioritization, EPSS, KEV, vulnerability disclosure, bug bounty, patch management, vulnerability scanning, asset inventory
Use this skill when:
-
Setting up vulnerability management programs
-
Interpreting CVSS scores and metrics
-
Prioritizing vulnerability remediation
-
Designing patch management processes
-
Implementing vulnerability disclosure programs
-
Managing bug bounty programs
-
Tracking CVE/CWE/NVD data
-
Creating SLA policies for remediation
Quick Decision Tree
-
Understanding vulnerability scores? → See CVSS Scoring
-
Prioritizing what to fix first? → See Risk-Based Prioritization
-
Designing remediation workflow? → See references/remediation-workflow.md
-
Setting up disclosure program? → See Vulnerability Disclosure
-
CVSS calculation details? → See references/cvss-scoring.md
Vulnerability Lifecycle
┌─────────────────────────────────────────────────────────────────┐ │ VULNERABILITY MANAGEMENT LIFECYCLE │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ DISCOVER ──▶ ASSESS ──▶ PRIORITIZE ──▶ REMEDIATE ──▶ VERIFY │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ │ ┌───────┐ ┌───────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │Scanner│ │CVSS │ │Risk │ │Patch/ │ │Rescan/ │ │ │ │Pentest│ │Context│ │Matrix │ │Config │ │Validate│ │ │ │Bug │ │Assets │ │EPSS+KEV│ │Mitigate│ │Close │ │ │ │Bounty │ │Impact │ │SLA │ │Accept │ │ │ │ │ └───────┘ └───────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ CONTINUOUS: Monitor ◀──────────────────────────────────────────┤ │ │ └─────────────────────────────────────────────────────────────────┘
CVSS Scoring Overview
Common Vulnerability Scoring System provides standardized severity ratings.
CVSS v3.1 Score Ranges
Score Severity Typical SLA
9.0-10.0 Critical 24-72 hours
7.0-8.9 High 7-14 days
4.0-6.9 Medium 30-60 days
0.1-3.9 Low 90-180 days
0.0 None Risk acceptance
CVSS v3.1 Metric Groups
public enum AttackVector { Network, Adjacent, Local, Physical } public enum AttackComplexity { Low, High } public enum PrivilegesRequired { None, Low, High } public enum UserInteraction { None, Required } public enum Scope { Unchanged, Changed } public enum ImpactLevel { None, Low, High }
/// <summary> /// CVSS v3.1 Base Score vector /// </summary> public sealed record CvssV31Vector( AttackVector AttackVector, AttackComplexity AttackComplexity, PrivilegesRequired PrivilegesRequired, UserInteraction UserInteraction, Scope Scope, ImpactLevel Confidentiality, ImpactLevel Integrity, ImpactLevel Availability) { private static readonly FrozenDictionary<AttackVector, (string Code, double Value)> AvMetrics = new Dictionary<AttackVector, (string, double)> { [AttackVector.Network] = ("N", 0.85), [AttackVector.Adjacent] = ("A", 0.62), [AttackVector.Local] = ("L", 0.55), [AttackVector.Physical] = ("P", 0.20) }.ToFrozenDictionary();
private static readonly FrozenDictionary<AttackComplexity, (string Code, double Value)> AcMetrics =
new Dictionary<AttackComplexity, (string, double)>
{
[AttackComplexity.Low] = ("L", 0.77),
[AttackComplexity.High] = ("H", 0.44)
}.ToFrozenDictionary();
private static readonly FrozenDictionary<ImpactLevel, (string Code, double Value)> ImpactMetrics =
new Dictionary<ImpactLevel, (string, double)>
{
[ImpactLevel.None] = ("N", 0.00),
[ImpactLevel.Low] = ("L", 0.22),
[ImpactLevel.High] = ("H", 0.56)
}.ToFrozenDictionary();
public string ToVectorString() =>
$"CVSS:3.1/AV:{AvMetrics[AttackVector].Code}/" +
$"AC:{AcMetrics[AttackComplexity].Code}/" +
$"PR:{GetPrCode()}/" +
$"UI:{(UserInteraction == UserInteraction.None ? "N" : "R")}/" +
$"S:{(Scope == Scope.Unchanged ? "U" : "C")}/" +
$"C:{ImpactMetrics[Confidentiality].Code}/" +
$"I:{ImpactMetrics[Integrity].Code}/" +
$"A:{ImpactMetrics[Availability].Code}";
public double CalculateBaseScore()
{
// Impact sub-score
var iscBase = 1 - (
(1 - ImpactMetrics[Confidentiality].Value) *
(1 - ImpactMetrics[Integrity].Value) *
(1 - ImpactMetrics[Availability].Value));
var impact = Scope == Scope.Unchanged
? 6.42 * iscBase
: 7.52 * (iscBase - 0.029) - 3.25 * Math.Pow(iscBase - 0.02, 15);
// Exploitability sub-score
var exploitability = 8.22 *
AvMetrics[AttackVector].Value *
AcMetrics[AttackComplexity].Value *
GetPrValue() *
(UserInteraction == UserInteraction.None ? 0.85 : 0.62);
if (impact <= 0) return 0.0;
var score = Scope == Scope.Unchanged
? Math.Min(impact + exploitability, 10)
: Math.Min(1.08 * (impact + exploitability), 10);
return Math.Round(score * 10) / 10; // Round to 1 decimal
}
private string GetPrCode() => PrivilegesRequired switch
{
PrivilegesRequired.None => "N",
PrivilegesRequired.Low => "L",
PrivilegesRequired.High => "H",
_ => "N"
};
private double GetPrValue() => (PrivilegesRequired, Scope) switch
{
(PrivilegesRequired.None, _) => 0.85,
(PrivilegesRequired.Low, Scope.Unchanged) => 0.62,
(PrivilegesRequired.Low, Scope.Changed) => 0.68,
(PrivilegesRequired.High, Scope.Unchanged) => 0.27,
(PrivilegesRequired.High, Scope.Changed) => 0.50,
_ => 0.85
};
}
// Example: Log4Shell (CVE-2021-44228) var log4shell = new CvssV31Vector( AttackVector: AttackVector.Network, AttackComplexity: AttackComplexity.Low, PrivilegesRequired: PrivilegesRequired.None, UserInteraction: UserInteraction.None, Scope: Scope.Changed, Confidentiality: ImpactLevel.High, Integrity: ImpactLevel.High, Availability: ImpactLevel.High);
Console.WriteLine($"Vector: {log4shell.ToVectorString()}"); Console.WriteLine($"Score: {log4shell.CalculateBaseScore()}"); // 10.0
For detailed CVSS scoring including Temporal and Environmental metrics, see references/cvss-scoring.md.
Risk-Based Prioritization
CVSS alone is insufficient for prioritization. Use multiple signals:
Prioritization Framework
using System.Collections.Frozen;
public enum AssetCriticality { Low, Medium, High, Critical } public enum AssetExposure { Internal, Dmz, External } public enum DataSensitivity { Public, Internal, Confidential, Restricted } public enum ExploitMaturity { Unproven, Poc, Functional, High }
/// <summary>Complete context for vulnerability prioritization</summary> public sealed record VulnerabilityContext( string CveId, double CvssBase, double? CvssEnvironmental = null, double? CvssTemporal = null, double EpssProbability = 0.0, // 0-1 probability of exploitation double EpssPercentile = 0.0, // Relative ranking bool InKev = false, DateTimeOffset? KevDueDate = null, AssetCriticality AssetCriticality = AssetCriticality.Medium, AssetExposure AssetExposure = AssetExposure.Internal, DataSensitivity DataSensitivity = DataSensitivity.Internal, bool PublicExploit = false, ExploitMaturity ExploitMaturity = ExploitMaturity.Unproven);
public sealed record PriorityResult(double Score, string Category, int SlaHours);
/// <summary>Multi-factor vulnerability prioritization</summary> public sealed class RiskPrioritizer { // Weights for different factors (tune based on org priorities) private static readonly FrozenDictionary<string, double> Weights = new Dictionary<string, double> { ["cvss"] = 0.25, ["epss"] = 0.20, ["kev"] = 0.20, ["asset"] = 0.20, ["exploit"] = 0.15 }.ToFrozenDictionary();
private static readonly FrozenDictionary<AssetCriticality, double> AssetScores =
new Dictionary<AssetCriticality, double>
{
[AssetCriticality.Low] = 25,
[AssetCriticality.Medium] = 50,
[AssetCriticality.High] = 75,
[AssetCriticality.Critical] = 100
}.ToFrozenDictionary();
private static readonly FrozenDictionary<AssetExposure, double> ExposureMultipliers =
new Dictionary<AssetExposure, double>
{
[AssetExposure.Internal] = 0.7,
[AssetExposure.Dmz] = 0.85,
[AssetExposure.External] = 1.0
}.ToFrozenDictionary();
private static readonly FrozenDictionary<DataSensitivity, double> DataMultipliers =
new Dictionary<DataSensitivity, double>
{
[DataSensitivity.Public] = 0.5,
[DataSensitivity.Internal] = 0.7,
[DataSensitivity.Confidential] = 0.9,
[DataSensitivity.Restricted] = 1.0
}.ToFrozenDictionary();
private static readonly FrozenDictionary<ExploitMaturity, double> ExploitScores =
new Dictionary<ExploitMaturity, double>
{
[ExploitMaturity.Unproven] = 20,
[ExploitMaturity.Poc] = 50,
[ExploitMaturity.Functional] = 80,
[ExploitMaturity.High] = 100
}.ToFrozenDictionary();
/// <summary>Calculate composite priority score (0-100)</summary>
public double CalculatePriorityScore(VulnerabilityContext vuln)
{
// CVSS component (0-10 normalized to 0-100)
var cvssScore = (vuln.CvssEnvironmental ?? vuln.CvssTemporal ?? vuln.CvssBase) * 10;
// EPSS component (probability * 100)
var epssScore = vuln.EpssProbability * 100;
// KEV component (binary with boost)
var kevScore = vuln.InKev ? 100.0 : 0.0;
// Asset criticality component
var assetScore = AssetScores[vuln.AssetCriticality]
* ExposureMultipliers[vuln.AssetExposure]
* DataMultipliers[vuln.DataSensitivity];
// Exploit maturity component
var exploitScore = ExploitScores[vuln.ExploitMaturity];
if (vuln.PublicExploit)
exploitScore = Math.Max(exploitScore, 60);
// Weighted composite
var priority =
Weights["cvss"] * cvssScore +
Weights["epss"] * epssScore +
Weights["kev"] * kevScore +
Weights["asset"] * assetScore +
Weights["exploit"] * exploitScore;
return Math.Round(priority, 1);
}
/// <summary>Categorize priority score</summary>
public static string GetPriorityCategory(double score) => score switch
{
>= 80 => "P1 - Critical",
>= 60 => "P2 - High",
>= 40 => "P3 - Medium",
_ => "P4 - Low"
};
/// <summary>Get remediation SLA in hours</summary>
public static int GetSlaHours(double score) => score switch
{
>= 80 => 24,
>= 60 => 168, // 7 days
>= 40 => 720, // 30 days
_ => 2160 // 90 days
};
/// <summary>Calculate complete priority result</summary>
public PriorityResult Evaluate(VulnerabilityContext vuln)
{
var score = CalculatePriorityScore(vuln);
return new PriorityResult(score, GetPriorityCategory(score), GetSlaHours(score));
}
}
// Example usage var vuln = new VulnerabilityContext( CveId: "CVE-2021-44228", CvssBase: 10.0, EpssProbability: 0.975, EpssPercentile: 0.999, InKev: true, AssetCriticality: AssetCriticality.Critical, AssetExposure: AssetExposure.External, DataSensitivity: DataSensitivity.Confidential, PublicExploit: true, ExploitMaturity: ExploitMaturity.High);
var prioritizer = new RiskPrioritizer(); var result = prioritizer.Evaluate(vuln);
Console.WriteLine($"Priority Score: {result.Score}"); // ~95 Console.WriteLine($"Category: {result.Category}"); // P1 - Critical Console.WriteLine($"SLA: {result.SlaHours} hours"); // 24
EPSS Integration
using System.Net.Http.Json; using System.Text.Json.Serialization;
public sealed record EpssScore( string Cve, double Epss, double Percentile, string Date);
/// <summary>Client for EPSS (Exploit Prediction Scoring System) API</summary> public sealed class EpssClient(HttpClient httpClient) { private const string BaseUrl = "https://api.first.org/data/v1/epss";
/// <summary>Get EPSS score for a CVE</summary>
public async Task<EpssScore?> GetScoreAsync(string cveId, CancellationToken ct = default)
{
var response = await httpClient.GetFromJsonAsync<EpssResponse>(
$"{BaseUrl}?cve={Uri.EscapeDataString(cveId)}", ct);
if (response?.Data is not { Count: > 0 })
return null;
var data = response.Data[0];
return new EpssScore(data.Cve, data.Epss, data.Percentile, data.Date);
}
/// <summary>Get EPSS scores for multiple CVEs</summary>
public async Task<IReadOnlyList<EpssScore>> GetBulkScoresAsync(
IEnumerable<string> cveIds,
CancellationToken ct = default)
{
var cveParam = string.Join(",", cveIds.Select(Uri.EscapeDataString));
var response = await httpClient.GetFromJsonAsync<EpssResponse>(
$"{BaseUrl}?cve={cveParam}", ct);
return response?.Data?
.Select(d => new EpssScore(d.Cve, d.Epss, d.Percentile, d.Date))
.ToArray() ?? [];
}
private sealed record EpssResponse(
[property: JsonPropertyName("data")] List<EpssData>? Data);
private sealed record EpssData(
[property: JsonPropertyName("cve")] string Cve,
[property: JsonPropertyName("epss")] double Epss,
[property: JsonPropertyName("percentile")] double Percentile,
[property: JsonPropertyName("date")] string Date);
}
// Registration with IHttpClientFactory // services.AddHttpClient<EpssClient>();
KEV (Known Exploited Vulnerabilities) Integration
using System.Collections.Concurrent; using System.Net.Http.Json; using System.Text.Json.Serialization;
/// <summary>CISA Known Exploited Vulnerability entry</summary> public sealed record KevEntry( string CveId, string VendorProject, string Product, string VulnerabilityName, DateOnly DateAdded, DateOnly DueDate, string ShortDescription, string RequiredAction, string Notes);
/// <summary>Client for CISA KEV catalog</summary> public sealed class KevClient(HttpClient httpClient) { private const string CatalogUrl = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json";
private readonly ConcurrentDictionary<string, KevEntry> _cache = new();
private DateTimeOffset _lastRefresh = DateTimeOffset.MinValue;
public DateTimeOffset LastRefresh => _lastRefresh;
/// <summary>Refresh KEV catalog from CISA</summary>
public async Task RefreshCatalogAsync(CancellationToken ct = default)
{
var catalog = await httpClient.GetFromJsonAsync<KevCatalog>(CatalogUrl, ct)
?? throw new InvalidOperationException("Failed to fetch KEV catalog");
_cache.Clear();
foreach (var vuln in catalog.Vulnerabilities ?? [])
{
var entry = new KevEntry(
CveId: vuln.CveId,
VendorProject: vuln.VendorProject,
Product: vuln.Product,
VulnerabilityName: vuln.VulnerabilityName,
DateAdded: DateOnly.Parse(vuln.DateAdded),
DueDate: DateOnly.Parse(vuln.DueDate),
ShortDescription: vuln.ShortDescription,
RequiredAction: vuln.RequiredAction,
Notes: vuln.Notes ?? string.Empty);
_cache[entry.CveId] = entry;
}
_lastRefresh = DateTimeOffset.UtcNow;
}
/// <summary>Check if CVE is in KEV catalog</summary>
public bool IsInKev(string cveId) => _cache.ContainsKey(cveId);
/// <summary>Get KEV entry for CVE</summary>
public KevEntry? GetEntry(string cveId) =>
_cache.TryGetValue(cveId, out var entry) ? entry : null;
/// <summary>Get all overdue KEV entries</summary>
public IReadOnlyList<KevEntry> GetOverdue()
{
var today = DateOnly.FromDateTime(DateTime.UtcNow);
return _cache.Values.Where(e => e.DueDate < today).ToArray();
}
private sealed record KevCatalog(
[property: JsonPropertyName("vulnerabilities")] List<KevVuln>? Vulnerabilities);
private sealed record KevVuln(
[property: JsonPropertyName("cveID")] string CveId,
[property: JsonPropertyName("vendorProject")] string VendorProject,
[property: JsonPropertyName("product")] string Product,
[property: JsonPropertyName("vulnerabilityName")] string VulnerabilityName,
[property: JsonPropertyName("dateAdded")] string DateAdded,
[property: JsonPropertyName("dueDate")] string DueDate,
[property: JsonPropertyName("shortDescription")] string ShortDescription,
[property: JsonPropertyName("requiredAction")] string RequiredAction,
[property: JsonPropertyName("notes")] string? Notes);
}
// Registration with IHttpClientFactory // services.AddHttpClient<KevClient>();
Vulnerability Disclosure
Security.txt
security.txt - RFC 9116 compliant
Place at /.well-known/security.txt
Contact: mailto:security@example.com Contact: https://example.com/security/report Expires: 2025-12-31T23:59:59.000Z Encryption: https://example.com/.well-known/pgp-key.txt Acknowledgments: https://example.com/security/hall-of-fame Preferred-Languages: en Canonical: https://example.com/.well-known/security.txt Policy: https://example.com/security/policy Hiring: https://example.com/careers/security
Disclosure Policy
Vulnerability Disclosure Policy
Scope
The following assets are in scope:
- *.example.com
- Example Mobile Apps (iOS, Android)
- Example API (api.example.com)
Out of scope:
- Third-party services
- Social engineering attacks
- Physical security testing
- Denial of service attacks
Rules of Engagement
- Do not access or modify data belonging to others
- Stop testing and report immediately if you access user data
- Do not publicly disclose before we've addressed the issue
- Provide sufficient detail to reproduce the issue
Safe Harbor
We will not pursue legal action against researchers who:
- Act in good faith
- Avoid privacy violations
- Do not cause service disruption
- Follow responsible disclosure timeline
Timeline
- Acknowledgment: 3 business days
- Initial assessment: 7 business days
- Status updates: Every 14 days
- Resolution target: 90 days (varies by severity)
Rewards
| Severity | Bounty Range |
|---|---|
| Critical | $5,000 - $25,000 |
| High | $2,000 - $5,000 |
| Medium | $500 - $2,000 |
| Low | $100 - $500 |
Bug Bounty Integration
using System.Collections.Frozen;
public enum ReportStatus { New, Triaged, Duplicate, Informative, NotApplicable, Resolved, BountyAwarded }
public enum BountySeverity { Critical, High, Medium, Low }
/// <summary>Bug bounty report tracking</summary> public sealed record BountyReport( string Id, string Title, string Reporter, BountySeverity Severity, ReportStatus Status, DateTimeOffset SubmittedAt, string Asset, string Description, string ReproductionSteps, string Impact, string? CveId = null, decimal? BountyAmount = null, DateTimeOffset? ResolvedAt = null);
public sealed record SlaComplianceResult( int SlaHours, double ElapsedHours, bool IsCompliant, double RemainingHours);
public sealed record DisclosureTimeline( DateTimeOffset Submitted, DateTimeOffset TriageDeadline, DateTimeOffset InitialResponse, DateTimeOffset ResolutionTarget, DateTimeOffset PublicDisclosure);
/// <summary>Manage bug bounty program operations</summary> public sealed class BugBountyManager { private static readonly FrozenDictionary<BountySeverity, (decimal Min, decimal Max)> SeverityBountyRange = new Dictionary<BountySeverity, (decimal, decimal)> { [BountySeverity.Critical] = (5000, 25000), [BountySeverity.High] = (2000, 5000), [BountySeverity.Medium] = (500, 2000), [BountySeverity.Low] = (100, 500) }.ToFrozenDictionary();
private static readonly FrozenDictionary<BountySeverity, int> SlaHours =
new Dictionary<BountySeverity, int>
{
[BountySeverity.Critical] = 24,
[BountySeverity.High] = 72,
[BountySeverity.Medium] = 168,
[BountySeverity.Low] = 336
}.ToFrozenDictionary();
/// <summary>Calculate bounty amount based on severity and factors</summary>
public decimal CalculateBounty(
BountySeverity severity,
double impactFactor = 1.0,
double qualityFactor = 1.0)
{
var (minBounty, maxBounty) = SeverityBountyRange.GetValueOrDefault(severity);
// Base bounty is midpoint
var baseBounty = (minBounty + maxBounty) / 2;
// Apply factors
var adjusted = baseBounty * (decimal)impactFactor * (decimal)qualityFactor;
// Clamp to range
return Math.Clamp(adjusted, minBounty, maxBounty);
}
/// <summary>Check if report is within SLA</summary>
public SlaComplianceResult CheckSlaCompliance(BountyReport report)
{
var slaHours = SlaHours.GetValueOrDefault(report.Severity, 168);
var elapsed = (DateTimeOffset.UtcNow - report.SubmittedAt).TotalHours;
return new SlaComplianceResult(
SlaHours: slaHours,
ElapsedHours: elapsed,
IsCompliant: elapsed <= slaHours,
RemainingHours: Math.Max(0, slaHours - elapsed));
}
/// <summary>Generate coordinated disclosure timeline</summary>
public static DisclosureTimeline GenerateDisclosureTimeline(DateTimeOffset submitted) =>
new(
Submitted: submitted,
TriageDeadline: submitted.AddDays(3),
InitialResponse: submitted.AddDays(7),
ResolutionTarget: submitted.AddDays(90),
PublicDisclosure: submitted.AddDays(104)); // 90 + 14 days
}
SLA Configuration
vulnerability-sla.yaml
sla_policy: name: "Production Vulnerability SLA" version: "2.0" effective_date: "2024-01-01"
Default SLAs by CVSS score
severity_slas: critical: # CVSS 9.0-10.0 detection_to_triage: 4h triage_to_remediation_start: 24h remediation_sla: 72h total_sla: 96h escalation_path: - level: 1 after: 24h notify: [security-team, engineering-lead] - level: 2 after: 48h notify: [ciso, vp-engineering] - level: 3 after: 72h notify: [ceo, board]
high: # CVSS 7.0-8.9
detection_to_triage: 8h
triage_to_remediation_start: 48h
remediation_sla: 168h # 7 days
total_sla: 216h
escalation_path:
- level: 1
after: 72h
notify: [security-team]
- level: 2
after: 120h
notify: [ciso]
medium: # CVSS 4.0-6.9
detection_to_triage: 24h
triage_to_remediation_start: 168h
remediation_sla: 720h # 30 days
total_sla: 888h
escalation_path:
- level: 1
after: 336h
notify: [security-team]
low: # CVSS 0.1-3.9
detection_to_triage: 72h
triage_to_remediation_start: 336h
remediation_sla: 2160h # 90 days
total_sla: 2496h
escalation_path: []
Overrides for specific conditions
overrides: - condition: "in_kev == true" override_sla: "critical" reason: "CISA KEV mandates accelerated remediation"
- condition: "epss_percentile >= 0.95"
decrease_sla_percent: 50
reason: "High exploitation probability"
- condition: "asset_exposure == 'external' and asset_criticality == 'critical'"
override_sla: "critical"
reason: "Internet-facing critical assets"
Risk acceptance policy
risk_acceptance: allowed_severities: [low, medium] max_acceptance_period: 180d required_approvers: low: [security-team-lead] medium: [ciso] documentation_required: true review_frequency: 30d
Security Checklist
Before finalizing vulnerability management setup:
-
Asset inventory complete and current
-
Scanning coverage for all assets
-
CVSS + EPSS + KEV prioritization configured
-
SLAs defined and communicated
-
Escalation paths established
-
Remediation workflow documented
-
Disclosure policy published (security.txt)
-
Bug bounty program considered
-
Metrics and reporting established
-
Exception/risk acceptance process defined
References
-
CVSS Scoring Deep Dive - v3.1 and v4.0 detailed calculation
-
Remediation Workflow - End-to-end remediation processes
Version History
- v1.0.0 (2025-12-26): Initial release with CVSS, prioritization, disclosure
Last Updated: 2025-12-26