Skip to content
Vulnerable Components

Vulnerable Components, Explained: Version Fingerprints, Public CVEs and the Patch That Never Shipped

Most of the code a web application serves was written by someone else. Here is how outdated libraries, CMS plugins and server products are fingerprinted and matched to public CVEs, how KEV and EPSS decide what to patch first, and the inventory habits that keep components current.

Part 1 of 1From the Vulnerable Components series

At a glance

CWE-1395
Interpreter
Third-party libraries, frameworks, CMS plugins and server products running on the server or in the browser
Required condition
A deployed component falls inside the affected version range of a published advisory and its vulnerable code is reachable.
Potential impact
Whatever the advisory describes, from script in every visitor's browser to unauthenticated remote code execution.
Primary defense
A complete component inventory, dependency auditing in every build, and patch deadlines driven by KEV and EPSS.

Safe practice: use benign proof only, in a disposable local lab or on a system you are explicitly authorized to test.

On this page

Most of the code a web application runs was written by someone else: the framework, the packages it pulls in, the jQuery file copied into /static years ago, the CMS plugins, and the commercial UI suite registered in web.config. When one of those components has a published vulnerability, every site running an affected version inherits it, and the public advisory doubles as instructions.

OWASP Top 10:2025 places this risk in A03 Software Supply Chain Failures, which maps CWE-1395, Dependency on Vulnerable Third-Party Component, and CWE-1035, the CWE category for the 2017 Top 10 entry Using Components with Known Vulnerabilities. Nothing here is a mistake in your own code. The failure is not knowing what you run, or not updating it.

The mental model: a version is a lookup key

Step Question Evidence
Fingerprint Which component and version is deployed? File names, banners, generator tags, readme files
Match Does that version fall inside an advisory's range? Vendor advisories and CVE records
Reach Is the vulnerable code path exposed here? The live application
Prioritize How likely is exploitation? CISA KEV, FIRST EPSS, CVSS

Attackers run the same four steps, often across thousands of sites at once. Fingerprinting is usually trivial, and the range check is a table lookup.

How it happens

Components drift for ordinary reasons. A library was copied into the repository instead of installed through a package manager, so no tool ever reports it. A transitive dependency three levels down stays pinned by a lockfile nobody refreshes. Someone installed a WordPress plugin outside the deployment pipeline. A Web Forms application still registers Telerik's upload and dialog handlers although the page that used them is long gone.

Some components stop receiving fixes altogether. AngularJS support ended on December 31, 2021, so advisories published since then have no official fixed release to upgrade to.

A concrete test: fingerprint, match, then stop

An authorized tester starts with what the site already publishes. The home page of shop.example carries two clues:

<meta name="generator" content="WordPress 6.2">
<script src="/static/js/jquery-1.8.3.min.js"></script>

A file name can lie, so the tester reads the file's own banner:

curl -s https://shop.example/static/js/jquery-1.8.3.min.js | head -n 1
/*! jQuery v1.8.3 jquery.com | jquery.org/license */

jQuery 1.8.3 falls inside several published ranges, among them CVE-2019-11358, prototype pollution through jQuery.extend, fixed in 3.4.0, and CVE-2020-11022 and CVE-2020-11023, cross-site scripting when untrusted HTML reaches DOM manipulation methods, fixed in 3.5.0. WordPress 6.2 sits inside CVE-2023-2745, a path traversal through translation file loading, fixed in 6.2.1. Plugin versions come from the same kind of evidence: the Stable tag line of a plugin's readme.txt, or a ?ver= query on its assets.

For a few products, one harmless request confirms what the version suggests. A provisioned Telerik Report Server should never show its first-run setup page:

GET /Startup/Register HTTP/1.1
Host: reports.example

A 200 carrying the administrator-creation form means the authentication bypass in CVE-2024-4358 is reachable. The tester records the response and stops; submitting the form would create an account on the target.

What an attacker gains

  • Remote code execution. CISA advisory AA23-074A describes attackers exploiting CVE-2019-18935, a .NET deserialization flaw in Telerik UI for ASP.NET AJAX, on a federal agency's IIS server years after the fix shipped; see .NET deserialization.
  • Script in every visitor's browser from an outdated jQuery, Bootstrap or AngularJS that handles untrusted data; see DOM-based XSS.
  • Prototype pollution through old Lodash, Handlebars or jQuery deep merges; see prototype pollution.
  • Authentication bypass and account takeover in CMS cores, plugins and admin products.

Prioritizing: KEV, EPSS and CVSS

A large application can match hundreds of advisories. CVSS measures how bad a flaw is if exploited, not how likely exploitation is. Two public sources answer that second question. CISA's Known Exploited Vulnerabilities catalog lists CVEs with evidence of exploitation in the wild. FIRST's Exploit Prediction Scoring System estimates the probability that a CVE will be exploited in the next 30 days, updated daily with a percentile.

A workable order: KEV-listed CVEs first, on a deadline measured in days; then high EPSS scores on internet-facing components; then the rest by CVSS and exposure.

