Skip to content

Vulnerability Scanning Basics

💡
Before you start

You need a terminal and Python 3 (python3 --version). You will build a working vulnerability scanner and run it against services you start on your own machine, so nothing here scans a system you do not own. The output below was captured in an isolated sandbox; on your machine the same code runs directly. To try the industry tool later, OpenVAS/GVM installs via sudo apt install openvas (a large download and a long feed sync).

🔴 Scan only systems you own or are authorised to assess. A vulnerability scan is active and noisy — it connects, probes, and sometimes trips services — and running one against a system without permission is an offence. Everything below targets 127.0.0.1.

What is Vulnerability Scanning?

Vulnerability scanning is the automated process of probing systems, networks, and applications to identify known security weaknesses. Scanners compare what they find against databases of known vulnerabilities (like CVE entries) to flag potential issues.

Unlike penetration testing, which involves actively exploiting vulnerabilities, scanning is primarily about discovery and identification. It is a critical step in any security assessment and should be performed regularly as part of an organization's security program.

⚠️
Always obtain written authorization

Never scan systems you do not own or have explicit permission to test. Unauthorized vulnerability scanning is illegal in most jurisdictions and can disrupt services.

Types of Vulnerability Scanners

Different scanners serve different purposes. Understanding the types helps you choose the right tool for each situation.

  • Network Scanners - Probe network hosts for open ports, services, and known vulnerabilities (e.g., OpenVAS, Nessus, Qualys)
  • Web Application Scanners - Test web applications for issues like XSS, SQL injection, and misconfigurations (e.g., OWASP ZAP, Burp Suite Scanner, Nikto)
  • Authenticated Scanners - Log into systems to perform deeper checks, finding vulnerabilities invisible from the outside (e.g., missing patches, weak local configs)
  • Agent-Based Scanners - Install lightweight agents on endpoints for continuous monitoring without network-based probing

Setting Up OpenVAS

OpenVAS (now Greenbone Vulnerability Management) is the most widely used open-source vulnerability scanner. It is free and has a comprehensive vulnerability database updated regularly.

# Install OpenVAS on Kali Linux
sudo apt update
sudo apt install gvm -y

# Run the setup (downloads vulnerability feeds - takes time)
sudo gvm-setup

# Start the services
sudo gvm-start

# Access the web interface
# Open browser to https://127.0.0.1:9392
💡
Initial setup takes time

The first-time setup downloads thousands of vulnerability test definitions (NVTs). This can take 30-60 minutes depending on your connection. The web interface will not work properly until the feed sync is complete.

Configuring and Running a Scan

A well-configured scan balances thoroughness with impact. Running an aggressive scan on production systems during business hours can cause outages.

Scan Configuration Checklist

  • Target scope - Define exactly which IPs, ranges, or hostnames to scan
  • Scan type - Full and deep (lab) vs. safe checks only (production)
  • Credentials - Provide SSH/SMB credentials for authenticated scanning when possible
  • Schedule - Run intensive scans during maintenance windows
  • Exclusions - Skip known fragile systems that might crash under probing
# Example: Quick Nmap vulnerability scan (NSE scripts)
nmap -sV --script=vuln 192.168.1.0/24

# Nikto web scanner against a specific target
nikto -h https://target.example.com

# OWASP ZAP command-line scan
zap-cli quick-scan -s all -r https://target.example.com

Interpreting Results

Raw scan results contain a mix of critical findings, informational notes, and false positives. Learning to triage results efficiently is one of the most valuable skills in security assessment.

CVSS Scoring

The Common Vulnerability Scoring System (CVSS) assigns a severity score from 0.0 to 10.0. Use these scores as a starting point for prioritization, but always consider the context of your specific environment.

  • Critical (9.0-10.0) - Immediate action required. Often remotely exploitable with no authentication
  • High (7.0-8.9) - Address within days. Significant risk of exploitation
  • Medium (4.0-6.9) - Address within weeks. Requires specific conditions to exploit
  • Low (0.1-3.9) - Address in next maintenance cycle. Limited impact

Dealing with False Positives

No scanner is perfect. False positives (reported vulnerabilities that do not actually exist) waste time and erode trust in scan results. Conversely, false negatives (real vulnerabilities that the scanner misses) create a dangerous false sense of security.

