VNX-JAVA-003 – SQL Injection via String Concatenation
Overview
This rule detects Java database access code — using raw JDBC (Statement.executeQuery, Statement.executeUpdate, createStatement()), Spring JdbcTemplate, or JPA EntityManager — where a SQL query string is assembled by concatenating user-supplied values with the + operator rather than using parameterized placeholders. This is SQL injection (CWE-89), the most exploited web vulnerability class for over two decades.
Severity: Critical | CWE: CWE-89 – Improper Neutralization of Special Elements used in an SQL Command
Why This Matters
SQL injection gives an attacker direct access to the database. Depending on the database user’s privileges and the database engine, the attacker can bypass authentication by supplying ' OR '1'='1, dump every row from every table, modify or delete data, call stored procedures that read local files (LOAD_FILE, OPENROWSET), or in some configurations execute OS commands (xp_cmdshell in SQL Server, COPY TO/FROM PROGRAM in PostgreSQL).
Automated exploitation tools such as SQLMap can fully compromise a vulnerable application in minutes once an injectable parameter is identified. The consequences span data breaches, regulatory fines under GDPR/HIPAA/PCI DSS, and complete application compromise.
What Gets Flagged
The rule matches .java files containing createStatement(), executeQuery(", or executeUpdate(" — all of which imply a literal SQL string or a string assembled at runtime — as well as JdbcTemplate or EntityManager query calls where the first argument ends with a + concatenation.
// FLAGGED: user input concatenated into a raw Statement
String username = request.getParameter("username");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name='" + username + "'");
// FLAGGED: JdbcTemplate with concatenated WHERE clause
String role = request.getParameter("role");
jdbcTemplate.query("SELECT * FROM accounts WHERE role='" + role + "'", rowMapper);
// FLAGGED: JPA EntityManager with concatenated filter
String id = request.getParameter("id");
entityManager.execute("DELETE FROM sessions WHERE token='" + id + "'");
Remediation
Use
PreparedStatementwith?placeholders for all JDBC code. The JDBC driver serializes parameter values correctly, escaping any SQL metacharacters. The query structure is fixed at compile time and cannot be changed by user input.// SAFE: PreparedStatement with positional parameters String username = request.getParameter("username"); String sql = "SELECT id, email FROM users WHERE name = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { // process row } } }Use named parameters with Spring
NamedParameterJdbcTemplate. This is the Spring-idiomatic replacement forJdbcTemplatewhen queries have multiple parameters, as it avoids positional confusion.// SAFE: named parameters with NamedParameterJdbcTemplate String sql = "SELECT * FROM accounts WHERE role = :role AND active = :active"; MapSqlParameterSource params = new MapSqlParameterSource() .addValue("role", request.getParameter("role")) .addValue("active", true); List<Account> accounts = namedJdbcTemplate.query(sql, params, accountRowMapper);Use JPA Criteria API or JPQL named parameters. Never concatenate values into a JPQL string; use
setParameterinstead.// SAFE: JPQL with named parameter TypedQuery<User> query = entityManager.createQuery( "SELECT u FROM User u WHERE u.email = :email", User.class); query.setParameter("email", request.getParameter("email")); List<User> users = query.getResultList();Use Spring Data JPA repositories. Repository methods derived from method names (
findByEmailAndActive) or annotated with@Queryand:paramsyntax are parameterized by default.Apply least-privilege database accounts. The database user used by the application should not have
DROP,CREATE, orFILEprivileges. Separate read-only and read-write users where possible.Add input validation as defence-in-depth. Validate the type, length, and format of all inputs before they reach the data layer. This does not replace parameterized queries but limits the blast radius of any future injection surface.
References
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- CAPEC-66: SQL Injection
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Java Security Cheat Sheet
- Spring JDBC – NamedParameterJdbcTemplate
- OWASP ASVS V5 – Validation, Sanitization and Encoding
- MITRE ATT&CK T1190 – Exploit Public-Facing Application