Reading a SelfSec finding

  • JS Library Fingerprint is passive. It fetches each discovered URL again, reads library names and versions from file names and banners, and matches them against a bundled catalog for jQuery, AngularJS, Bootstrap, Lodash, Moment.js, Handlebars, Prototype and Underscore. Each CVE becomes its own CWE-1035 finding with evidence such as jquery v1.8.3 is affected by CVE-2020-11022.
  • CVE Annotation Match sends nothing. It compares the versions reconnaissance recorded for WordPress, Drupal, Joomla, Magento, Sitefinity, Kendo UI and Telerik products with bundled advisory feeds, and reports each match with the advisory's own severity, CVSS score and CWE. A range includes its first affected version and excludes the fixed one.
  • CVE Native Probe tests specific Telerik and Sitefinity CVEs against the live response, such as a default dialog encryption key, a reachable RadAsyncUpload deserialization path or an exposed Report Server setup page. Apart from an informational note that ScriptResource.axd is live, its findings are critical or high and belong at the top of the queue.
  • Every finding that names a CVE shows its KEV status, EPSS score and percentile, fetched from CISA and FIRST during the scan, and an SSVC decision of Track, Track*, Attend or Act. SelfSec treats a KEV listing as active exploitation, an EPSS score of 0.1 or more as a public proof of concept, CVSS 7.0 or more as total technical impact and a network vector with low attack complexity as automatable, and assumes medium mission impact.

A version match is evidence, not proof. Vendors backport fixes to older branches, distributions patch packages without changing the upstream version, and a readme can lag the code. Check the vendor advisory before you close a finding or escalate it.

The fix: inventory, update, gate the build

Upgrading to a fixed release is the fix; the rest is about never losing track again. Install front-end libraries through the package manager rather than copying files, so the lockfile records the real version and the audit can see it. In npm, overrides lifts a vulnerable transitive dependency without waiting for its parent:

{
  "dependencies": {
    "jquery": "^3.7.1"
  },
  "overrides": {
    "lodash": "^4.18.1"
  }
}

Install with npm ci and run npm audit --audit-level=high in the pipeline; it exits non-zero on high and critical advisories.

NuGet audits packages during restore. In Directory.Build.props, cover transitive packages and break the build on high and critical advisories:

<Project>
  <PropertyGroup>
    <NuGetAudit>true</NuGetAudit>
    <NuGetAuditMode>all</NuGetAuditMode>
    <NuGetAuditLevel>low</NuGetAuditLevel>
    <WarningsAsErrors>$(WarningsAsErrors);NU1903;NU1904</WarningsAsErrors>
  </PropertyGroup>
</Project>

dotnet list package --vulnerable --include-transitive prints the same report on demand.

Commercial components need the vendor's channel. For Telerik UI for ASP.NET AJAX, upgrade to a current release, give each key a unique random value instead of relying on fallbacks, and disable the upload handler when no page uses it:

<appSettings>
  <add key="Telerik.AsyncUpload.ConfigurationEncryptionKey" value="[unique random value]" />
  <add key="Telerik.Upload.ConfigurationHashKey" value="[unique random value]" />
  <add key="Telerik.Web.UI.DialogParametersEncryptionKey" value="[unique random value]" />
  <add key="Telerik.Web.DisableAsyncUploadHandler" value="true" />
</appSettings>

On WordPress, wp core update and wp plugin update --all apply releases from WP-CLI. Delete plugins and themes nobody uses; a deactivated plugin's files stay on disk, reachable and fingerprintable.

Fixes that do not hold

  • Hiding version banners: file names, hashes and behavior still identify the release, and the vulnerable code still runs.
  • Upgrading the package but not the copy: a vendored file under /static, a pinned CDN URL or a second bundle keeps serving the old version.
  • Updating direct dependencies only: the vulnerable code often arrives transitively.
  • Rotating keys without upgrading: unique Telerik keys raise the bar, but older builds keep the flawed code.
  • A WAF rule as the end state: virtual patching buys time, but a new encoding or path reopens the hole.
  • Suppressing an advisory because "we never call that function" without checking every caller and every dependency that might.

A developer review checklist

  1. Inventory every component, including vendored files, CDN script URLs, CMS plugins and server products, and generate a CycloneDX or SPDX SBOM from the build.
  2. Install through package managers, commit lockfiles and build with locked restores.
  3. Audit dependencies on every build and fail on high and critical advisories.
  4. Let Dependabot or Renovate open update pull requests, and subscribe to vendor advisories for components no package manager tracks.
  5. Patch KEV-listed CVEs first, then high EPSS scores on internet-facing components.
  6. Remove unused libraries, plugins, themes and handlers.
  7. Rescan the deployed site: the audit reads your manifest, while attackers read what you serve.

References

