A comment field, username, article title, or URL parameter may seem like ordinary data. However, when that data is displayed back in the browser without proper protection, it can become a gateway for cross-site scripting or XSS.
XSS occurs when the browser treats data that should only be text as HTML or JavaScript. The impact is not always a change in page appearance. In certain conditions, an attacker can misuse a user's session, display fake forms, monitor interactions, or perform actions on behalf of the victim. OWASP warns that XSS is not a problem with a single solution; defenses must be tailored to where the data is used.
Common Mistake: Assuming All Data Is Safe
Many applications receive data through forms, URLs, cookies, APIs, or databases. Developers then feel secure because they have checked the input when the data comes in. However, data that passes validation still needs to be secured again when displayed.
Imagine someone fills in a name with plain text. Its value may be safe when stored in the database, but the context changes when that name is inserted into HTML attributes, URLs, or JavaScript code. The browser reads each context with different rules. Therefore, one escaping function does not fit all places.
Input validation answers the question: “Is this data compliant with the application rules?” Escaping answers the question: “How is this data displayed as data, not as code?” Sanitization is used when the application needs to accept some HTML, such as in an article editor or comments with specific formatting.
Main Principle: Secure Data When Displaying
For plain text within HTML, use output escaping. This process converts special characters like less than signs or quotes into forms that are not interpreted as markup.
In PHP, the basic function commonly used is htmlspecialchars(). For example:
<?php
$name = $_GET['name'] ?? '';
echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8');ENT_QUOTES helps convert both single and double quotes, while the UTF-8 declaration clarifies character handling. However, this function is suitable for HTML text contexts and simple attributes, not for all places.
Do not place user data directly into JavaScript blocks, CSS, event handlers like onclick, or URLs just because it has passed htmlspecialchars(). OWASP emphasizes that encoding must follow the target context. HTML encoding does not automatically make values safe for JavaScript or URLs.
Safe Patterns in WordPress
WordPress provides escaping functions that should be used when outputting data. The principle is simple: perform escaping as close as possible to when the data is printed, not just when the data is stored.
esc_html()for text displayed between HTML tags.esc_attr()for attribute values likeclass,title, orvalue.esc_url()for URLs used in attributes likehrefandsrc.wp_kses()orwp_kses_post()when the application needs to retain allowed HTML.
A safer example for titles and links:
<h2><?php echo esc_html( $title ); ?></h2>
<a href="<?php echo esc_url( $url ); ?>">
<?php echo esc_html( $label ); ?>
</a>Avoid patterns like echo $title; if the value comes from users, metadata, APIs, or other sources that you do not fully control. An internal database is also not a reason to assume data is always clean. Data can enter through imports, other plugins, hacked admin accounts, or legacy processes that have vulnerabilities.
Escaping Does Not Mean Removing All HTML
Another mistake is using a single approach for all content. If users are only allowed to enter names, use strict validation and display it as text. If users can write articles with bold formatting, links, and lists, removing all HTML will ruin the intended functionality.
In such situations, use sanitization with a list of allowed elements and attributes. In WordPress, wp_kses_post() can help retain a common subset of HTML used in content, but it is still necessary to consider application needs and security updates.
Sanitization is also not a one-time process. The sanitization library should be updated, and sanitized results should not be modified in ways that could reintroduce harmful content.
Also Check for XSS Occurring in the Browser
XSS does not always originate from PHP templates. JavaScript code in the browser can also create problems, especially when retrieving values from URLs, location.hash, or user input and inserting them into innerHTML.
To display plain text through JavaScript, it is safer to use textContent:
const message = new URLSearchParams(location.search).get('message') || '';
document.querySelector('#message').textContent = message;Be cautious with innerHTML, document.write(), eval(), and inline event handlers. If the application truly needs to render HTML from users, use a properly maintained and configured sanitizer, not just a simple removal of the <script> tag.
What You Can Do Now
- List all places the application displays user data: HTML pages, attributes, URLs, JavaScript, emails, and API responses.
- Pair escaping functions with their contexts, not just the source of the data.
- In WordPress, replace direct outputs like
echo $valuewith appropriate functions, such asesc_html()oresc_attr(). - Look for uses of
innerHTML,document.write(), and inline event handlers in JavaScript. - Add tests for characters like
<,>, quotes, and values resembling URLs or HTML. - Use Content Security Policy as an additional layer, not as a substitute for code fixes.
In essence, user data should not be viewed as an enemy and does not always need to be discarded. The key is to ensure that the browser understands the data as data. Validation restricts what can enter, sanitization cleans up HTML when HTML is indeed needed, while escaping ensures that data does not turn into code when displayed.
Sources & Further Reading
- OWASP Cross Site Scripting Prevention Cheat Sheet
- WordPress Developer Resources: Escaping Data
- PHP Manual: htmlspecialchars
– Rio Yotto @rioyotto
