How to fix XSS attack vectors like javascript: URLs and data: script sources?

xss-attack-vectors-present global

The issue

Two routes let injected code run even on pages without inline scripts: javascript: URLs, and a policy that allows scripts from data: URIs. A data: script carries its whole payload in the URL, so no attacker server is needed, and a javascript: URL runs when a link is followed or a form submitted. Either one undoes much of what a strict policy is meant to do.

The cause is markup or URL handling that accepts a javascript: URL, and a script-src that lists data:, often added to make something else work.

Scope

javascript: URLs can appear in any attribute that takes a URL, on any page, and most often come from user-supplied links. data: in script-src is set in the shared policy and applies wherever it is sent.

How to check you have it

  • In probeo: a policy that lets scripts load from data: is reported as csp:script-src-data, graded major. javascript: URLs in markup aren’t part of probeo’s header check.
  • By hand: search page source for javascript: in href, src, action and formaction. Run curl -sI https://your-site.example/ | grep -i content-security-policy and look for data: in script-src.
  • With a tool: Google’s CSP Evaluator flags data: as a script source.

The fix

Remove data: from script-src and stop accepting javascript: URLs.

Accept only http:, https: and any schemes the application genuinely needs in user-supplied URLs. Replace href="javascript:void(0)" with a <button>, or with preventDefault() in a listener. Keep data: only in directives where it can’t run code, such as img-src or font-src. A script-src without 'unsafe-inline' also blocks javascript: URLs, since the browser treats them as inline script.

Done when: no attribute holds a javascript: URL, script-src doesn’t allow data:, and the check above comes back clean.

Do not

  • Do not allow javascript: URLs, even for void(0).
  • Do not put data: in script-src, object-src or worker-src.
  • Do not rely on the policy alone without URL validation and output encoding.
  • Do not assume removing inline scripts closes these routes.

FAQ

Why doesn’t CSP script-src block javascript: URLs automatically?

It does, when script-src doesn’t allow 'unsafe-inline': the browser treats a javascript: URL as inline script. Policies that keep 'unsafe-inline' let them run, which is why URL validation still matters.

How does data: in script-src differ from data: in img-src?

In img-src it allows images encoded into the URL, which can’t run code. In script-src it allows a script encoded into the URL, which runs like any other.

Is javascript:void(0) in href attributes a real security risk?

Not in itself. The risk is that markup or a sanitizer that accepts javascript: for void(0) accepts it for anything. Banning the scheme is simpler than judging each value.

What CSP directives address these vectors specifically?

script-src without data: and without 'unsafe-inline' covers both. object-src 'none' and base-uri 'none' close two neighboring routes.

Can input validation alone prevent these XSS vectors?

It has to be right on every path that accepts a URL, and one miss is enough. The policy is the second layer that holds when validation fails.

How do these vectors interact with the existing inline scripts and unsafe-inline issues?

Closely. javascript: URLs are blocked by the same rule that blocks inline scripts, so removing 'unsafe-inline' closes both. data: in script-src is separate and has to be removed on its own.

Which product detects this

Probeo checks this on every page of your site as part of Site Clarity. This page explains one instance; the product tells you every page that has it.