Vulnerability Management
Vulnerability management identifies security weaknesses in systems and applications, prioritises them by exploitability and impact, and drives remediation within defined timeframes. This task covers the complete cycle from asset discovery through scanning, prioritisation, remediation tracking, and exception handling. Execute these procedures on a continuous basis: authenticated scans weekly, unauthenticated scans daily, and prioritisation review within 24 hours of new findings.
Prerequisites
| Requirement | Detail |
|---|---|
| Scanner access | Administrative credentials for vulnerability scanner (OpenVAS, Nessus, or Qualys) |
| Scan credentials | Service accounts with read access to target systems for authenticated scanning |
| Asset inventory | Current list of IP ranges, hostnames, and system owners from CMDB or asset register |
| Network access | Scanner host must reach all target networks; firewall rules permitting scanner source IPs |
| Permissions | Vulnerability analyst role with write access to ticketing system for remediation tracking |
| Time allocation | 4 hours weekly for scan review and prioritisation; additional time for remediation coordination |
Verify scanner connectivity before beginning. For OpenVAS, confirm the Greenbone Vulnerability Manager service is running:
sudo systemctl status gvmd# Expected: active (running)
sudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \ --xml "<get_version/>"# Expected: <get_version_response status="200">For Nessus, verify the service and API availability:
sudo systemctl status nessusd# Expected: active (running)
curl -sk https://localhost:8834/server/status# Expected: {"status":"ready"}Ensure scan credentials have been tested against representative targets. Authentication failures during scans produce incomplete results that miss critical vulnerabilities requiring local access to detect.
Procedure
Asset discovery and scope definition
Export the current asset inventory from your configuration management database or asset register. The scope must include all systems the organisation operates, including cloud instances, containers, and field office infrastructure.
For organisations using a spreadsheet-based inventory, structure the export with these columns:
IP/Hostname,Environment,Owner,OS,Criticality,Last Scan Date10.0.1.50,Production,j.smith@example.org,Ubuntu 22.04,High,2024-11-01192.168.5.20,Development,k.jones@example.org,Windows Server 2022,Medium,2024-11-08Identify assets not present in the inventory through network discovery. Run an Nmap sweep against all known network ranges:
Terminal window nmap -sn -PE -PP -PM -PS21,22,23,25,80,443,3389 \10.0.0.0/16 192.168.0.0/16 172.16.0.0/12 \-oX discovery_results.xmlCompare discovered hosts against the asset inventory. Any discrepancy indicates either an inventory gap or an unauthorised device requiring investigation.
Categorise assets into scan groups based on network location, criticality, and scan window constraints. Critical production systems require authenticated scans during maintenance windows. Development and test systems tolerate more aggressive scanning during business hours.
Create scan groups in your scanner. For OpenVAS:
Terminal window sudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml '<create_target><name>Production Servers</name><hosts>10.0.1.0/24,10.0.2.0/24</hosts><port_list id="33d0cd82-57c6-11e1-8ed1-406186ea4fc5"/><ssh_credential id="[credential-uuid]"/></create_target>'Define scan schedules that balance detection coverage against operational impact. Configure authenticated scans for weekly execution during low-usage periods. Unauthenticated external scans run daily without operational restrictions.
+------------------------------------------------------------------+| SCAN SCHEDULE MATRIX |+------------------------------------------------------------------+| || DAILY (02:00-04:00 UTC) || +---------------------------+ || | External unauthenticated | || | - Perimeter hosts | || | - Web applications | || | - API endpoints | || +---------------------------+ || || WEEKLY (Saturday 22:00-06:00 UTC) || +---------------------------+ || | Internal authenticated | || | - Production servers | || | - Database servers | || | - Domain controllers | || +---------------------------+ || || WEEKLY (Wednesday 12:00-18:00 UTC) || +---------------------------+ || | Development/Test | || | - Dev environments | || | - Staging systems | || | - CI/CD infrastructure | || +---------------------------+ || || MONTHLY (First Sunday 22:00-06:00 UTC) || +---------------------------+ || | Full credential scan | || | - All assets | || | - Deep plugin set | || +---------------------------+ || |+------------------------------------------------------------------+Figure 1: Scan schedule distribution across asset categories
Vulnerability scanning execution
Initiate the scheduled scan or trigger an ad-hoc scan for newly deployed systems. For OpenVAS, create and start a task:
Terminal window # Create scan taskTASK_UUID=$(sudo -u gvm gvm-cli socket --gmp-username admin \--gmp-password $GVM_PASS \--xml '<create_task><name>Weekly Production Scan</name><config id="daba56c8-73ec-11df-a475-002264764cea"/><target id="[target-uuid]"/><scanner id="08b69003-5fc2-4037-a479-93b440211c73"/></create_task>' | grep -oP 'id="\K[^"]+')# Start the tasksudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml "<start_task task_id=\"$TASK_UUID\"/>"Monitor scan progress to detect stalled or failed scans early. Check task status every 30 minutes during execution:
Terminal window sudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml "<get_tasks task_id=\"$TASK_UUID\"/>" | \grep -oP 'progress="\K[^"]+'# Expected: value between 0 and 100, increasing over timeA scan showing 0% progress after 30 minutes indicates a connectivity or credential problem. A scan showing the same percentage for over 60 minutes suggests a hung scanner process.
Export scan results upon completion. Generate reports in both machine-readable and human-readable formats:
Terminal window # Get the report ID from the completed taskREPORT_UUID=$(sudo -u gvm gvm-cli socket --gmp-username admin \--gmp-password $GVM_PASS \--xml "<get_tasks task_id=\"$TASK_UUID\"/>" | \grep -oP 'report id="\K[^"]+' | head -1)# Export as CSV for processingsudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml "<get_reports report_id=\"$REPORT_UUID\" format_id=\"c1645568-627a-11e3-a660-406186ea4fc5\"/>" \> scan_results.csv# Export as PDF for stakeholder reportingsudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml "<get_reports report_id=\"$REPORT_UUID\" format_id=\"c402cc3e-b531-11e1-9163-406186ea4fc5\"/>" \| base64 -d > scan_report.pdf
Vulnerability prioritisation
Import scan results into your vulnerability tracking system or spreadsheet. Each vulnerability requires assessment against multiple factors before assigning remediation priority.
The CVSS base score provides a standardised severity rating from 0.0 to 10.0, but this score alone produces poor prioritisation. A CVSS 9.8 vulnerability in an isolated development server presents less organisational risk than a CVSS 7.5 vulnerability in an internet-facing payment system.
Apply contextual factors to each vulnerability. Calculate an adjusted priority score using this formula:
Priority Score = CVSS Base Score × Asset Criticality × Exposure Factor × Exploit AvailabilityWhere:- Asset Criticality: 1.5 (Critical), 1.2 (High), 1.0 (Medium), 0.7 (Low)- Exposure Factor: 1.5 (Internet-facing), 1.2 (Partner-accessible), 1.0 (Internal only)- Exploit Availability: 1.5 (Active exploitation), 1.3 (Public exploit), 1.0 (No known exploit)Worked example for a SQL injection vulnerability:
CVE-2024-1234: SQL Injection in web applicationCVSS Base Score: 8.6Asset: Donor management portal (Critical, Internet-facing)Exploit: Public Metasploit module availablePriority Score = 8.6 × 1.5 × 1.5 × 1.3 = 25.1Compare to:CVE-2024-5678: Local privilege escalationCVSS Base Score: 7.8Asset: Development workstation (Low, Internal only)Exploit: No known public exploitPriority Score = 7.8 × 0.7 × 1.0 × 1.0 = 5.5The SQL injection receives priority despite comparable CVSS scores because contextual factors multiply the base risk.
Consult the CISA Known Exploited Vulnerabilities (KEV) catalogue for any vulnerabilities under active exploitation. KEV-listed vulnerabilities receive automatic Critical priority regardless of calculated score:
Terminal window # Download current KEV cataloguecurl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \-o kev.json# Check if a specific CVE is listedjq -r '.vulnerabilities[] | select(.cveID=="CVE-2024-1234")' kev.jsonAny match indicates the vulnerability is under active exploitation and requires remediation within the KEV-specified timeframe, which is 14 days for most entries.
Check the Exploit Prediction Scoring System (EPSS) for vulnerabilities not yet in KEV. EPSS provides a probability score (0.0 to 1.0) indicating likelihood of exploitation within 30 days:
Terminal window # Query FIRST EPSS APIcurl -s "https://api.first.org/data/v1/epss?cve=CVE-2024-1234" | \jq '.data[0].epss'# Returns: 0.89 (89% probability of exploitation)Vulnerabilities with EPSS scores above 0.7 warrant treatment equivalent to known-exploited vulnerabilities. Scores between 0.4 and 0.7 justify accelerated remediation timelines.
Assign remediation SLAs based on final priority classification:
Priority Criteria Remediation SLA Verification deadline Critical Priority score > 20, or KEV-listed, or EPSS > 0.7 72 hours 96 hours High Priority score 12-20, or CVSS ≥ 9.0 7 days 10 days Medium Priority score 6-12, or CVSS 7.0-8.9 30 days 45 days Low Priority score < 6, or CVSS < 7.0 90 days 120 days
Remediation workflow
Create remediation tickets for each vulnerability requiring action. Group vulnerabilities affecting the same system or requiring the same fix into a single ticket to reduce operational overhead.
Ticket content must include:
Title: [Priority] CVE-2024-1234 - SQL Injection in donor-portalAffected Asset: donor-portal.example.org (10.0.5.20)CVE: CVE-2024-1234CVSS: 8.6Priority: CriticalDescription: SQL injection vulnerability in login form allowsunauthenticated attackers to extract database contents.Remediation: Upgrade donor-portal application to version 3.2.1Vendor Advisory: https://vendor.example/security/2024-001SLA: 72 hours (due: 2024-11-18 14:00 UTC)Owner: j.smith@example.orgVerification: Re-scan after patching confirms vulnerability absentAssign tickets to system owners identified in the asset inventory. For organisations without clear ownership, escalate to IT management for assignment within 24 hours of vulnerability discovery.
+------------------------------------------------------------------+| REMEDIATION WORKFLOW |+------------------------------------------------------------------+| || +-------------------+ || | Vulnerability | || | Identified | || +---------+---------+ || | || v || +---------+---------+ +-----------------------+ || | Owner | | No owner identified | || | Assigned? +---->| Escalate to IT Mgmt | || +---------+---------+ | (24hr assignment SLA) | || | +-----------+-----------+ || | Yes | || v | || +---------+---------+<----------------+ || | Ticket Created | || | SLA Clock Starts | || +---------+---------+ || | || v || +---------+---------+ || | Owner Implements | || | Fix | || +---------+---------+ || | || v || +---------+---------+ || | Owner Requests | || | Verification | || +---------+---------+ || | || v || +---------+---------+ +-----------------------+ || | Verification | No | Return to Owner | || | Scan Confirms +------->| Reset SLA Clock | || | Fix? | +-----------------------+ || +---------+---------+ || | || | Yes || v || +---------+---------+ || | Ticket Closed | || | Metrics Captured | || +-------------------+ || |+------------------------------------------------------------------+Figure 2: Remediation workflow from identification through closure
Monitor remediation progress against SLA. Run daily reports showing tickets approaching or exceeding their deadlines:
Terminal window # Example query for Jira-based trackingcurl -s -u "$JIRA_USER:$JIRA_TOKEN" \"https://jira.example.org/rest/api/2/search?jql=project=VULN+AND+status!=Closed+AND+duedate<=1d" | \jq '.issues[] | {key: .key, summary: .fields.summary, due: .fields.duedate}'Tickets within 24 hours of SLA breach require escalation to the owner’s manager. Tickets exceeding SLA require documentation and escalation to IT leadership.
Execute verification scans within the verification deadline to confirm remediation success. Target only the specific hosts remediated rather than running a full infrastructure scan:
Terminal window # Targeted verification scan with OpenVASsudo -u gvm gvm-cli socket --gmp-username admin --gmp-password $GVM_PASS \--xml '<create_target><name>Verification - CVE-2024-1234</name><hosts>10.0.5.20</hosts><port_list id="33d0cd82-57c6-11e1-8ed1-406186ea4fc5"/><ssh_credential id="[credential-uuid]"/></create_target>'The vulnerability must be absent from verification scan results to close the ticket. Partial remediation or compensating controls require exception handling rather than closure.
Exception handling
Initiate an exception request when remediation is impossible within the SLA. Valid exception reasons include vendor patch unavailability, business-critical system constraints, and third-party managed systems outside organisational control. Convenience, resource constraints, and competing priorities do not justify exceptions for Critical or High vulnerabilities.
Document the exception request with required fields:
VULNERABILITY EXCEPTION REQUESTVulnerability: CVE-2024-1234Affected Asset: legacy-finance.example.orgOriginal SLA: 72 hours (Critical)Reason for Exception:Application vendor discontinued support in 2023. No patch available.Replacement project scheduled for Q2 2025.Compensating Controls:1. Network segmentation - system isolated to finance VLAN (10.0.8.0/24)2. WAF rule deployed blocking SQL injection patterns3. Enhanced monitoring - all database queries logged to SIEM4. Reduced attack surface - removed public-facing componentsResidual Risk Assessment:Attack requires authenticated access from finance VLAN.WAF blocks 94% of SQL injection attempts in testing.Estimated residual risk: Medium (reduced from Critical)Requested Exception Period: 180 days (until replacement deployment)Review Date: 2025-02-15Requestor: j.smith@example.orgDate: 2024-11-15Route exception requests through the approval chain based on original vulnerability priority:
Original Priority Approver Maximum Exception Period Critical CISO or IT Director 30 days, renewable High Security Manager 90 days, renewable Medium Security Analyst Lead 180 days Low System Owner 365 days Implement compensating controls before exception approval. The approver must verify compensating controls are operational, not merely planned. Document control effectiveness with evidence:
Terminal window # Verify network segmentationnmap -Pn -p 443 10.0.5.20 -S 10.0.1.100# Expected: filtered (not reachable from general network)nmap -Pn -p 443 10.0.5.20 -S 10.0.8.50# Expected: open (reachable from finance VLAN)# Verify WAF rule deploymentcurl -s "https://legacy-finance.example.org/login" \-d "user=admin' OR '1'='1" | grep -i blocked# Expected: WAF block page returnedTrack exceptions in a register separate from the main vulnerability tracking. Review all active exceptions at the cadence appropriate to their priority: Critical exceptions weekly, High exceptions fortnightly, Medium and Low monthly.
+------------------------------------------------------------------+| EXCEPTION APPROVAL FLOW |+------------------------------------------------------------------+| || +-------------------+ || | Exception | || | Requested | || +---------+---------+ || | || v || +---------+---------+ +-----------------------+ || | Compensating | No | Request Returned | || | Controls +------->| Implement controls | || | Implemented? | | before resubmitting | || +---------+---------+ +-----------------------+ || | || | Yes || v || +---------+---------+ || | Security Review | || | Control Efficacy | || +---------+---------+ || | || v || +---------+---------+ +-----------------------+ || | Controls | No | Additional controls | || | Adequate? +------->| required | || +---------+---------+ +-----------------------+ || | || | Yes || v || +---------+---------+ || | Approver Review | || | (per priority) | || +---------+---------+ || | || +----+----+ || | | || v v || +----+----+ +--+-------------+ || |Approved | | Denied | || |Exception| | Remediation | || |Registered| Required | || +---------+ +----------------+ || |+------------------------------------------------------------------+Figure 3: Exception approval flow with control verification
Reporting and metrics
Generate weekly vulnerability status reports summarising the current state and remediation progress. Include these metrics:
WEEKLY VULNERABILITY REPORT - Week ending 2024-11-15CURRENT STATE+-----------+-------+--------+---------+| Priority | Open | In SLA | Overdue |+-----------+-------+--------+---------+| Critical | 3 | 2 | 1 || High | 12 | 10 | 2 || Medium | 47 | 45 | 2 || Low | 134 | 132 | 2 |+-----------+-------+--------+---------+| Total | 196 | 189 | 7 |+-----------+-------+--------+---------+ACTIVITY THIS WEEK- New vulnerabilities identified: 23- Vulnerabilities remediated: 31- Exceptions approved: 2- Exceptions expired: 1MEAN TIME TO REMEDIATE (last 30 days)- Critical: 2.3 days (SLA: 3 days)- High: 5.8 days (SLA: 7 days)- Medium: 22.4 days (SLA: 30 days)- Low: 67.2 days (SLA: 90 days)OVERDUE ITEMS REQUIRING ESCALATION1. CVE-2024-1234 on legacy-finance (Critical, 5 days overdue)2. CVE-2024-2345 on dev-server-03 (High, 3 days overdue)Calculate trend metrics monthly to identify systemic issues:
Vulnerability Density = Total Open Vulnerabilities / Total AssetsExample: 196 vulnerabilities / 450 assets = 0.44 vulnerabilities per assetTarget: < 0.5 for mature programmes, < 1.0 for developing programmesSLA Compliance Rate = (Tickets Closed in SLA / Total Tickets Closed) × 100Example: 31 in SLA / 35 total = 88.6%Target: > 95% for Critical/High, > 90% for Medium/LowRecurrence Rate = (Vulnerabilities Reopened / Vulnerabilities Closed) × 100Example: 4 reopened / 35 closed = 11.4%Target: < 5% indicates effective remediationPresent quarterly executive summaries showing risk reduction over time. Express findings in business terms rather than technical metrics:
QUARTERLY EXECUTIVE SUMMARY - Q3 2024Risk Posture: IMPROVINGKey Achievements:- Reduced critical vulnerabilities from 12 to 3 (75% reduction)- Achieved 94% SLA compliance for critical issues- Eliminated all internet-facing critical vulnerabilitiesAreas Requiring Attention:- Legacy finance system carries accepted risk (exception in place)- Remediation capacity constrained - 2 weeks backlog on Medium itemsResource Request:- Additional 0.5 FTE for remediation would reduce backlog by 60%
Verification
Confirm the vulnerability management process is functioning correctly through these checks:
Scan coverage verification:
# Compare scanned assets against inventoryINVENTORY_COUNT=$(wc -l < asset_inventory.csv)SCANNED_COUNT=$(sudo -u gvm gvm-cli socket --gmp-username admin \ --gmp-password $GVM_PASS \ --xml "<get_hosts/>" | grep -c '<host')
echo "Inventory: $INVENTORY_COUNT, Scanned: $SCANNED_COUNT"# Coverage should exceed 95%COVERAGE=$(echo "scale=2; $SCANNED_COUNT / $INVENTORY_COUNT * 100" | bc)echo "Coverage: $COVERAGE%"Prioritisation consistency:
Review 10 randomly selected vulnerabilities and recalculate their priority scores manually. All scores should match the assigned priority within one category.
Remediation SLA adherence:
# Calculate SLA compliance from ticketing systemCLOSED_IN_SLA=$(curl -s -u "$JIRA_USER:$JIRA_TOKEN" \ "https://jira.example.org/rest/api/2/search?jql=project=VULN+AND+status=Closed+AND+resolved>=startOfMonth()" | \ jq '[.issues[] | select(.fields.resolutiondate <= .fields.duedate)] | length')
TOTAL_CLOSED=$(curl -s -u "$JIRA_USER:$JIRA_TOKEN" \ "https://jira.example.org/rest/api/2/search?jql=project=VULN+AND+status=Closed+AND+resolved>=startOfMonth()" | \ jq '.total')
echo "SLA Compliance: $(echo "scale=1; $CLOSED_IN_SLA / $TOTAL_CLOSED * 100" | bc)%"Exception register accuracy:
Cross-reference active exceptions against current scan results. Every excepted vulnerability must still appear in scans; if absent, the exception should be closed.
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
| Scan shows 0 hosts discovered | Firewall blocking scanner, incorrect IP ranges, network routing issue | Verify scanner can ping targets; check firewall rules allow scanner source IP; confirm IP ranges match current network architecture |
| Authenticated scan shows far fewer vulnerabilities than expected | Credential failure, insufficient permissions, credential lockout | Check scanner logs for authentication errors; verify service account has local admin or root equivalent; confirm account not locked |
| Scan never completes | Target host unresponsive, scan hanging on specific check, scanner resource exhaustion | Reduce concurrent scan threads; exclude problematic hosts and scan separately; increase scanner memory allocation |
| Same vulnerability reappears after remediation | Incomplete patching, configuration management overwriting fix, different instance of same vulnerability | Verify patch applied to correct host; check for configuration automation reverting changes; confirm vulnerability is identical CVE and host |
| CVSS scores not matching vendor advisories | Scanner using outdated NVD data, vendor-modified score, scanner interpolation error | Update scanner vulnerability feeds; compare scanner CVE data against NVD directly; use vendor CVSS where available |
| Scans impacting production performance | Scan timing during peak hours, aggressive scan settings, network saturation | Reschedule to maintenance windows; reduce scan intensity settings; implement rate limiting on scanner |
| False positives overwhelming real findings | Banner-based detection without verification, outdated scanner signatures, misidentified software | Enable active verification where available; update scanner feeds; tune scanner to reduce banner-only detections |
| Unable to scan cloud instances | Security groups blocking scanner, ephemeral instances changing IPs, container environments | Deploy cloud-native scanner or agent; use API-based scanning for cloud providers; implement container scanning in CI/CD |
| Verification scan still shows vulnerability present | Patch requires reboot not yet performed, cached scan data, vulnerability in different component | Confirm service restart after patching; wait for cache expiry or force rescan; verify exact file versions match patched state |
| Exception request rejected despite compensating controls | Controls insufficiently documented, control effectiveness not demonstrated, residual risk still too high | Provide evidence of control testing; demonstrate detection capability; propose additional controls to further reduce risk |
| Metrics show declining performance despite remediation effort | Scan scope expansion, new systems added faster than remediation, detection capability improvement | Segment metrics by asset age; track new vs legacy vulnerability rates separately; normalise metrics by asset count |
| Scanner credential warning after password rotation | Scan credentials updated in source system but not scanner, credential synchronisation delay | Update credentials in scanner configuration immediately after rotation; implement credential management integration |
Automation
Automate routine elements of vulnerability management to reduce manual effort and improve consistency.
Scheduled scan execution:
# Daily external scan at 02:000 2 * * * gvm /usr/local/bin/run-external-scan.sh
# Weekly internal scan Saturday 22:000 22 * * 6 gvm /usr/local/bin/run-internal-scan.shAutomatic ticket creation from scan results:
#!/usr/bin/env python3import jsonimport requestsfrom datetime import datetime, timedelta
def calculate_sla(priority): sla_days = {'Critical': 3, 'High': 7, 'Medium': 30, 'Low': 90} return datetime.now() + timedelta(days=sla_days.get(priority, 90))
def create_ticket(vuln): priority = classify_priority(vuln) due_date = calculate_sla(priority)
ticket = { 'fields': { 'project': {'key': 'VULN'}, 'issuetype': {'name': 'Vulnerability'}, 'summary': f"[{priority}] {vuln['cve']} - {vuln['name'][:50]}", 'description': format_description(vuln), 'duedate': due_date.strftime('%Y-%m-%d'), 'assignee': {'name': get_asset_owner(vuln['host'])} } }
response = requests.post( 'https://jira.example.org/rest/api/2/issue', json=ticket, auth=(JIRA_USER, JIRA_TOKEN) ) return response.json()KEV and EPSS enrichment:
#!/bin/bash# Download current KEV cataloguecurl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \ -o /var/lib/vulnmgmt/kev.json
# Enrich each vulnerability with KEV and EPSS datawhile read -r cve; do # Check KEV kev_match=$(jq -r ".vulnerabilities[] | select(.cveID==\"$cve\")" /var/lib/vulnmgmt/kev.json)
# Get EPSS epss_score=$(curl -s "https://api.first.org/data/v1/epss?cve=$cve" | jq -r '.data[0].epss // 0')
# Update vulnerability record echo "$cve,$kev_match,$epss_score" >> /var/lib/vulnmgmt/enriched.csvdone < /var/lib/vulnmgmt/pending_cves.txtResource-constrained implementation
Organisations with limited IT capacity can implement vulnerability management using free tools. Deploy OpenVAS on a dedicated virtual machine with 4 CPU cores and 8GB RAM. Start with monthly authenticated scans of critical systems only, expanding scope as capacity allows. Use spreadsheet-based tracking initially, migrating to ticketing systems as volume increases. Prioritise ruthlessly: focus exclusively on Critical and High vulnerabilities until those are under control before addressing Medium and Low findings.