Server Side Template Injection in Cassandra
How Server Side Template Injection Manifests in Cassandra
When developers need to build CQL queries dynamically they sometimes reach for a template engine (Apache Velocity, FreeMarker, Handlebars, etc.) to insert values such as IDs, names, or filter clauses. If user‑supplied data is placed directly into the template without proper escaping, the template engine may treat that data as a directive or expression. In a Cassandra‑backed service this can lead to Server Side Template Injection (SSTI): the attacker injects template syntax that gets evaluated before the resulting CQL string is sent to the database, potentially allowing arbitrary code execution, file read, or data exfiltration.
A realistic vulnerable pattern looks like this:
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import java.io.StringWriter;
public class VulnerableUserDao {
private final VelocityEngine ve = new VelocityEngine();
private final CqlSession session = CqlSession.builder().build();
public String getUserById(String userId) {
// ❌ userId is inserted straight into the template
String template = "SELECT * FROM users WHERE user_id = '$userId' ";
VelocityContext ctx = new VelocityContext();
ctx.put("userId", userId);
StringWriter writer = new StringWriter();
ve.evaluate(ctx, writer, "log", template);
String renderedCql = writer.toString();
SimpleStatement stmt = SimpleStatement.newInstance(renderedCql);
return session.execute(stmt).one().toString();
}
}
Because the Velocity engine evaluates the template, an attacker could supply a value like:
$userId = "'; #set($x = Runtime.getRuntime().exec('id')); #"
which would cause the template to execute arbitrary Java code before the CQL is formed. The resulting CQL might be malformed, but the damage (command execution, file read) has already happened. This maps to OWASP A03:2021 – Injection and has been seen in real‑world vulnerabilities such as CVE‑2020‑13942 (Apache Velocity SSTI).
Cassandra‑Specific Detection
Detecting SSTI in a Cassandra context requires looking for places where user input flows into a template engine before the resulting string is passed to the Cassandra driver. Manual code review should focus on:
- Any call to a template engine’s render/merge/evaluate method where the template string contains user‑controlled variables.
- Subsequent use of the rendered output as a CQL query passed to
session.execute()orsession.prepare(). - Lack of input validation or escaping of the user data before it reaches the template.
middleBrick helps automate this discovery. When you submit an API endpoint, middleBrick runs its 12 parallel security checks, including the Input Validation check that traces unauthenticated user‑controlled parameters through the application flow. If the scanner observes that a parameter reaches a template‑rendering sink and is later used in a Cassandra query without sanitisation, it will flag the finding with a severity rating and provide remediation guidance.
Example of using the middleBrick CLI to scan a service:
middlebrick scan https://api.example.com/users
The CLI returns a JSON report that includes a finding such as:
{
"id": "SSTI-001",
"name": "Server Side Template Injection via Velocity",
"severity": "critical",
"description": "User‑supplied userId parameter is inserted into a Velocity template before being executed as a CQL query.",
"remediation": "Use prepared statements with bound parameters; avoid building CQL strings with template engines."
}
This gives developers a concrete, actionable starting point for fixing the issue.
Cassandra‑Specific Remediation
The safest way to interact with Cassandra is to let the driver handle query construction via prepared statements and bound parameters. This eliminates the need for string concatenation or template engines altogether, removing the injection surface. If dynamic query building is unavoidable (e.g., optional filters), use the driver’s QueryBuilder or a whitelist‑based approach.
Fixed version of the earlier DAO using a prepared statement:
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.cql.*;
public class SafeUserDao {
private final CqlSession session = CqlSession.builder().build();
private final PreparedStatement getUserStmt;
public SafeUserDao() {
String cql = "SELECT * FROM users WHERE user_id = ?";
this.getUserStmt = session.prepare(cql);
}
public String getUserById(String userId) {
BoundStatement bound = getUserStmt.bind(userId);
Row row = session.execute(bound).one();
return row == null ? null : row.toString();
}
}
Key points:
- The query string contains only a placeholder (
?) – no user data is mixed into the SQL. - The driver serialises the
userIdvalue safely, preventing both traditional injection and any template‑engine side effects. - If you must use a template engine for other purposes (e.g., rendering HTML), ensure that any data that will later become part of a CQL query is never passed to the engine.
- Consider disabling Cassandra user‑defined functions (UDFs) if they are not required, as a successful SSTI could otherwise be chained to register a malicious UDF.
- Apply input validation (allow‑list of expected characters, length limits) as a defence‑in‑depth measure, but rely on prepared statements as the primary control.
By following this pattern you remove the SSTI vector while still gaining the performance benefits of Cassandra’s prepared statement caching.