Weakness reference
CWE-87

Improper Neutralization of Alternate XSS Syntax

This weakness occurs when a developer tries to prevent cross-site scripting XSS attacks by blocking obvious patterns like <script> tags, but fails to account…

01Summary

This weakness occurs when a developer tries to prevent cross-site scripting (XSS) attacks by blocking obvious patterns like <script> tags, but fails to account for the many alternative ways browsers can execute JavaScript. An attacker can bypass these incomplete filters using HTML entities, Unicode escapes, case variations, or event handlers, allowing malicious scripts to run anyway. The result is a false sense of security that leaves the application vulnerable.

02How It Happens

Developers often implement XSS defenses by searching for and removing or escaping specific dangerous strings — most commonly <script> tags. However, browsers are forgiving and interpret HTML in many ways. A filter that only blocks <script> will miss <ScRiPt> (case variation), <script%20> (whitespace injection), &#60;script&#62; (HTML entity encoding), or event handlers like <img onerror="...">. The core problem is blacklist-based filtering
: trying to enumerate all possible attack vectors is inherently incomplete. Browsers continue to evolve, and new bypass techniques emerge regularly.

The weakness is compounded when the filter is applied inconsistently — for example, sanitizing user input on display but not on storage, or filtering one input field but not another. Even a single unfiltered entry point can undo all the protection elsewhere.

03Real-World Impact

An attacker who can inject alternate XSS syntax can steal session cookies, redirect users to phishing sites, deface page content, or perform actions on behalf of logged-in users. Because the filter appears to be in place, site owners and security reviewers may not discover the vulnerability until it is actively exploited. The damage is often silent — malicious scripts can exfiltrate data without obvious signs.

04Vulnerable & Fixed Patterns

Vulnerable pattern
def sanitize_user_input(user_text):
    # Attempt to block <script> tags
    if "<script>" in user_text.lower():
        return user_text.replace("<script>", "")
    return user_text

# Later, in a template or response
user_comment = request.args.get('comment')
safe_comment = sanitize_user_input(user_comment)
return render_template('page.html', comment=safe_comment)

Why it's vulnerable:
The filter only checks for the exact string <script> in lowercase, missing case variations (<ScRiPt>), HTML entities (&#60;script&#62;), and event handlers (<img onerror="alert(1)">). The sanitized output is then rendered directly into HTML without further encoding.

Fixed pattern
from markupsafe import escape

def display_user_input(user_text):
    # HTML-encode all special characters
    return escape(user_text)

# In a template or response
user_comment = request.args.get('comment')
safe_comment = display_user_input(user_comment)
return render_template('page.html', comment=safe_comment)
Vulnerable pattern
<?php
function remove_script_tags($input) {
    // Attempt to block <script> tags
    return str_replace("<script>", "", $input);
}

$user_comment = $_GET['comment'];
$safe = remove_script_tags($user_comment);
echo "<p>" . $safe . "</p>";
?>

Why it's vulnerable:
The filter only removes the exact string <script>, allowing bypasses like <SCRIPT>, <script >, or event handlers like <svg onload="...">. The output is echoed directly into HTML without encoding.

Fixed pattern
<?php
$user_comment = $_GET['comment'];
// HTML-encode all special characters
$safe = htmlspecialchars($user_comment, ENT_QUOTES, 'UTF-8');
echo "<p>" . $safe . "</p>";
?>

05Prevention Checklist

Use output encoding, not input filtering.
Encode all user-controlled data based on context (HTML, JavaScript, URL, CSS) using established libraries — never try to blacklist dangerous strings.
Apply a whitelist approach if filtering is necessary.
Define what *is* allowed (e.g., <b>, <i>, <a href="...">) rather than what is forbidden; use a library like HTML Purifier or DOMPurify.
Encode at the point of output.
Sanitize on storage if you must, but always encode again when rendering — defense in depth prevents a single bypass from compromising the whole system.
Use Content Security Policy (CSP).
Deploy a strict CSP header to restrict script execution to trusted sources, mitigating the impact of any XSS that slips through.
Test with alternate encodings.
Include case variations, HTML entities, Unicode escapes, and event handlers in your security test cases.
Keep dependencies updated.
If using a sanitization library, ensure it is regularly patched as new bypass techniques are discovered.

06Signs You May Already Be Affected

Review your application's input handling code for any custom filters that search for specific strings like <script>, onclick, or onerror. Check your logs for requests containing unusual characters, HTML entities, or mixed-case tags — these may indicate attempted bypasses. If you find user-supplied content rendered in HTML without encoding, or if your sanitization logic is more than a few lines long, the application is likely at risk.

07Related Recent Vulnerabilities