Cross-Site Scripting (XSS) via Untrusted Input in innerHTML, outerHTML, document.write in Browser

High Risk Cross-Site Scripting
JavaScriptBrowserXSSDOMdocument.writeouterHTML

What it is

Using dangerous DOM methods like innerHTML, outerHTML, and document.write() with untrusted user input creates XSS vulnerabilities, allowing attackers to inject and execute malicious scripts in the browser context.

// Vulnerable patterns with document methods

// 1. document.write with user input
function renderWelcomePage() {
    const params = new URLSearchParams(window.location.search);
    const name = params.get('name');
    const theme = params.get('theme');
    
    document.write('<html><head>');
    if (theme) {
        // Vulnerable: theme parameter in document.write
        document.write('<style>' + theme + '</style>');
    }
    document.write('</head><body>');
    
    if (name) {
        // Vulnerable: name parameter in document.write  
        document.write('<h1>Welcome ' + name + '!</h1>');
    }
    document.write('</body></html>');
}

// 2. outerHTML with user data
function replaceContent(elementId, userContent) {
    const element = document.getElementById(elementId);
    
    // Vulnerable: outerHTML replacement
    element.outerHTML = '<div class="user-content">' + userContent + '</div>';
}

// 3. insertAdjacentHTML vulnerabilities
function addComment(comment) {
    const commentsContainer = document.getElementById('comments');
    
    // Vulnerable: insertAdjacentHTML with user data
    commentsContainer.insertAdjacentHTML('beforeend',
        '<div class="comment">' +
        '<strong>' + comment.author + '</strong>: ' +
        comment.text +
        '</div>'
    );
}

// 4. Dynamic script injection
function loadUserScript(scriptContent) {
    // Extremely vulnerable: Dynamic script creation
    document.write('<script>' + scriptContent + '</script>');
}

// 5. Form data processing
function displayFormSubmission() {
    const formData = new FormData(document.getElementById('userForm'));
    const message = formData.get('message');
    
    // Vulnerable: Form data in document.write
    document.write('<div class="confirmation">You submitted: ' + message + '</div>');
}
// Secure alternatives to vulnerable document methods

// 1. Safe page rendering without document.write
function renderWelcomePage() {
    const params = new URLSearchParams(window.location.search);
    const name = params.get('name');
    const theme = params.get('theme');
    
    // Safe theme application
    if (theme && isValidTheme(theme)) {
        applyTheme(theme);
    }
    
    // Safe name display
    if (name && isValidName(name)) {
        const welcomeHeader = document.createElement('h1');
        welcomeHeader.textContent = 'Welcome ' + name + '!';
        document.body.appendChild(welcomeHeader);
    }
}

function isValidTheme(theme) {
    const allowedThemes = ['light', 'dark', 'blue', 'green'];
    return allowedThemes.includes(theme);
}

function applyTheme(theme) {
    document.body.className = 'theme-' + theme;
}

