Old Kit, New Kit Part 6: Vulnerability Scanning — Nuclei vs Nmap's Vulnerability Scripts
This is the fifth tool-pair actually run in a series that’s testing classic, well-documented Kali tooling against its quieter modern replacements, with a genuine look at what a lab Wazuh deployment sees from each (Part 1 has the full lab build). This pair needs an upfront caveat the others didn’t: the outline calls it “nuclei vs manual checklist,” and there’s no single command that stands in for a manual checklist. The classic baseline used here is nmap --script vuln instead — a real, common Kali workflow, but not a like-for-like race the way rustscan-vs-nmap or feroxbuster-vs-dirb were. Worth saying plainly rather than pretending otherwise: one side of this pair is an automated template engine, the other is a stand-in for a human process.
How this was verified
nuclei was already installed on Kali1 (v3.11.1). Templates were current after nuclei -update-templates (14.70s, “No new updates found for nuclei templates”) — 10730 templates loaded, clustering down to 2468 template groups / 2336 actual requests. For the classic side, nmap -sV --script vuln ran against all three hosts, reusing the already-known open-port list from Part 2’s full scan of metasploitable2 rather than repeating a 65535-port scan:
$ PORTS=$(grep -oP '^\d+(?=/tcp\s+open)' nmap-metasploitable2.txt | paste -sd, -)
$ time nmap -sV --script vuln -p$PORTS 10.10.10.20 -oN nmap-vuln-metasploitable2.txt
$ time nmap -sV --script vuln -p22,80 10.10.10.30 -oN nmap-vuln-target-web.txt
$ time nmap -sV --script vuln -p22,80 10.10.10.40 -oN nmap-vuln-target-wordpress.txt
For nuclei, one run per web-facing service — metasploitable2 has two, its base HTTP install and a Tomcat instance on :8180:
$ time nuclei -u http://10.10.10.20 -o nuclei-metasploitable2.txt
$ time nuclei -u http://10.10.10.20:8180 -o nuclei-metasploitable2-8180.txt
$ time nuclei -u http://10.10.10.30 -o nuclei-target-web.txt
$ time nuclei -u http://10.10.10.40 -o nuclei-target-wordpress.txt
The classic baseline: nmap’s vulnerability scripts
Timing: 454.45s against metasploitable2’s full known-open port list, 35.02s against target-web (ports 22+80 only), 35.68s against target-wordpress (same). The bulk of metasploitable2’s runtime and output comes from the bundled vulners script, which does CPE/version-string lookups against the Vulners CVE database — high recall, a large volume of CVE references per service, but purely version-based. A vulners hit means “this version string is associated with these CVEs,” not “this CVE was confirmed here.” nmap’s other vuln scripts did produce two directly actionable, non-version-based findings on metasploitable2: RMI registry default-configuration RCE (rmi-vuln-classloader) flagged on both port 1099 and port 48139 (a second RMI instance, GNU Classpath’s grmiregistry).
The modern tool: nuclei
Timing and match counts: 113.81s / 69 matches against metasploitable2’s base HTTP install, 392.66s / 18 matches against its Tomcat instance on :8180, 105.65s / 25 matches against target-web, and 119.51s / 32 matches against target-wordpress (that last run reported “Scan completed in 1m” with “HTTP connections: 13585 total, 2517 new, 11068 reused (81.5%)”).
Where nuclei pulls ahead of a version-lookup approach is that a meaningful share of its metasploitable2 findings are actively verified, not inferred from a version string. The vsftpd 2.3.4 backdoor (CVE-2011-2523) got an actual command executed through it — id came back uid=0(root). The distccd command-execution flaw (CVE-2004-2687) was triggered the same way, returning uid=1(daemon). A batch of default-credential templates logged in for real: VNC (password123, and separately password), PostgreSQL (postgres/postgres and an empty password, against both the postgres and template1 databases), Tomcat’s manager application (tomcat/tomcat), and seven distinct weak FTP credential pairs against the ftp user. Ghostcat (CVE-2020-1938, the Apache JServ Protocol smuggling bug) came back flagged critical on port 8009 for both the base install and the :8180 Tomcat instance.
That’s the real methodological line worth drawing for this pair: nmap’s vulners script is a recall-oriented lookup that never touches the service beyond a version probe; a working majority of nuclei’s headline metasploitable2 findings above are the result of nuclei actually doing the thing — an actual login, an actual command run — which carries a different weight than a CVE reference matched off a banner string.
The Part 5 callback
Two of nuclei’s target-web and target-wordpress findings are worth flagging specifically because they reproduce Part 5’s own results by an entirely different route. nuclei’s robots-txt-endpoint template found the exact same backup.zip and internal-notes.txt on target-web that Part 5’s feroxbuster crawl found — but by simply reading robots.txt, not by recursive link-extraction. And on target-wordpress, nuclei’s wordpress-directory-listing template independently confirmed the wp-includes/ Apache directory listing feroxbuster’s heuristics flagged in Part 5, then found a second one Part 5 didn’t mention: wp-content/uploads/. nuclei’s wp-user-enum template, working through the REST API (/?rest_route=/wp/v2/users/), also corroborated nmap’s own http-wordpress-users NSE finding of the username wpadmin. Three separate tools, three separate techniques, the same underlying misconfigurations — a decent argument that these particular findings are real rather than tool-specific artifacts.
Timing: an asymmetry that flips depending on the target
| Target | nmap —script vuln | nuclei |
|---|---|---|
| metasploitable2 | 454.45s | 113.81s (base) + 392.66s (:8180) |
| target-web | 35.02s | 105.65s |
| target-wordpress | 35.68s | 119.51s |
nmap’s runtime scales with what’s actually open and how CVE-rich it is — brutal against metasploitable2’s dozens of vulnerable services, fast against target-web/target-wordpress’s two open ports. nuclei runs the opposite way: its default template set carries fixed overhead regardless of target complexity, because it’s still probing a long list of speculative default ports (9780, 5814, 4040, and others) on every run and logging each as “Skipped … from target list as found unresponsive permanently” even against a two-port host. The honest summary: nmap’s cost is proportional to the target, nuclei’s cost is proportional to its own template set.
What Wazuh actually saw
Both target-web and target-wordpress carry Wazuh agents, so this section covers only those two. The four relevant runs (both nmap vuln scans and both nuclei scans against these targets) landed inside an 18:43–19:03 BST window on Kali1, which is 17:43–18:03 UTC on the manager — the same hour offset established in Part 5. Grepping the manager’s alert log precisely by Rule: NNNNN and bounded to that window and each agent gave the cleanest, most diverse alert haul this series has produced:
target-web (17:43–18:03 UTC):
12098 Rule: 31101 Web server 400 error code
931 Rule: 31151 Multiple web server 400 error codes from same source ip
534 Rule: 31104 Common web attack
402 Rule: 31105 XSS (Cross Site Scripting) attempt
112 Rule: 31103 SQL injection attempt
67 Rule: 31106 A web attack returned code 200 (success)
64 Rule: 31164 SQL injection attempt
60 Rule: 31516 Suspicious URL access
58 Rule: 31153 Multiple common web attacks from same source ip
44 Rule: 31154 Multiple XSS attempts from same source ip
15 Rule: 31152 Multiple SQL injection attempts from same source ip
9 Rule: 31515 PHPMyAdmin scans (looking for setup.php)
9 Rule: 31166 Shellshock attack attempt
1 Rule: 31168 Shellshock attack detected (level 15)
2 Rule: 31110 PHP CGI-bin vulnerability attempt
target-wordpress (17:43–18:03 UTC):
11920 Rule: 31101 Web server 400 error code
918 Rule: 31151 Multiple web server 400 error codes from same source ip
534 Rule: 31104 Common web attack
409 Rule: 31105 XSS (Cross Site Scripting) attempt
115 Rule: 31103 SQL injection attempt
16 Rule: 31509 CMS (WordPress or Joomla) login attempt
2 Rule: 31510 CMS (WordPress or Joomla) brute force attempt (level 8)
10 Rule: 31166 Shellshock attack attempt
1 Rule: 31168 Shellshock attack detected (level 15)
2 Rule: 31110 PHP CGI-bin vulnerability attempt
5 Rule: 31533 High amount of POST requests in a small period (likely bot)
(Full tallies ran to nearly 30 distinct rule IDs per host; the above are the ones that actually classify the traffic rather than just counting it.) That’s the real headline of this section: every earlier pair in this series that produced any Wazuh signal at all produced one or two dominant rules — generic 400-error noise. This pair triggered actual attack-signature rules: SQL injection, XSS, Shellshock, PHP CGI-bin, WordPress-specific brute-force and login-attempt detection. Total volume is lower than Part 5’s single feroxbuster run against target-wordpress (roughly 14000 alerts per host here across four separate tool runs, versus 75228+5908 from one 668-second feroxbuster crawl), but the signal is qualitatively richer — a defender looking at this log sees “SQL injection attempt” and “Shellshock attack attempt,” not just a wall of 400s.
A few of the actual matched requests, quoted verbatim from the alert log:
** Alert 1788026576.70931150: - web,accesslog,attack,sql_injection,...
2026 Aug 29 18:02:56 (target-wordpress) any->/var/log/apache2/access.log
Rule: 31103 (level 7) -> 'SQL injection attempt.'
Src IP: 10.10.10.5
10.10.10.5 - - [29/Aug/2026:19:02:55 +0100] "GET /index.php?rest_route=/podlove/v1/social/services/contributor/1&id=1%20UNION%20ALL%20SELECT%20NULL,NULL,md5('CVE-2021-24666'),NULL,NULL,NULL--%20- HTTP/1.1" 404 591 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
2026 Aug 29 18:01:08 (target-wordpress) any->/var/log/apache2/access.log
Rule: 31166 (level 6) -> 'Shellshock attack attempt'
Src IP: 10.10.10.5
10.10.10.5 - - [29/Aug/2026:19:01:07 +0100] "GET /cgi-bin/stats HTTP/1.1" 404 474 "() { ignored; }; echo Content-Type: text/html; echo ; /bin/cat /etc/passwd" "Mozilla/5.0 (X11; Linux x86_64; rv:1.9.5.20) Gecko/ Firefox/3.6.6"
2026 Aug 29 17:44:06 (target-wordpress) any->/var/log/apache2/access.log
Rule: 31110 (level 6) -> 'PHP CGI-bin vulnerability attempt.'
Src IP: 10.10.10.5
10.10.10.5 - - [29/Aug/2026:18:44:05 +0100] "POST /?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input HTTP/1.1" 200 69143 "-" "Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html)"
Those three requests are a good illustration of a second, more concrete stealth difference than the one Part 4 found with whatweb’s User-Agent. Every SQL injection and XSS request nuclei fired used a different, entirely plausible browser User-Agent — Chrome on Mac, old Firefox on Linux, mobile Safari, and so on — rotated per request. The Shellshock template is the one exception, but not because nuclei slipped: the exploit is the User-Agent header, so a real browser string there would defeat the check. nmap’s PHP-CGI probe, by contrast, went out with its NSE HTTP library’s literal default string, Mozilla/5.0 (compatible; Nmap Scripting Engine; https://nmap.org/book/nse.html) — nmap identifies itself outright on this particular check.
That last alert is also its own small investigation. It’s a genuine, live CVE-2012-1823 (PHP-CGI argument injection) probe fired by nmap’s own vuln script battery — and nmap’s report has zero mentions of it, positive or negative:
$ grep -i -B2 -A6 'cgi' nmap-vuln-target-wordpress.txt
$ grep -i -B2 -A6 'CVE-2012-1823' nmap-vuln-target-wordpress.txt
(no output — either search)
The 69143-byte response looked worth chasing, since other CGI probes in the same batch came back as small 404s. But the exploit only works against a raw php-cgi binary interpreting the query string as command-line flags — a structurally dead attack against a standard mod_php/php-fpm WordPress stack, which is what this box runs. Three baseline GETs to the homepage came back identically sized (68828 bytes), and replicating nmap’s exact request — same method, same query string — came back 200/68828 bytes, byte-identical to the baseline on diff. The 315-byte gap in the original alert was just ordinary momentary WordPress variance, not code execution. The real finding here isn’t a vulnerability — it’s a gap in nmap’s own reporting: it fired a live remote-code-execution attempt as part of its default battery and told the operator nothing about the outcome either way.
Verdict
This pair isn’t a fair race, and it shouldn’t be read as one — vulners-driven version lookup and nuclei’s active-check templates are answering different questions. On this lab, nuclei proved several real, working compromises a version-string approach could only gesture at: root access through a known backdoor, real command execution, and a working set of default credentials across four separate services. nmap’s own vuln scripts weren’t nothing, though — the RMI classloader findings were concrete, and its PHP-CGI check fired a genuine exploit attempt it simply never reported the result of, whichever way that result went. On detection, this was the most linguistically rich haul the series has produced: not a flood of one or two rules but close to thirty distinct signature rules firing, real attack-classified alerts rather than generic 400-error noise, even though the raw volume was a fraction of Part 5’s single feroxbuster run. And nuclei’s habit of rotating realistic browser User-Agents, against nmap NSE’s default self-identifying string, is a second and sharper data point for something this series keeps finding: “modern” here often means “quieter,” independent of whether either tool actually got caught.