Step 114. Reconnaissance 2: Vulnerability Scanners and Reading Their Results — A Scanner Is a Candidate Generator
Level 2 — Introduction to Security and the Basics of Attack Skills | Difficulty ★★★☆☆ | Estimated time: 3 hours
Prerequisites: you’ve built the target’s service inventory (the port/service/version table) in Step 113. You can use nmap’s basic options (
-sV,--script). A Linux terminal (including WSL) is ready.
- What you need: a Linux terminal and nmap. Today’s scan targets are strictly
127.0.0.1(your own computer) and, if you have the lab, Metasploitable2 (hereafter MS2). - Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Every hands-on measurement in this chapter was performed against
127.0.0.1only, on WSL Linux (Nmap 7.94SVN). MS2-targeted output and commercial scanner screens are marked as "Screen example."
In Step 113 we drew a "map" of the target — which ports are running which services at which versions. Today we handle the tool that automatically stamps "this is a candidate for a breach" marks onto that map: the vulnerability scanner. Scanners are convenient, but they also lie. So today’s real goal is not how to "run" a scanner — it is how to read a scanner’s claims critically.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Automatically collect vulnerability candidates with nmap’s
vulnscript category - Extract CVE numbers from scanner output and organize them into a list
- Explain why false positives occur and cross-validate scan results against version information
- Follow a workflow that treats the scanner not as a "conclusion" but as a "candidate generator"
- Narrow the port and script scope to manage time when a scan is slow
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Linux terminal (WSL or lab VM), nmap 7.94’s NSE scripts |
| Today’s commands | nmap --script vuln, --script "http-vuln-*" (narrowing scope), grep CVE resultsfile, --script-help |
| Concepts needed | CVE numbers, false positives and false negatives, Step 113’s service inventory |
| Today’s artifact | A vulnerability candidate list + a confidence judgment for each candidate (high / needs verification) |
2-1. What a Vulnerability Scanner Does
A vulnerability scanner is the final process of reconnaissance. After identifying services and versions, it checks a database for "this version has these known vulnerabilities" and pulls out candidates. There are three representative kinds.
- nmap’s NSE
vulncategory: vulnerability check scripts built into the nmap we already know. Today’s measurement environment (nmap 7.94SVN on WSL) had 50 built-in scripts withvulnin the name. - Dedicated scanners like Nessus / OpenVAS: commercial and open-source scanners with tens of thousands of checks. They produce pretty reports from a web interface, at the cost of heavy installation and licensing. Today we only look at them as screen examples.
- Scripts tied to external APIs like vulners: they query an external vulnerability DB with the versions they found. The weakness is that they need the internet — today’s measurement exposes this weakness plainly.
2-2. False Positives and False Negatives — A Scanner’s Two Lies
A scanner’s judgment is mostly "version-number comparison." Two kinds of errors arise from this.
- False positive (says vulnerable when it isn’t): distributions (like Ubuntu) often fix a vulnerability by porting just the patch backward (backporting) without raising the version number. The scanner sees only the version and says "vulnerable," but it’s actually already fixed. Conversely, there are also cases where the configuration differs so the attack conditions don’t hold.
- False negative (misses what’s there): new vulnerabilities not yet in the scanner’s DB, services hiding on nonstandard ports, and services that hide their banners don’t get caught. "The scanner said it’s clean" does not mean "it’s safe."
That’s why a scanner’s output is not a conclusion but a candidate list. Whether something is actually breachable must be confirmed in the next stage (manual verification, exploitation), and that confirmation begins in Step 117.
2-3. The Procedure for Reading Results
In professional reconnaissance, the order for processing scanner results is fixed.
- Run the scan and save the results to an
-oNfile - Extract only the CVE numbers from the output into a list (
grep) - Cross-check against Step 113’s inventory (services and versions) — is the version the scanner claims the same as the version I verified myself?
- Attach a confidence level to each candidate — "high (version match + attack conditions hold)" or "needs verification (version is the only basis)"
- Set verification priorities and hand off to the next stage
3. Follow Along
3-1. Preparation — A Practice Service and a Look at the Built-in Scripts
As in Step 81, scanning an empty computer teaches nothing, so let’s open a practice web server.
Input (terminal 1)
mkdir -p /tmp/steplab && cd /tmp/steplab
echo "<h1>hello plain world</h1>" > index.html
python3 -m http.server 8000
Don’t close this window for the rest of the exercises. In another terminal, let’s count how many of today’s protagonists — the vuln scripts — exist.
Input (terminal 2)
ls /usr/share/nmap/scripts/ | grep -c vuln
Output (measured 2026-09-09):
50
How to read it: this nmap has 50 scripts at the vuln category level. They’re divided by service — HTTP (http-vuln-*), SMB (smb-vuln-*), SMTP (smtp-vuln-*), and so on. Running all 50 at once is what --script vuln does — and you’ll soon learn with your body why that’s a problem.
Why: knowing the tool’s scale is the starting point of time management. Running "all 50 against every service on the target" is heavier than it sounds.
3-2. A Vulnerability Scan with Narrowed Scope
To get a feel for it, let’s pick only the HTTP-service vulnerability scripts (http-vuln-*) and run them against port 8000.
Input
nmap --script "http-vuln-*" -sV -p 8000 127.0.0.1
Output (measured 2026-09-09):
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-09-09 15:12 KST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00014s latency).
PORT STATE SERVICE VERSION
8000/tcp open http SimpleHTTPServer 0.6 (Python 3.12.3)
|_http-server-header: SimpleHTTP/0.6 Python/3.12.3
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.30 seconds
How to read it: more than 20 http-vuln-* scripts all ran (6.30 seconds), yet not a single script reported an additional vulnerability. The |_http-server-header line is not a vulnerability report but a banner reconfirmation — this script belongs to the version category, so it tags along automatically when -sV is on (verified by measurement: the script file’s categories = {"version"}). Python’s simple server is a clean target that matches none of the CVEs the scanner knows.
Why: first get the feel that "the scanner saying nothing" is also a normal result. Only then does the contrast with the next step’s "chatty result" register as extraordinary.
3-3. The Whole vuln Category — And the Time Bomb
Now let’s run the entire category. But first, make a prediction. If you run 50 scripts against a single port 8000, how long will it take?
Input
time nmap --script "vuln and not vulners" -p 8000 127.0.0.1 -oN /tmp/steplab/vuln_all.txt
Output (measured 2026-09-09 — run with a 200-second limit, aborted on timeout):
real 3m20.003s ← after 200 seconds, timeout forcibly terminated the scan
The scan never finished, and the results file was empty (nmap writes the file only when a scan completes). What ate the time? Let’s interrogate the suspect with --script-help.
Input
nmap --script-help http-slowloris-check
Output (measured 2026-09-09, excerpt):
http-slowloris-check
Categories: vuln safe
Tests a web server for vulnerability to the Slowloris DoS attack without
actually launching a DoS attack.
...
The script opens two connections to the server, each without the final CRLF.
After 10 seconds, second connection sends additional header. Both connections
then wait for server timeout.
How to read it: because this script measures "how long the server holds onto incomplete connections," by design it waits until the server’s timeout expires. The check itself is built to consume time. With scripts like this mixed in, running the whole vuln category can take minutes even on a single port.
Why: in real work, "the scan won’t finish" is an accident that blows up the day’s schedule. This is why assignment guides warn that "vuln scripts can take 30+ minutes — narrow ports with -p." Now that we know the culprit, let’s exclude the slow scripts and run again.
3-4. Finishing the Run Without the Slow Scripts
Input
time nmap --script "vuln and not vulners and not http-slowloris-*" -sV -p 8000 127.0.0.1 -oN /tmp/steplab/vuln_fast.txt
Output (measured 2026-09-09):
PORT STATE SERVICE VERSION
8000/tcp open http SimpleHTTPServer 0.6 (Python 3.12.3)
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
|_http-server-header: SimpleHTTP/0.6 Python/3.12.3
Nmap done: 1 IP address (1 host up) scanned in 79.29 seconds
How to read it: it finished in 79 seconds, and this time three scripts politely reported "Couldn’t find any." How you read this output matters — "Couldn’t find any XSS" does not mean "there is no XSS"; it means "this script’s checking method did not discover one." The possibility of a false negative is written right into the sentence.
Why: a scanner’s negative result is not proof of a negative. Reading the tone of this one line is half of the critical reading skill we learn today.
3-5. The Trap of External-API Scripts — vulners
The vuln category also contains the vulners script, which queries an external vulnerability DB (vulners.com) with the versions it finds. What happens if you run it in an isolated lab?
Input
nmap --script vulners -sV -p 8000 127.0.0.1
Output (measured 2026-09-09):
PORT STATE SERVICE VERSION
8000/tcp open http SimpleHTTPServer 0.6 (Python 3.12.3)
|_http-server-header: SimpleHTTP/0.6 Python/3.12.3
Nmap done: 1 IP address (1 host up) scanned in 6.70 seconds
How to read it: no report at all. Not even an error message. Unable to reach the external DB, it quietly gave up. A fine example that "no output" can mean not "no vulnerabilities" but "check impossible."
Why: when a scanner is silent, you must always distinguish "it’s clean" from "it couldn’t run." In an environment with a blocked network, like today’s measurement, the latter is the correct answer.
3-6. Extracting CVEs from Results — Against MS2 (Screen Example)
Let’s see how the results change when we run the same scan against the lab’s MS2 (Metasploitable2). MS2 is a practice virtual machine made deliberately vulnerable, so output like the following is normal (this environment has no MS2, so this is marked as a Screen example).
Input (in the lab, assuming MS2’s IP is 192.168.56.101)
nmap --script vuln -sV 192.168.56.101 -oN ms2_vuln.txt
Screen example (excerpt):
21/tcp open ftp vsftpd 2.3.4
|_smtp-vuln-cve2010-4344: ...
| sslv2-drown:
| ciphers: ...
| vulners:
| cpe:/a:vsftpd:vsftpd:2.3.4:
| CVE-2011-2523 10.0 https://vulners.com/cve/CVE-2011-2523
...
|_ssl-ccs-injection: VULNERABLE: ...
Input — extract only the CVE numbers from the results file into a list:
grep -oE 'CVE-[0-9]{4}-[0-9]+' ms2_vuln.txt | sort -u
Screen example:
CVE-2004-2687
CVE-2011-2523
CVE-2015-3306
...
How to read it: for an old system like MS2, dozens of results are normal (consistent with the assignment guide’s warning). grep -oE ... | sort -u is the professional technique for compressing scan results into a "CVE candidate list." Against our environment’s practice-server results file, this command returned 0 hits (measured 2026-09-09) — remember the contrast between a clean target and a vulnerable target.
Why: raw scanner output can’t go into a report. Only with this step of summarizing into a CVE list can you move to the next stage (reading each CVE in the NVD, in Step 115).
3-7. Cross-Validation — Do We Believe the Scanner?
The final process is checking the scanner’s claims against information you gathered yourself. The procedure goes like this (lab basis).
- If the scanner said "vsftpd 2.3.4 has CVE-2011-2523," check whether the version you verified yourself with
-sVin Step 113 really is 2.3.4 — banner manipulation might have fooled it. - Attach a confidence level to each candidate. For example: "CVE-2011-2523 — version match, public exploit exists → high confidence," "CVE-2004-2687 — version is the only basis → needs verification."
- This list becomes the raw material for choosing actual exploits in Steps 116–117.
Why: building the doubt list — "of the things the scanner called vulnerable, which actually break?" — is today’s completion criterion and the habit that decides the quality of an entire penetration test.
4. Missions & Exercises
Mission — A Vulnerability Candidate List with Confidence Judgments
- With the practice server from 3-1 running, execute
nmap --script "vuln and not vulners and not http-slowloris-*" -sV -p 8000 127.0.0.1 -oN vuln_fast.txtand record the elapsed time. - Count how many scripts in the results file reported "Couldn’t find."
- If your lab has MS2, run
nmap --script vuln -sV <MS2_IP> -oN ms2_vuln.txt(it takes time — you may narrow with-p), and build a CVE candidate list withgrep -oE 'CVE-[0-9]{4}-[0-9]+' ms2_vuln.txt | sort -u. - Pick 3 interesting CVEs from the list and organize, for each, "the basis the scanner presented (version? response test?)" and "whether it matches the Step 113 inventory" in a table.
- Attach a confidence level (high / needs verification) to each candidate, with a one-line rationale for each judgment.
Exercises
Exercise 1. Name two representative reasons false positives occur.
Exercise 2. When a scan result says Couldn't find any stored XSS vulnerabilities, explain the difference between what this sentence guarantees and what it does not.
Exercise 3. Using one script we examined today as evidence, explain why running the whole --script vuln category can take minutes even on a single port.
Exercise 4. When a scanner is silent (no reports at all), name two possible interpretations and what you would check to tell them apart.
5. Model Answers & Completion Criteria
Mission Model Answer
Items 1–2 are confirmed directly by measurement. In the 2026-09-09 measurement environment, the scan took 79.29 seconds, and there were three "Couldn’t find" reports (CSRF, DOM XSS, stored XSS). In your environment the numbers may differ depending on the open services.
The CVE extraction command for item 3 is as confirmed in the Screen example:
grep -oE 'CVE-[0-9]{4}-[0-9]+' ms2_vuln.txt | sort -u
For items 4–5, a table like this is sufficient (MS2-based example):
| CVE | Scanner’s basis | Matches inventory? | Confidence | One-line rationale |
|---|---|---|---|---|
| CVE-2011-2523 | vsftpd 2.3.4 version match | Match (verified with -sV on port 21) |
High | Version match + many public exploits |
| CVE-2004-2687 | distccd version estimate | Needs checking | Needs verification | No version in the banner — only an estimate |
| CVE-2015-3306 | Assumes mod_copy module present | Needs checking | Needs verification | Whether the module is actually loaded is unconfirmed |
How to verify: ① Does the results file contain the scan date/time and full command (-oN‘s automatic record)? ② Is the CVE list deduplicated with sort -u? ③ Does "version match or not" enter the confidence judgment as a basis — if everything is "high" with no grounds, do it again.
Exercise Answers
Answer 1. First, distribution backport patches — the vulnerability is fixed but the version number stays the same, so the scanner classifies it as "vulnerable." Second, attack conditions not holding — the version is indeed vulnerable, but the relevant feature (module/setting) is disabled, so the attack doesn’t actually work.
Answer 2. What it guarantees: the fact that "this script’s checking method (signatures, test payloads) found nothing." What it does not guarantee: that XSS actually doesn’t exist. Other input paths, other payloads, and pages behind authentication are outside this check. A negative result is not proof of a negative.
Answer 3. A script like http-slowloris-check must measure "how long the server holds incomplete connections," so by design it waits for the server’s timeout (--script-help measurement: "Both connections then wait for server timeout"). With scripts like this mixed into the category, a full run stretches to minutes — in the 2026-09-09 measurement it hit the 200-second limit and was aborted, while excluding it finished in 79 seconds.
Answer 4. One interpretation is "it’s truly clean"; the other is "the checks didn’t run." Today’s vulners script ending silently after failing to reach the external DB (6.70 seconds, no output — measured) is an example of the latter. How to tell them apart: check whether the scan completed normally (time, the Nmap done line), whether the scripts actually ran (--script-trace, or the required conditions in each script’s help), and whether the script needs external resources.
Completion Criteria Checklist
- [ ] I can explain the difference (scope and time) between
--script vulnand--script "http-vuln-*" - [ ] I can narrow scope with a script selection expression (
and not ...) when a scan is slow - [ ] I extracted CVE numbers from a results file into a deduplicated list
- [ ] I can distinguish and explain false positives and false negatives
- [ ] I can accurately interpret the meaning of a "Couldn’t find" sentence
- [ ] I can state the procedure for cross-validating scanner results against the Step 113 inventory
- [ ] Mission: I completed the confidence-judgment table for 3 candidates
6. Common Pitfalls & Fixes
Wall 1. The vuln scan never finishes
Symptom (measured 2026-09-09): checking a single port with --script "vuln and not vulners" hadn’t completed after 200 seconds, so timeout forcibly terminated it, and the -oN results file was empty.
Cause: scripts like http-slowloris-check, whose check itself waits for the server timeout, are mixed into the category. Because nmap writes the file only when the scan finishes, aborting loses the record too.
Fix: exclude slow scripts with a selection expression — --script "vuln and not vulners and not http-slowloris-*" (measured: finished in 79 seconds). In real work, narrow the port range (-p) as well.
Wall 2. The vulners script says nothing at all
Symptom (measured 2026-09-09): running --script vulners -sV finished quietly in 6.70 seconds without a single line of vulnerability reports.
Cause: this script queries an external API (vulners.com) with versions. In a lab without internet or on a closed network, the check itself is impossible, and it moves on silently without an error.
Fix: don’t misread "silence = clean." Check with --script-help whether the script needs external resources, and if you’re in a blocked environment, substitute another means of checking (an offline NVD DB, searchsploit — Step 115).
Wall 3. The scanner dumps dozens of results and I don’t know where to start
Symptom (Screen example, MS2 target): dozens of CVEs appear.
Cause: MS2 is a system made deliberately vulnerable, so this is normal. The problem isn’t the scan — it’s the absence of priorities.
Fix: ① turn them into a list with grep -oE 'CVE-[0-9]{4}-[0-9]+' file | sort -u, ② cross-check versions against the Step 113 inventory, ③ sort by "is there a public exploit?" After these three filters, the candidates worth verifying fit on one hand.
Wall 4. The version looks different between the scanner and the inventory
Symptom: -sV said vsftpd 2.3.4, but another script based its judgment on a different version.
Cause: each script estimates versions through a different path (banner, response patterns, heuristics). A banner can also be changed by an administrator.
Fix: take the value you verified yourself (the -sV banner) as the standard, mark the scanner’s estimate as "weak basis," and lower its confidence. This is why cross-validation exists.
Wall 5. I attach the scanner report to my report as-is
Symptom: you paste the raw Nessus/nmap output wholesale into the results report.
Cause: scanner output is a "candidate," not a "conclusion." If false positives are reported as-is, you’ll have someone applying the wrong patches.
Fix: process in the order candidate list → cross-validation → confidence notation → manual verification, then report. Today’s mission table is that processing format.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Vulnerability scanner | A tool that checks versions and responses against a DB and automatically pulls out vulnerability "candidates" |
| False positive | Says vulnerable when it isn’t — backport patches and unmet conditions are the main causes |
| False negative | Misses what’s there — vulnerabilities outside the DB, hidden services |
| Reading a negative result | "Couldn’t find" means not "there is none" but "this method didn’t find one" |
| Cross-validation | The essential process of checking the scanner’s claims against your self-gathered inventory |
| Candidate generator | The scanner’s proper place — the conclusion is drawn by a human’s verification |
Today’s Commands
| Command | What it does |
|---|---|
ls /usr/share/nmap/scripts/ | grep -c vuln |
Count built-in vuln scripts (measured: 50) |
nmap --script "http-vuln-*" -sV -p port target |
Scan with only the HTTP vulnerability scripts |
nmap --script "vuln and not vulners and not http-slowloris-*" ... |
Category scan excluding slow scripts |
grep -oE 'CVE-[0-9]{4}-[0-9]+' resultsfile | sort -u |
Compress scan results into a CVE candidate list |
nmap --script-help scriptname |
Check a script’s behavior, duration, and requirements |
nmap ... -oN file |
A reproducible scan record with the full command written in |
An Instinct More Important Than Commands
A vulnerability scanner is a diligent but thoughtless assistant. It dumps dozens of candidates based on version numbers alone; when it can’t find something it says only "couldn’t find," never "there is none"; and when the external DB is blocked, it goes quiet without even an error. The skill of using this assistant lies not in the run command but in the reading — the habit of asking "what is the basis of this report, and does it match what I verified myself?"
Remember the contrast today’s measurements showed. Against the clean practice server, 20 scripts finished in 6 seconds without a word, while one slow script held an entire scan hostage past 200 seconds. And the CVE numbers the scanner pulls out are not the end — they’re the beginning. The reading that deciphers each number in the NVD and Exploit-DB is what completes reconnaissance, and that way of reading is the subject of Step 115.
Once every box is checked, Step 114 is complete. Click the checkbox in the sidebar to save your progress.