Verification Strategies

  • Manual verification - Confirm critical findings by testing the vulnerability yourself
  • Cross-reference - Run a second scanner to see if it reports the same issue
  • Version checking - Verify the software version matches the vulnerable version range
  • Patch status - Check if the patch for the CVE has been applied even if the version number was not bumped

Reporting and Remediation

A vulnerability scan is only useful if findings lead to action. Structure your reports to drive remediation, not just list problems.

Effective Report Structure

  • Executive summary - Overall risk posture, critical findings count, trend comparison
  • Prioritized findings - Grouped by severity, with remediation steps for each
  • Affected assets - Clear list of which systems are impacted
  • Remediation timeline - Realistic deadlines based on severity and business impact
  • Compensating controls - Interim mitigations while permanent fixes are implemented

Now Do It Yourself: Build a Vulnerability Scanner in Five Steps

A vulnerability scanner is two things bolted together: something that detects what is running, and a knowledge base of which versions are known to be vulnerable. You will build exactly that — stand up services, detect their versions, match them against a small CVE database, and then meet the failure mode that makes real scan reports untrustworthy: the false positive. Every block of output below was produced by running the code.

1
Stand up a target with a mix of old and current software

Go: a terminal in a writable folder.

Do: save this as target.py and run it in its own terminal. Two services are old and one is current — a good scanner must tell them apart.

import socket, threading, time
banners = {
    2121: b"220 ProFTPD 1.3.5 Server ready\r\n",
    2222: b"SSH-2.0-OpenSSH_9.6\r\n",                       # CURRENT
    8080: b"HTTP/1.1 200 OK\r\nServer: Apache/2.4.29\r\n\r\nok",
}
def serve(port, banner):
    s = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("127.0.0.1", port)); s.listen(5)
    while True:
        c,_ = s.accept()
        try: c.settimeout(0.3); c.recv(200)
        except OSError: pass
        c.sendall(banner); c.close()
for p,b in banners.items():
    threading.Thread(target=serve, args=(p,b), daemon=True).start()
print("target up on 2121, 2222, 8080"); time.sleep(300)

You should see: target up on 2121, 2222, 8080. Leave it running and open a second terminal for the scanner.

If not: Address already in use — change the port numbers and keep them consistent in the scanner.

2
Detect the version behind each port

Go: the second terminal, in the same folder.

Do: save this as scan.py. First, the detection half: connect, read the banner, and pull out product and version.

import socket, re

def grab(port):
    s = socket.socket(); s.settimeout(0.5)
    try:
        s.connect(("127.0.0.1", port))
        s.sendall(b"HEAD / HTTP/1.0\r\n\r\n" if port == 8080 else b"\r\n")
        raw = s.recv(200).decode(errors="replace")
        m = re.search(r"Server:\s*(.+)", raw)
        return m.group(1).strip() if m else raw.strip().splitlines()[0]
    finally:
        s.close()

def detect(banner):
    m = re.search(r"(ProFTPD|Apache|OpenSSH)[/_ ]([0-9]+\.[0-9]+(?:\.[0-9]+)?)", banner)
    return (m.group(1), m.group(2)) if m else (None, None)

for port in (2121, 2222, 8080):
    print(port, detect(grab(port)))

You should see: each service reduced to a product and a version number:

2121 ('ProFTPD', '1.3.5')
2222 ('OpenSSH', '9.6')
8080 ('Apache', '2.4.29')

If not: a (None, None) means the banner did not match the pattern — print the raw grab(port) to see what the service actually sent, then widen the regex.

3
Match versions against a vulnerability database

Go: the same scan.py.

Do: add the knowledge-base half — a tiny table of known-vulnerable versions — and report a finding per port.

VULN_DB = [
    ("ProFTPD", {"1.3.5"},            "CVE-2015-3306",  "CRITICAL", "mod_copy remote code execution"),
    ("Apache",  {"2.4.29", "2.4.28"}, "CVE-2019-0211",  "HIGH",     "local privilege escalation"),
    ("OpenSSH", {"7.4", "7.3"},       "CVE-2016-10009", "MEDIUM",   "agent forwarding code load"),
]
print("%-6s %-16s %s" % ("PORT", "DETECTED", "FINDING"))
for port in (2121, 2222, 8080):
    prod, ver = detect(grab(port))
    finding = "clean"
    for p, vulns, cid, sev, note in VULN_DB:
        if prod == p and ver in vulns:
            finding = "%s [%s] %s" % (cid, sev, note)
    print("%-6d %-16s %s" % (port, "%s %s" % (prod, ver), finding))

