Is your site running a version of jQuery with a known XSS hole?
Open the console on your own site and type jQuery.fn.jquery. It answers with a version string. Now go look up whether anyone has published an exploit for that exact number.
For most sites running jQuery, the answer is yes. It's one of the most studied libraries on the web, so when a hole is found it gets a CVE number, a writeup, and usually a proof-of-concept anyone can copy. The fix ships in a new release. But your page is still serving the old one, because you added jQuery in 2019 for a plugin or a tutorial or a theme, it worked, and you moved on. It has been sitting in your <head> ever since, doing its job, asking nothing of you, and an old version doesn't quietly patch itself.
The hole you probably have
The big one is CVE-2020-11022 and its twin CVE-2020-11023, both fixed in jQuery 3.5.0. Anything below that line is affected, and a lot of the web is below that line.
The mechanism is specific. jQuery has methods that take a chunk of HTML and put it into the page: .html(), .append(), .prepend(), .before(), .after(). Before 3.5.0, the routine that cleaned that HTML (jQuery.htmlPrefilter) handled certain self-closing tags wrong. A crafted string that looks inert gets rewritten on the way in, and an onload or onerror handler that was supposed to be stripped survives into the DOM and runs. That's cross-site scripting. Not theoretical. The exact payloads are in the public advisory.
So the question is whether any code on your site passes content into one of those methods where part of that content can come from a URL, a query string, a stored comment, a webhook, or anything a stranger can influence. If it does, that stranger can run JavaScript in your visitors' browsers as your site.
What that buys an attacker
Script that runs as your origin runs with your origin's trust. That's the whole point of the same-origin model, and it's why XSS is rated the way it is.
Concretely, a successful payload on your page can:
- Read cookies that aren't marked
HttpOnlyand ship them to an attacker's server. If your session cookie isn'tHttpOnly, that's account takeover. (Worth checking separately: a session cookie without HttpOnly or Secure is its own finding.) - Rewrite what the user sees: swap the login form's action to a phishing endpoint, change the bank details on an invoice, inject a fake "your session expired" prompt.
- Make authenticated requests as the logged-in user, because the browser attaches their cookies automatically.
The visitor sees your domain in the address bar, your TLS padlock, your design. Everything says they're safe. The malicious code is wearing your site's face.
Where the old version comes from
Almost never on purpose. It arrives one of a few ways, and they're all easy to miss.
A WordPress theme or plugin bundles its own copy of jQuery and pins an old version. A <script src> points at a CDN URL with the version baked into the path, like code.jquery.com/jquery-1.12.4.min.js, and nobody ever bumped the number. A build pipeline locked the version in a lockfile years ago and it has been faithfully reinstalled ever since. The library does exactly what it was told. The problem is what it was told in 2019.
You can read the version yourself in a few seconds. Open your site, open the browser console, and run:
jQuery.fn.jquery
// "1.12.4"
If that string is below 3.5.0, you're exposed to the prefilter XSS. If jQuery is undefined, check the page source for a jquery script tag anyway, since a plugin may load it lazily. And if you find more than one copy at different versions, which happens more than you'd think, the oldest one is the one that decides your risk.
How to fix it
Update the version. That's genuinely the fix, and it's usually one line.
If the tag points at a CDN, change the version in the URL and add integrity protection while you're there:
<script src="https://code.jquery.com/jquery-1.12.4.min.js"></script>
If it's bundled by a package manager, bump it in package.json, reinstall, and rebuild. If a theme or plugin pins the old copy, update the theme or plugin, since you usually can't override its bundled version safely. After you ship, re-read jQuery.fn.jquery to confirm the new number is live and the old file isn't still cached somewhere.
The reason that integrity attribute matters is its own subject. If you're loading jQuery or anything else from a CDN, read why a third-party script needs Subresource Integrity next.
The version string is right there in the JavaScript you serve, which is exactly why this is one of the easiest things to check without ever logging in. SurfaceCheckr fetches your pages the way a browser does, pulls the jQuery version out of the bundle, and matches it against the known CVE line, then tells you the number and where on the page it loads from. Whether a specific .html() call on an authenticated route is actually reachable, we can't say, because we read from the outside and don't sign in. That reachability question stays with you. The part most people never check, that you're shipping a 2016 build with a public exploit attached to it, takes us a couple of minutes from the curb.
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.