function isValidName(name) {
    return typeof name === 'string' && 
           name.length > 0 && 
           name.length <= 50 &&
           /^[a-zA-Z\s'-]+$/.test(name);
}

// 2. Safe content replacement
function replaceContent(elementId, userContent) {
    const element = document.getElementById(elementId);
    if (!element) return;
    
    // Create new element safely
    const newElement = document.createElement('div');
    newElement.className = 'user-content';
    newElement.textContent = userContent; // Safe text assignment
    
    // Replace element safely
    element.parentNode.replaceChild(newElement, element);
    newElement.id = elementId; // Preserve ID if needed
}

// 3. Safe comment addition
function addComment(comment) {
    const commentsContainer = document.getElementById('comments');
    
    // Validate comment data
    if (!isValidComment(comment)) {
        console.error('Invalid comment data');
        return;
    }
    
    // Create comment element safely
    const commentDiv = document.createElement('div');
    commentDiv.className = 'comment';
    
    const authorSpan = document.createElement('strong');
    authorSpan.textContent = comment.author;
    
    const separator = document.createTextNode(': ');
    
    const textSpan = document.createElement('span');
    textSpan.textContent = comment.text;
    
    commentDiv.appendChild(authorSpan);
    commentDiv.appendChild(separator);
    commentDiv.appendChild(textSpan);
    
    commentsContainer.appendChild(commentDiv);
}

function isValidComment(comment) {
    return comment &&
           typeof comment.author === 'string' &&
           typeof comment.text === 'string' &&
           comment.author.length > 0 &&
           comment.author.length <= 50 &&
           comment.text.length > 0 &&
           comment.text.length <= 500;
}

// 4. No dynamic script injection - use safe alternatives
function loadUserConfiguration(configData) {
    try {
        // Parse configuration safely
        const config = JSON.parse(configData);
        
        // Apply configuration through safe methods
        applyUserConfiguration(config);
    } catch (error) {
        console.error('Invalid configuration data:', error);
    }
}

function applyUserConfiguration(config) {
    const allowedConfigs = ['theme', 'language', 'fontSize'];
    
    for (const [key, value] of Object.entries(config)) {
        if (allowedConfigs.includes(key)) {
            switch (key) {
                case 'theme':
                    if (isValidTheme(value)) {
                        applyTheme(value);
                    }
                    break;
                case 'language':
                    if (isValidLanguage(value)) {
                        setLanguage(value);
                    }
                    break;
                case 'fontSize':
                    if (isValidFontSize(value)) {
                        setFontSize(value);
                    }
                    break;
            }
        }
    }
}

// 5. Safe form data processing
function displayFormSubmission() {
    const formData = new FormData(document.getElementById('userForm'));
    const message = formData.get('message');
    
    // Validate message
    if (!isValidMessage(message)) {
        displayError('Invalid message format');
        return;
    }
    
    // Create confirmation safely
    const confirmationDiv = document.createElement('div');
    confirmationDiv.className = 'confirmation';
    
    const confirmText = document.createTextNode('You submitted: ');
    const messageSpan = document.createElement('span');
    messageSpan.className = 'user-message';
    messageSpan.textContent = message;
    
    confirmationDiv.appendChild(confirmText);
    confirmationDiv.appendChild(messageSpan);
    
    document.body.appendChild(confirmationDiv);
}

function isValidMessage(message) {
    return typeof message === 'string' &&
           message.trim().length > 0 &&
           message.length <= 1000;
}

function displayError(errorMessage) {
    const errorDiv = document.createElement('div');
    errorDiv.className = 'error';
    errorDiv.textContent = errorMessage;
    document.body.appendChild(errorDiv);
}

💡 Why This Fix Works

The vulnerable examples show dangerous document methods being used with user input. The secure versions use safe DOM APIs and proper validation to prevent XSS attacks.

Why it happens

Using document.write() to output user-controlled content directly into the HTML document stream.

Root causes

document.write() with User Input

Using document.write() to output user-controlled content directly into the HTML document stream.

Preview example – JAVASCRIPT
// User input from URL or form
const userName = new URLSearchParams(location.search).get('name');

// Vulnerable: document.write with user input
if (userName) {
    document.write('<h1>Welcome ' + userName + '!</h1>');
    // Attacker can use: ?name=<script>alert('XSS')</script>
}

outerHTML Assignment

Assigning user-controlled data to outerHTML property, which replaces the entire element with potentially malicious HTML.

Preview example – JAVASCRIPT
function updateElement(elementId, newContent) {
    const element = document.getElementById(elementId);
    
    // Vulnerable: outerHTML assignment
    element.outerHTML = '<div class="updated">' + newContent + '</div>';
    // newContent could contain: </div><script>alert('XSS')</script><div>
}

insertAdjacentHTML with User Data

Using insertAdjacentHTML method with user input to inject HTML content at specific positions.

Preview example – JAVASCRIPT
function addNotification(message) {
    const container = document.getElementById('notifications');
    
    // Vulnerable: insertAdjacentHTML with user input
    container.insertAdjacentHTML('beforeend', 
        '<div class="notification">' + message + '</div>'
    );
    // message could contain malicious HTML
}

Fixes

1

Replace document.write() with Safe DOM Methods

Avoid document.write() entirely and use createElement or textContent for safe content insertion.

2

Use Safe Element Replacement

Replace outerHTML assignments with safe element creation and replacement methods.

3

Safe Content Insertion with DOM APIs

Use appendChild, insertBefore, and other DOM methods instead of insertAdjacentHTML.

Detect This Vulnerability in Your Code

Sourcery automatically identifies cross-site scripting (xss) via untrusted input in innerhtml, outerhtml, document.write in browser and many other security issues in your codebase.