Vulnerable and Outdated Components: How to Find OWASP A06 Risks

Your application's security posture is only as strong as the weakest component it runs on. That's not a hypothetical — Log4Shell (CVE-2021-44228) was remotely exploitable in tens of thousands of applications because they all shared the same logging library. Heartbleed exposed private keys from web servers running a specific OpenSSL version range. The 2017 Equifax breach that compromised 147 million records traced back to a known, unpatched Apache Struts vulnerability with a public exploit available for two months before the breach.

OWASP A06 covers exactly this attack surface: software that has reached its end of life, frameworks running vulnerable versions, and third-party dependencies carrying unpatched CVEs. The challenge isn't that these vulnerabilities are subtle — many have CVSS scores above 9.0 with public exploit code. The challenge is that organizations often don't know what versions they're running, and even when they do, patching cycles lag behind disclosure timelines.

This guide walks through how to identify vulnerable and outdated components from the outside in — the techniques attackers use to enumerate versions, how to map those versions to CVEs, and what DAST tooling can surface versus what requires manual verification.

Why Outdated Components Are a Distinct Attack Category

Most vulnerability classes require an attacker to find a flaw in your code. A06 is different: the flaw is already documented, often with a CVSS score, a CVE ID, a proof-of-concept, and sometimes a Metasploit module. An attacker doesn't need to discover anything — they just need to confirm you're running the affected version.

This flips the attacker economics entirely. Scanning for component versions at scale is trivial. A single Shodan query or a Nuclei template sweep can identify every internet-facing server running Apache 2.4.49 (the path traversal/RCE vulnerability, CVE-2021-41773) in minutes. The work is fingerprinting, not exploitation research.

The other reason A06 warrants its own category: it's transitive. Your code might be flawless and still be exposed because a library you imported three years ago hasn't been updated, or because your container base image ships with a vulnerable version of curl or zlib. The vulnerability graph extends through your entire dependency tree, not just the code your team wrote.

Common Vulnerability Patterns

Outdated server-side frameworks Web frameworks advertise their version through HTTP response headers and error pages. An Express.js server leaks its version via X-Powered-By: Express. Spring Boot applications often expose build information through Actuator endpoints (/actuator/info). Django debug mode outputs framework version in error pages. Rails exposes version details in X-Runtime headers and stacktraces. A known vulnerable framework version with an available exploit is one of the most direct paths to RCE in a web application assessment.
Exposed admin interfaces for outdated software WordPress installations, phpMyAdmin, Jenkins, and other self-hosted tools often run versions that are years behind the current release. These interfaces are high-value targets because they're directly accessible, authentication is sometimes weak or bypassed by the CVE itself, and the admin panel gives direct access to sensitive data or code execution. WordPress plugins are statistically the most exploited category — the plugin ecosystem has thousands of abandoned plugins with unpatched vulnerabilities that predate their last commit.
Client-side libraries with known CVEs JavaScript libraries served to the browser carry their own vulnerability surface. jQuery versions below 3.5 are vulnerable to prototype pollution (CVE-2019-11358) and XSS via $.parseHTML(). Bootstrap 3.x has XSS vulnerabilities in tooltip and popover components. Angular 1.x (AngularJS) reached end-of-life in December 2021 and has multiple unpatched CVEs. These libraries are often loaded from CDNs or bundled in builds that haven't been updated in years — they're invisible to server-side scanning but fully enumerable from the client side.
Web server version disclosure Apache, Nginx, and IIS all include version information in their default Server response headers. Server: Apache/2.4.49 (Unix) tells an attacker the exact version to cross-reference against NVD. IIS version numbers narrow down the Windows Server release, enabling targeted OS-level CVE research. Nginx version disclosure in combination with third-party module banners (nginx/1.14.0 + PHP/7.2.24) creates a complete fingerprint of the stack.
End-of-life CMS and plugin versions Content management systems accumulate plugins, themes, and core versions that drift behind the patched release. WordPress core auto-updates for minor security releases but not for major versions. Many production sites run WordPress 5.x or earlier, PHP 7.x (end-of-life since November 2022), and a collection of plugins that were last updated when the site was built. Drupal and Joomla have similar patterns. Plugin vulnerabilities are catalogued exhaustively in databases like WPScan Vulnerability Database and Exploit-DB, making version enumeration directly actionable.

Version Fingerprinting Techniques

Before you can map a component to a CVE, you need to know what version is running. Version disclosure happens through several channels:

HTTP response headers

The most direct source. Headers like Server, X-Powered-By, and X-AspNet-Version often contain explicit version strings. A basic curl request reveals them:

# Retrieve response headers without fetching the body
curl -sI https://target.example.com

# Example output revealing the full stack
HTTP/1.1 200 OK
Server: Apache/2.4.51 (Debian)
X-Powered-By: PHP/7.4.33
X-Generator: Drupal 9 (https://www.drupal.org)
X-Content-Type-Options: nosniff

Note that a hardened server will suppress or modify these headers — absence of a Server header is itself a signal worth noting, and it shifts the approach to passive fingerprinting.

HTML meta tags and generator strings

CMS platforms often embed their identity in the HTML source. WordPress includes a <meta name="generator" content="WordPress 6.1.1"> tag by default. Joomla and Drupal do the same. These persist even when server headers are suppressed because they're generated by the application layer, not the web server.

# Extract generator meta tags from a page
curl -s https://target.example.com | grep -i 'generator'

# Also check for version strings in script/link src attributes
curl -s https://target.example.com | grep -Eo 'ver=[0-9.]+|[0-9]+\.[0-9]+\.[0-9]+\.js'

JavaScript filenames with version numbers

Libraries bundled or loaded with explicit version numbers in their filenames are a clean fingerprint source: jquery-3.4.1.min.js, bootstrap-3.3.7.min.js, angular-1.8.2.min.js. CDN-hosted libraries are particularly predictable — the full URL encodes the version.

Error pages and stack traces

Default error pages are version goldmines. A Django DEBUG=True 500 error page outputs the full framework version, Python version, installed packages, and file paths. Tomcat default error pages include the Tomcat version and Java version. A 404 from an unpatched Apache installation shows the server version in the footer. Triggering error conditions (malformed request, invalid path) is a standard fingerprinting step.

nmap service version scanning

For infrastructure-level component identification, nmap's version detection probes application banners actively:

# Service version detection on web ports
nmap -sV -p 80,443,8080,8443 target.example.com

# Aggressive version detection with script scanning
nmap -sV --version-intensity 9 -p 80,443 target.example.com

# Run HTTP-specific NSE scripts for additional fingerprinting
nmap -p 80,443 --script http-headers,http-server-header,http-generator target.example.com

The http-generator script extracts CMS generator tags. http-headers dumps all response headers. Combined with -sV, a single nmap run can enumerate the web server version, application framework, and CMS version in one pass.

CVE Mapping Workflow

Once you have version strings, the workflow to prioritize exploitable vulnerabilities follows a consistent pattern:

  1. Enumerate versions — collect all component versions identified across the target: web server, application framework, CMS, plugins, client-side libraries.
  2. Query NVD — search the NIST National Vulnerability Database (nvd.nist.gov) for each component and version. Filter by CVSS score. Anything above 7.0 warrants attention; above 9.0 is critical.
  3. Check Exploit-DB — NVD tells you the vulnerability exists. Exploit-DB tells you whether a working exploit is public. A high-CVSS CVE with no public exploit is lower urgency than a medium-CVSS CVE with a Metasploit module.
  4. Verify exploitability in context — not every CVE that applies to a version is exploitable in every deployment. An RCE that requires authentication matters less if the vulnerable endpoint isn't exposed. A DoS vulnerability matters more on high-availability infrastructure. Map the CVE's attack vector against what you can actually reach.
  5. Check Shodan for exposure scale — if you're assessing infrastructure, Shodan searches like http.server:"Apache/2.4.49" show you how broadly the vulnerable version is deployed, which is useful context for executive reporting.

Testing Client-Side Components

Server-side scanners don't see JavaScript libraries loaded in the browser. Testing client-side components requires a different approach: enumerate what the page actually loads.

The manual approach is straightforward — open DevTools, go to the Sources or Network tab, and look for versioned library filenames. The systematic approach uses tools purpose-built for this:

Retire.js is the standard tool for client-side component analysis. It runs as a browser extension, a CLI tool, or a Grunt/Gulp plugin. The CLI version scans a directory of JS files or a URL:

# Install Retire.js
npm install -g retire

# Scan a URL for vulnerable client-side libraries
retire --js --jspath https://target.example.com

# Scan a local directory of JS files
retire --path /path/to/webapp/public/js/

Retire.js maintains its own vulnerability database that maps library versions to CVEs, including libraries that aren't in NVD. It flags the specific CVE, the affected version range, and the severity.

Also check for libraries loaded from third-party CDNs in the page source. A page loading jQuery from a CDN with Subresource Integrity (SRI) attributes is correctly handling integrity verification. A page loading an old jQuery version without SRI is both running a vulnerable library and susceptible to CDN compromise.

What Tools Help

Tool What it covers When to use it
Retire.js Client-side JavaScript library CVEs Any assessment of a web app; run against the live URL or the JS build directory
OWASP Dependency-Check Server-side dependency CVEs (Java, .NET, Node, Python, Ruby) Build pipeline integration; requires access to source or build artifacts
Nuclei Known CVE exploitation templates for specific component versions Post-fingerprinting verification that a CVE is actually exploitable on the target
nmap version scripts Web server and service version banners Infrastructure-level scanning; good for catching server and middleware versions
WPScan WordPress core, theme, and plugin vulnerabilities Any target running WordPress; enumerates installed plugins and maps to CVEs
Nikto Outdated server software, default files, version disclosure headers Broad initial sweep; noisy but catches many common misconfigurations and old software

Nuclei deserves special mention. Its template library includes purpose-built checks for specific high-impact CVEs — not generic version matching, but actual proof-of-concept request sequences that confirm a vulnerability is present and reachable. Running Nuclei's CVE template set against a target after fingerprinting gives you confirmed exploitability rather than version-match inference.

# Run all CVE templates against a target
nuclei -u https://target.example.com -t cves/

# Run templates for a specific year
nuclei -u https://target.example.com -t cves/2021/

# Run a specific CVE template (e.g., Log4Shell)
nuclei -u https://target.example.com -t cves/2021/CVE-2021-44228.yaml

# Enumerate component versions with technology detection templates
nuclei -u https://target.example.com -t technologies/

What Automated DAST Tools Catch vs. Miss

Component vulnerability detection is one of the stronger areas for automated scanning — but there are meaningful gaps between what a scanner surfaces and what's fully confirmed.

What automated scanners do well: Version fingerprinting from HTTP headers, generator tags, and JavaScript filenames is mechanical and highly reliable. Mapping those versions to CVE databases is equally deterministic. A good scanner will enumerate every version string it can extract and flag every known CVE for each version. This is fast, comprehensive across the identified attack surface, and doesn't require manual effort per component.

Where it gets harder: Confirming that a specific CVE is exploitable in a specific deployment context requires understanding the application's configuration, which endpoints are reachable, and whether mitigating controls are in place. A scanner that sees Apache/2.4.49 can flag CVE-2021-41773, but confirming exploitability of the path traversal requires actually testing the traversal payload — something Nuclei templates can do, but generic scanners may report as informational rather than confirmed.

What scanners consistently miss: Components that suppress version disclosure entirely, libraries loaded via dynamic imports or bundled without version strings in filenames, and vulnerabilities in custom builds of open-source software that have backported patches without updating version numbers (common in Linux distribution packages). A Red Hat or Debian build of Apache might report a version that appears vulnerable but has the CVE patched in a backport — version string matching will false-positive here.

How Ironimo Approaches Component Scanning

Every Ironimo scan runs a full version fingerprinting pass as part of the initial reconnaissance phase. This covers HTTP response headers across all discovered endpoints (not just the homepage), HTML source analysis for generator tags and versioned asset URLs, client-side JavaScript library enumeration, and error page analysis to surface framework and runtime version disclosures.

Identified versions are correlated against CVE databases with CVSS scoring and exploit-availability flagging. Findings are prioritized by two factors: severity (CVSS base score) and exploitability (whether a public exploit or Nuclei template confirms the vulnerability is reachable). A CVE with CVSS 9.8 and a working Nuclei template gets a different priority level than a CVE with CVSS 7.2 and no known public exploit.

Ironimo also flags components that are end-of-life even when no specific CVE is present. Running PHP 7.x or Python 2.x means you're accumulating unpatched vulnerabilities with no vendor response path — that's a risk posture finding independent of any single CVE.

What Ironimo explicitly doesn't claim: that it can exploit complex, multi-step CVEs or validate exploitability in heavily customized deployments. Version fingerprinting and CVE correlation are automated and reliable. Confirming that a Spring4Shell payload executes in your specific Spring Boot configuration, or that a deserialization gadget chain works against your exact Java classpath, requires manual verification that an automated scanner can't replicate.

Key Takeaways

Ironimo runs a full version fingerprinting pass on every scan — HTTP headers, HTML source, client-side JavaScript, and error pages — and correlates identified versions against CVE databases with CVSS scoring and exploit-availability flags.

You get a clear view of what outdated components are exposed, which CVEs apply, and which findings have confirmed public exploits. The same tooling professional pentesters use to identify A06 risks, running continuously against your application.

Start free scan
← Back to blog