Module: CVE Annotation Match, CVE Native Probe and JS Library Fingerprint. How it probes: JS Library Fingerprint is passive: each discovered URL is fetched again with a plain GET and its body searched for a library name followed by a version, in a file name such as jquery-1.8.3.min.js or a banner such as jQuery v1.8.3, for 12 libraries, eight of which have advisories in its bundled catalog. CVE Annotation Match sends nothing of its own; it reads the core, plugin, theme, module and extension versions that WordPress, Drupal, Joomla, Magento, Sitefinity, Kendo UI and Telerik reconnaissance recorded from generator tags and headers, readme, changelog and manifest files, asset version queries, CDN paths and script banners. Where reconnaissance marked Telerik UI for ASP.NET AJAX, Telerik Report Server or Sitefinity present, CVE Native Probe sends targeted requests: dialog parameters built from documented default keys, a RadAsyncUpload configuration carrying a random marker, a JSON body with a $type discriminator to the Sitefinity form services when the recorded version is below 14.2.7905 or unknown, and GET requests for the Report Server setup page, the Reporting REST paths, ScriptResource.axd and the RadUploadTemp folder. How it confirms: Version matches are correlation, not exploitation: the detected version must be at or above an advisory's first affected version and below its fixed version, an advisory with no fixed release matches every version from the first affected one, and each matching CVE becomes its own finding with the advisory's severity and CVSS score; a Magento module seen without a version is listed as informational. A native probe reports only when the live response carries the product's own signature: a DialogHandler error or dialog marker with no padding, MAC or cryptographic-operation error, a RadAsyncUpload type-loading or deserialization message, an HTTP 500 with a JSON deserialization error from Sitefinity, the Report Server administrator-creation form with HTTP 200, or a directory listing of RadUploadTemp; a reachable Reporting REST path is reported at lower confidence because no deserialization payload is sent, and a live ScriptResource.axd handler is only an informational note. Every finding that names a CVE is enriched from CISA's KEV feed and FIRST's EPSS API with its KEV status, its EPSS score and percentile, and an SSVC decision. A finding moves from Possible to Confirmed only after this check. Reported as CWE-1035, CWE-502, CWE-326, CWE-288, CWE-548, CWE-200 or the advisory's own CWE · ATT&CK T1190, T1059, T1213 or the techniques mapped from the advisory's CWE · SARIF 2.1.0.
How SelfSec proves this one
Module
CVE Annotation Match, CVE Native Probe and JS Library Fingerprint
How it probes
JS Library Fingerprint is passive: each discovered URL is fetched again with a plain GET and its body searched for a library name followed by a version, in a file name such as jquery-1.8.3.min.js or a banner such as jQuery v1.8.3, for 12 libraries, eight of which have advisories in its bundled catalog. CVE Annotation Match sends nothing of its own; it reads the core, plugin, theme, module and extension versions that WordPress, Drupal, Joomla, Magento, Sitefinity, Kendo UI and Telerik reconnaissance recorded from generator tags and headers, readme, changelog and manifest files, asset version queries, CDN paths and script banners. Where reconnaissance marked Telerik UI for ASP.NET AJAX, Telerik Report Server or Sitefinity present, CVE Native Probe sends targeted requests: dialog parameters built from documented default keys, a RadAsyncUpload configuration carrying a random marker, a JSON body with a $type discriminator to the Sitefinity form services when the recorded version is below 14.2.7905 or unknown, and GET requests for the Report Server setup page, the Reporting REST paths, ScriptResource.axd and the RadUploadTemp folder.
How it confirms
Version matches are correlation, not exploitation: the detected version must be at or above an advisory's first affected version and below its fixed version, an advisory with no fixed release matches every version from the first affected one, and each matching CVE becomes its own finding with the advisory's severity and CVSS score; a Magento module seen without a version is listed as informational. A native probe reports only when the live response carries the product's own signature: a DialogHandler error or dialog marker with no padding, MAC or cryptographic-operation error, a RadAsyncUpload type-loading or deserialization message, an HTTP 500 with a JSON deserialization error from Sitefinity, the Report Server administrator-creation form with HTTP 200, or a directory listing of RadUploadTemp; a reachable Reporting REST path is reported at lower confidence because no deserialization payload is sent, and a live ScriptResource.axd handler is only an informational note. Every finding that names a CVE is enriched from CISA's KEV feed and FIRST's EPSS API with its KEV status, its EPSS score and percentile, and an SSVC decision.
Reported as CWE-1035, CWE-502, CWE-326, CWE-288, CWE-548, CWE-200 or the advisory's own CWE · ATT&CK T1190, T1059, T1213 or the techniques mapped from the advisory's CWE · SARIF 2.1.0

A reflected response alone is not enough to promote a Vulnerable Components finding. The classical engine has to confirm the behavior before it reaches the report. See the detection engine

SelfSec scanner

Find Vulnerable Components in your own app

Reading about the class is one thing. SelfSec tests for it against an application you are authorized to assess, and only calls it real when independent evidence agrees.

Find it Confirmed, not guessed 21 vulnerability modules, with the classical engine owning the verdict. Prove it Evidence you can re-read Every finding keeps the raw request, the response and the baseline it was measured against.
Join the launch list