Back to Blog
low SEVERITY7 min read

SQL Injection via String Formatting: How Parameterized Queries Save the Day

A database query in DBeaver's Altibase extension was constructing SQL statements using `String.format()` with user-controlled input, creating a classic SQL injection vulnerability. The fix replaces the unsafe string interpolation with parameterized queries using `PreparedStatement`, ensuring user input is always treated as data rather than executable SQL. This type of vulnerability is deceptively simple to introduce but equally simple to fix once you know what to look for.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 28, 2026•Reviewed June 3, 2026

Answer Summary

This is a SQL injection vulnerability (CWE-89) in DBeaver's Altibase extension, written in Java. The root cause is the use of `String.format()` to interpolate user-controlled input directly into SQL query strings. The fix replaces these unsafe string-formatted queries with JDBC `PreparedStatement` parameterized queries, which ensure user input is always treated as literal data and never as executable SQL syntax.

Vulnerability at a Glance

cweCWE-89
fixReplaced String.format() SQL construction with JDBC PreparedStatement parameterized queries
riskAttacker-controlled input could manipulate SQL query logic, potentially exposing or corrupting database contents
languageJava
root causeSQL queries constructed with String.format() incorporating user-supplied values without sanitization
vulnerabilitySQL Injection via String Formatting

Introduction

SQL injection has topped security vulnerability charts for decades — and for good reason. It's one of those vulnerabilities that's embarrassingly easy to introduce and potentially catastrophic to leave unpatched. Despite being well-understood, it continues to appear in codebases everywhere, from hobby projects to enterprise software.

This post examines a real SQL injection vulnerability discovered and patched in DBeaver, a popular open-source database management tool. The vulnerability lived in the Altibase database extension and involved a seemingly harmless use of String.format() to build a SQL query. Let's dig in.


The Vulnerability Explained

What Went Wrong?

The vulnerable code was located in AltibaseMetaModel.java, inside a method called getDepDDLFromDbmsMetadata. This method retrieves dependent DDL (Data Definition Language) statements from the Altibase database engine using the DBMS_METADATA package.

Here's the problematic code:

// VULNERABLE CODE
String getDepDdlQry = "SELECT dbms_metadata.get_dependent_ddl('%s', '%s', '%s') FROM DUAL";

Statement stmt = conn.createStatement();
rs = stmt.executeQuery(
    String.format(getDepDdlQry, depObjectType, sourceObject.getName(), schemaName)
);

At first glance, this looks like a straightforward query. But look closely: three user-influenced values — depObjectType, sourceObject.getName(), and schemaName — are being interpolated directly into the SQL string using String.format().

This means the final SQL string is constructed before it ever reaches the database. If any of those values contain SQL metacharacters, they become part of the query's logic, not just its data.

How Could It Be Exploited?

Imagine a schema name or object name that contains a single quote or a SQL comment sequence. An attacker (or even a maliciously named database object) could craft a value like:

schemaName = "PUBLIC', 'MALICIOUS_TYPE') -- "

The resulting query would become:

SELECT dbms_metadata.get_dependent_ddl('SOME_TYPE', 'SOME_OBJECT', 'PUBLIC', 'MALICIOUS_TYPE') -- ') FROM DUAL

The comment sequence (--) causes the rest of the original query to be ignored. Depending on the database's stored procedures and permissions, this could be leveraged to:

  • Exfiltrate sensitive metadata from the database catalog
  • Cause unexpected procedure calls with attacker-controlled arguments
  • Bypass access controls enforced at the application layer
  • Trigger errors that leak internal database structure information

Real-World Impact

In the context of a database management tool like DBeaver, this is particularly sensitive. The tool often connects to databases with elevated privileges — DBA accounts, schema owners, or administrative users. A successful injection in this context could have a blast radius far larger than the same vulnerability in a typical web application.

Additionally, because this code path is triggered when browsing database objects, the attack surface includes any scenario where an attacker can influence object names — for example, in shared database environments or when opening a database file provided by an untrusted source.

Severity Note: While this vulnerability is rated low severity (due to the specific conditions required to exploit it), SQL injection vulnerabilities should never be dismissed. The "low" rating reflects exploitability constraints, not the potential damage if those constraints are met.


The Fix

What Changed?

The fix is clean, minimal, and follows industry best practices. Here's the patched code:

// FIXED CODE
String getDepDdlQry = "SELECT dbms_metadata.get_dependent_ddl(?, ?, ?) FROM DUAL";

PreparedStatement pstmt = conn.prepareStatement(getDepDdlQry);
pstmt.setString(1, depObjectType);
pstmt.setString(2, sourceObject.getName());
pstmt.setString(3, schemaName);
rs = pstmt.executeQuery();

Before vs. After

