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.
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
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.
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.
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.
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.
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.
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.
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.py
— vsftpd 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
Regular vulnerability scanning is a cornerstone of proactive security. Combined with timely remediation, it dramatically reduces your organization's attack surface.