Web security

XSS (cross-site scripting)

Definition

XSS (cross-site scripting) is a vulnerability that lets an attacker run their own JavaScript in the page another visitor is viewing. The script then acts in the victim's browser with the site's privileges: performing actions on their behalf, reading the data on screen or redirecting them.

XSS (cross-site scripting), Web security

XSS stands for cross-site scripting; the X is used instead of a C to avoid confusion with CSS style sheets. It is an injection flaw: the site inserts data from the outside into its page without neutralizing it, and the browser interprets that data as code. MITRE catalogs it as CWE-79. Because the script appears to come from the site itself, the browser gives it the same trust as the rest of the page.

How an XSS attack works

It all starts with a value the attacker controls: a URL parameter, a form field, a comment, an HTTP header saved in visitor statistics. If the code outputs that value raw, a string like <script>…</script> or an onerror="…" attribute becomes executable. The script can then read the page content, send requests on behalf of the logged-in user, change what they see or redirect them to another site.

Five-step diagram of a stored XSS: the attacker injects a script tag into a field, the site saves it, an administrator opens the screen that displays it, the page returns the script without escaping and the script runs in the administrator's session
A stored XSS waits for its victim: all it takes is an administrator viewing the booby-trapped data.

On a WordPress site, the prime target is the administrator. A script running in their session can create an account, install a plugin or change a setting without ever knowing their password: the browser itself sends the login cookies with every request.

The different types of XSS

  • Stored (persistent) XSS: the script is saved on the server, in the database for example, then served again every time the data is displayed. The victim doesn't have to do anything special, just open the page.
  • Reflected XSS: the script travels in the request, often a booby-trapped link, and the server immediately copies it into its response. The victim has to click.
  • DOM-based XSS: the page's own JavaScript inserts untrusted data, such as the fragment of a URL. The server may never see it go by.

OWASP also describes “blind” XSS: a stored variant that fires later, in an interface the attacker cannot see. That is exactly the case for the logs, statistics and form submissions you review from the Dashboard. The real-world XSS cases in popular plugins show this scenario is anything but theoretical.

Video: “Cross-Site Scripting (XSS) Explained” (PwnFunction)

For WordPress: escape late

The WordPress developer documentation sets a simple rule: escape as late as possible, at the moment the data is output, with the function that matches the context.

Output contextFunction
Text inside an HTML elementesc_html()
Attribute valueesc_attr()
URL in href or srcesc_url()
Inline JavaScriptesc_js()
Textarea contentesc_textarea()
HTML where some tags are allowedwp_kses(), wp_kses_post()
<h2><?php echo esc_html( $title ); ?></h2>
<a href="<?php echo esc_url( $link ); ?>" title="<?php echo esc_attr( $info ); ?>">View</a>

Validating and sanitizing input is still useful, but it does not replace output escaping: the same value can be displayed in several contexts.

How to protect your site without touching code

  • Update plugins and themes quickly: XSS flaws exploited at scale usually target versions that have already been patched.
  • Keep few privileged accounts and delete the ones nobody uses anymore.
  • Watch for new administrator accounts, a common sign of an exploited XSS.
  • Add a Content Security Policy, keeping in mind that a strict version, which bans inline scripts, is hard to maintain with WordPress. Our guide to HTTP security headers for WordPress covers the options.
  • A web application firewall can stop known payloads, with no guarantee against a new variant.

WP Masquerade Pro sends security headers, including an optional basic CSP. That CSP does not restrict scripts, so it does not block an XSS. WP Masquerade is neither a web application firewall nor a code fixer: only updating the faulty component closes the hole.

Common mistakes

  • Confusing XSS with CSRF: the latter makes the browser send a forged request without running any script in the page. An XSS, on the other hand, can bypass anti-CSRF protections, since the script reads the tokens in the page.
  • Assuming data is “internal” because it shows up in the admin area: an HTTP header or a visited URL comes from the outside.
  • Using esc_html() in an attribute or esc_attr() for a URL: each context has its own function.
  • Thinking that protection against SQL injection is enough: data stored safely in the database can still be dangerous when displayed.

Further reading

  • Article How to set up HTTP security headers on WordPress: CSP, HSTS and the rest
  • Page WordPress security headers

Sources

  1. OWASP: Cross Site Scripting (XSS) (external link, opens in a new tab)
  2. MITRE CWE-79: Improper Neutralization of Input During Web Page Generation (Cross-site Scripting) (external link, opens in a new tab)
  3. WordPress Developer Resources: Escaping Data (external link, opens in a new tab)

All definitions

WordPress security, without the noise

One e-mail when something matters: new attacks seen in the wild, plugin updates and practical guides. No spam, unsubscribe in one click.

Start free, go Pro when you’re ready

The free version already hides WordPress and moves the login page. Pro adds aliases, two-factor authentication and monitoring.