You should see: the two old services flagged with real CVEs, and the current one correctly left alone — that last part is as important as the finds:

PORT   DETECTED         FINDING
2121   ProFTPD 1.3.5    CVE-2015-3306 [CRITICAL] mod_copy remote code execution
2222   OpenSSH 9.6      clean
8080   Apache 2.4.29    CVE-2019-0211 [HIGH] local privilege escalation

You have built a vulnerability scanner. OpenVAS and Nessus are this idea with a database of hundreds of thousands of checks instead of three.

If not: if OpenSSH 9.6 gets flagged, your VULN_DB has 9.6 in a version set by mistake — only the old versions belong there.

4
Meet the false positive that makes reports lie

Go: look hard at the Apache finding.

Do: consider what a version string does not tell you. Linux distributions routinely backport a security fix into a package while leaving its version number unchanged — so Ubuntu’s Apache/2.4.29 may already be patched against CVE-2019-0211, even though your matcher, seeing only 2.4.29, screams “HIGH”.

your scanner sees   : Apache 2.4.29         -> flags CVE-2019-0211
reality on Ubuntu   : 2.4.29-1ubuntu4.27    -> fix backported, NOT vulnerable
verdict             : FALSE POSITIVE until confirmed

You should see: why a version match is a lead, not a verdict. A finding must be confirmed — by an authenticated check that reads the real package build, or by safely testing the actual vulnerability — before it goes in a report. Handing a client a list of unconfirmed version matches destroys trust the first time one is wrong.

If not: the opposite error exists too — a false negative, where a custom build hides its version and a real vulnerability is missed. Neither is solved by the version string alone, which is the whole lesson.

5
Do it with a real scanner — and prioritise the results

Go: with OpenVAS/GVM installed, or any authenticated scanner, pointed at a host you own.

Do: run an authenticated scan (credentials let it read installed package versions, avoiding the step-4 guesswork), then triage the report.

# conceptually, what a real scan produces, sorted for action:
CRITICAL  ProFTPD 1.3.5   CVE-2015-3306   remote code exec, exploit public  -> PATCH NOW
HIGH      Apache 2.4.29   CVE-2019-0211   confirmed unpatched                -> schedule
INFO      OpenSSH 9.6     -                current                           -> none

You should see: that the scan is the easy part; the value is triage. Sort by severity × exploitability × exposure — a CRITICAL with a public exploit on an internet-facing host beats a HIGH that needs local access. Remediation is then concrete: patch, reconfigure, or isolate, then re-scan to prove the finding is gone.

If not: an unauthenticated scan produces more false positives (it can only see banners, like your step-3 tool); wherever you can, give the scanner credentials so it reads the truth instead of guessing from version strings.

🎉
Check yourself before moving on

Without scrolling up: your scanner reports Apache 2.4.29 → CVE-2019-0211 [HIGH]. Your client runs Ubuntu and says “we patched that months ago.” Who is right, and how do you settle it? Answer: possibly both — and only a deeper check settles it. Ubuntu backports fixes without changing the version string, so 2.4.29 can be patched against that CVE while still reading as 2.4.29. Your version-only match is a false positive until confirmed by an authenticated check of the real package build (e.g. 2.4.29-1ubuntu4.27) or a safe test of the vulnerability itself. Never ship an unconfirmed version match as a finding.

Now do it without the page: add a fourth service to target.pyvsftpd 2.3.4, famous for a backdoored release — and add its CVE to your VULN_DB. Re-run the scanner and confirm it flags the new service. Then write one sentence on how you would confirm that finding before putting it in a report.

Summary

In this tutorial, you learned:

  • What vulnerability scanning is and how it differs from penetration testing
  • The different types of scanners and when to use each
  • How to set up and configure OpenVAS for vulnerability scanning
  • How to interpret scan results and understand CVSS severity scores
  • Strategies for identifying and managing false positives
  • How to structure actionable remediation reports
🎉
You can now assess system security!

Regular vulnerability scanning is a cornerstone of proactive security. Combined with timely remediation, it dramatically reduces your organization's attack surface.