Aspect Before (Vulnerable) After (Fixed)
Query construction String.format() with %s placeholders SQL ? parameter placeholders
Statement type Statement PreparedStatement
Input handling Interpolated into SQL string Passed as typed parameters
Injection risk ✅ Present ❌ Eliminated

Why Does This Fix Work?

Parameterized queries (also called prepared statements) work by separating the SQL code from the data. When you write:

String getDepDdlQry = "SELECT dbms_metadata.get_dependent_ddl(?, ?, ?) FROM DUAL";
PreparedStatement pstmt = conn.prepareStatement(getDepDdlQry);

The SQL structure is sent to the database first, and the database parses and compiles it as a query template. The ? placeholders are slots for data — nothing more. When you subsequently call:

pstmt.setString(1, depObjectType);

The JDBC driver ensures the value is properly escaped and transmitted as a string literal, not as SQL syntax. No matter what depObjectType contains — single quotes, semicolons, comment sequences — it will always be treated as a string value, never as SQL code.

This is fundamentally different from string formatting, where the database sees a single pre-built string and has no way to distinguish between "code the developer wrote" and "data the user provided."


Key Takeaways

Let's recap what we learned from this vulnerability and its fix:

  1. String.format() and SQL don't mix. Whenever you see SQL being built with string interpolation, treat it as a red flag.

  2. PreparedStatement is the right tool. In Java, there's almost never a good reason to use a raw Statement when executing queries with external data.

  3. "Internal" code paths need security review too. This vulnerability wasn't in a login form or a search box — it was in a metadata retrieval utility. Security doesn't stop at the "user-facing" boundary.

  4. Automated tools catch real bugs. This fix was identified through automated static analysis, demonstrating the value of integrating security scanning into your development workflow.

  5. The fix was simple. Ten lines of code changed, zero functionality lost, one vulnerability eliminated. Security improvements don't always require architectural overhauls — sometimes they're just about using the right API.


Conclusion

SQL injection is a vulnerability with a simple root cause and an equally simple cure: never trust string concatenation or formatting for SQL construction; always use parameterized queries. The vulnerability fixed here in DBeaver's Altibase extension is a textbook example of how easy it is to make this mistake, and how straightforward it is to correct.

As developers, we owe it to our users and our systems to treat every database query as a potential attack surface. The good news is that the tools to write safe SQL have existed for decades — we just need to use them consistently.

If you're working with JDBC in Java, make PreparedStatement your default. If you're using another language or framework, learn its equivalent parameterization mechanism. Your future self — and your security team — will thank you.


Found a vulnerability in your codebase? Automated security scanning can help identify issues like this one before they reach production. Consider integrating tools like Semgrep, SonarQube, or GitHub's CodeQL into your CI/CD pipeline.

Prevention and further reading

Related Articles

critical

Go ArticleRemove() SQL Injection via fmt.Sprintf IN Clause

The Go backend's `ArticleRemove` function built a SQL `DELETE` statement by joining a caller-supplied slice of article IDs directly into the query string with `fmt.Sprintf`. Because the `aids` values came straight from the frontend with no validation or escaping, an attacker could inject arbitrary SQL into the `IN(...)` clause. The fix replaces string concatenation with a parameterized query using placeholders and a `DB.Exec` argument list.

critical

JdbcSinkFunction.invoke() SQL Injection via Unvalidated Identifiers

The `JdbcSinkFunction.invoke()` method in Lacus's RTC engine built SQL INSERT statements with `String.format()`, placing database name, table name, and column names directly into the query string without validation. Although the actual row values used parameterized `?` placeholders, the identifiers themselves were injectable, letting a crafted sink configuration break out of the intended INSERT statement. The fix adds a strict allowlist regex that rejects any identifier containing characters out

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

critical

Toolforge Database Creation SQL Injection via Unicode Backtick Bypass

A critical SQL injection vulnerability in the database creation routine of a Node.js wiki library allowed attackers to inject arbitrary SQL by exploiting a weak validation check. The fix replaces a character-based block with a strict allowlist pattern, ensuring only valid database identifiers reach the SQL engine.

critical

heatmap.php SQL Injection: $_REQUEST Parameters in Unparameterized

A critical SQL injection vulnerability in the heatmap data retrieval endpoint allowed attackers to execute arbitrary database commands by manipulating coordinate bounds or time range parameters. The vulnerability affected all six user-controlled $_REQUEST parameters passed directly into query construction without parameterization.

critical

Actual Budget addTransaction.sh SQL Injection via Shell Variable

A critical SQL injection vulnerability in Actual Budget's transaction automation script allowed attackers to manipulate database records through shell variables interpolated directly into SQL strings. The fix introduces proper escaping functions and numeric validation to prevent injection through unquoted fields.