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 orbisai0security
May 28, 2026

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

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 valuesdepObjectType, 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."


Prevention & Best Practices

1. Always Use Parameterized Queries

This is the golden rule. Whenever you're incorporating external data into a SQL query, use parameterized queries or prepared statements. This applies to:

  • User input from forms or APIs
  • Values read from files or external systems
  • Data retrieved from other database queries
  • Object names, schema names, and identifiers passed between systems
// ❌ Never do this
String query = "SELECT * FROM " + tableName + " WHERE id = " + userId;

// ✅ Always do this
String query = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(query);
pstmt.setInt(1, userId);

2. Be Extra Careful with Metadata Queries

Queries involving database metadata (schema names, object names, DDL operations) are often overlooked in security reviews because they feel "internal." But as this vulnerability demonstrates, these code paths can be just as dangerous as user-facing query builders.

3. Use an ORM or Query Builder

Object-Relational Mappers (ORMs) like Hibernate, jOOQ, or Spring Data JPA handle parameterization automatically in most cases. They reduce the surface area for injection vulnerabilities by abstracting raw SQL construction.

// jOOQ example - parameterization handled automatically
Result<Record> result = create
    .select()
    .from(TABLE)
    .where(SCHEMA_NAME.eq(schemaName))
    .fetch();

4. Conduct Regular Code Reviews with Security Focus

SQL injection vulnerabilities often slip through because they look correct. Establishing a checklist for code reviews that specifically calls out:

  • Any use of String.format(), StringBuilder, or + concatenation in SQL contexts
  • Any use of Statement instead of PreparedStatement
  • Any query construction involving external or user-influenced data

5. Use Static Analysis Tools

Tools like Semgrep, SonarQube, SpotBugs with FindSecBugs, and Checkmarx can automatically detect SQL injection patterns during CI/CD pipelines. The rule that caught this vulnerability (insecure-document-method) is an example of exactly this kind of automated detection.

# Example Semgrep rule concept for Java SQL injection
rules:
  - id: java-sql-injection-string-format
    pattern: |
      String $QUERY = String.format($TEMPLATE, ...);
      $STMT.executeQuery($QUERY);
    message: Potential SQL injection via string formatting
    severity: WARNING

6. Follow Security Standards

This vulnerability maps to well-established security standards:

  • OWASP Top 10 (2021): A03 - Injection — SQL injection remains one of the most critical web application security risks
  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command — The canonical classification for SQL injection
  • SANS Top 25 — SQL injection consistently appears on this list of most dangerous software errors

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #39705

Related Articles

high

SQL Injection in OceanBase Connector: How f-Strings Can Sink Your RAG Platform

A critical SQL injection vulnerability was discovered and patched in the OceanBase database connector used by a RAG (Retrieval-Augmented Generation) platform, where user-controlled filter expressions were directly embedded into SQL WHERE clauses using Python f-strings without any parameterization or validation. This flaw exposed the platform's entire knowledge base to complete compromise, including unauthorized data access, modification, and deletion. The fix replaces unsafe string interpolation

low

When innerHTML Meets User Data: Fixing XSS Vulnerabilities in JavaScript

A low-severity Cross-Site Scripting (XSS) vulnerability was identified in `agent_chat.js`, where user-controlled data was being passed directly into DOM manipulation methods like `innerHTML`. While rated low severity, XSS vulnerabilities can be chained with other attacks to steal session tokens, redirect users, or execute arbitrary scripts in a victim's browser. The fix eliminates the unsafe pattern by replacing direct HTML injection with safer DOM manipulation techniques.

low

From text/template to html/template: Closing the XSS Door in Go

A cross-site scripting (XSS) vulnerability was discovered and patched in a Go-based application where the `text/template` package was being used instead of the safer `html/template` package for rendering HTML content. This single-line fix — swapping one import — prevents user-controlled data from being injected as raw HTML, closing a potential attack vector for malicious script injection. While rated low severity, XSS vulnerabilities are among the most common and exploitable web security issues,