İçeriğe geç

SQL Injection: A Friendly Guide to Understanding the Threat

Imagine typing your email address into a website’s login form. The application takes that value, searches its database, and finds your account.

Nothing unusual so far.

Now imagine entering a piece of SQL instead of an email address. What happens next depends on how the developer built the database query.

If the application inserts your input directly into its SQL statement, you might change what the database executes.

That’s the problem behind SQL Injection, often shortened to SQLi.

Although SQL Injection has been around for decades, developers still encounter it in modern web applications, REST APIs, backend services, and systems that use object-relational mapping frameworks.

The good news? Developers can prevent most SQL Injection vulnerabilities through a few reliable coding practices.

This guide explains how SQL Injection works, why it matters, and how to write safer Java database code.

What Is SQL Injection?

SQL Injection is a security vulnerability that allows an attacker to interfere with a database query by supplying specially crafted input.

The vulnerability appears when an application mixes untrusted data with SQL commands.

Consider a database query that retrieves a customer by email address:

SELECT id, email, full_name
FROM customers
WHERE email = '[email protected]';

This query looks for a specific customer.

But what if the application builds the query by joining SQL text with whatever the user enters?

An attacker may then influence the query’s logic instead of simply providing a value.

Depending on the application, database permissions, and query structure, SQL Injection can enable attackers to:

  • Access sensitive customer information
  • Read data that should remain private
  • Modify or delete database records
  • Bypass certain authentication checks
  • Interfere with application business logic
  • Disrupt database availability

Some database configurations can expose additional capabilities when the application connects with excessive privileges.

The OWASP Top 10:2025 includes SQL Injection within A05:2025 – Injection, a broader category covering injection vulnerabilities.

The important distinction is that SQL itself isn’t the problem.

The vulnerability comes from how an application handles SQL commands and external data.

How Does SQL Injection Work?

Let’s examine a small Java application.

Suppose an online store allows users to search for customer information by email address.

A developer might write the following JDBC code.

A Vulnerable Java Example

public Customer findByEmail(
        Connection connection,
        String email
) throws SQLException {

    String sql =
        "SELECT id, email, full_name " +
        "FROM customers WHERE email = '" +
        email + "'";

    try (Statement statement =
             connection.createStatement();
         ResultSet rs =
             statement.executeQuery(sql)) {

        if (rs.next()) {
            return new Customer(
                rs.getLong("id"),
                rs.getString("email"),
                rs.getString("full_name")
            );
        }

        return null;
    }
}

The code appears straightforward.

It creates a SQL query, executes it, and retrieves the first matching customer.

Unfortunately, one line introduces a serious security weakness:

"FROM customers WHERE email = '" + email + "'"

The application inserts the supplied email address directly into the SQL command.

That means the value becomes part of the SQL statement’s text.

The database cannot reliably distinguish the developer’s intended query structure from SQL syntax that arrived through the user input.

What Happens When an Attacker Changes the Input?

Instead of a normal email address, consider this input:

' OR '1'='1'

When the application concatenates it into the original query, the final SQL becomes:

SELECT id, email, full_name
FROM customers
WHERE email = '' OR '1'='1';

Look closely at the condition:

'1'='1'

This comparison always evaluates to true.

As a result, the database may return every row instead of looking for one customer.

The sample Java method reads only the first result, but the query itself no longer applies the intended email filter.

If other application logic treats that result as proof of identity or authorization, the consequences could become much more serious.

This example captures the central idea behind SQL Injection:

The attacker turns input data into executable SQL logic.

Why Is SQL Injection Dangerous?

SQL Injection can affect more than a single database query.

Its impact depends on the application’s architecture, the affected SQL statement, and the privileges assigned to the database connection.

Exposure of Sensitive Data

Applications often store customer details, addresses, order histories, and other valuable information.

A vulnerable query can expose data that users should never access.

Even when an attacker cannot download an entire database, accessing another customer’s records creates a security and privacy problem.

Unauthorized Data Modification

Some application accounts can create, update, or delete database records.

If an attacker exploits a suitable SQL Injection vulnerability through one of these accounts, the attacker may gain opportunities to modify business data.

For an online store, that might affect product information, customer records, or order management processes.

Application Disruption

A maliciously altered query may consume excessive database resources.

Expensive operations can reduce performance and interfere with legitimate requests.

Under certain conditions, this behavior can contribute to a denial-of-service incident.

Authentication and Authorization Risks

SQL Injection can sometimes interfere with authentication or authorization checks.

However, not every SQL Injection vulnerability gives an attacker the ability to log in as another user.

The actual outcome depends on how the application uses the query result and which permissions the database account holds.

Common Types of SQL Injection

Security professionals commonly classify SQL Injection techniques by how attackers retrieve information or observe the application’s behavior.

1. In-Band SQL Injection

In-band SQL Injection uses the same communication channel to influence the SQL query and observe its output.

Two common forms exist.

Error-Based SQL Injection

Attackers examine database error messages to learn about the underlying system.

Detailed error responses may reveal query structure, table names, or other technical information.

Union-Based SQL Injection

Attackers attempt to combine the results of different SQL queries through the UNION operator.

The technique requires compatible query structures and data types.

2. Blind SQL Injection

Some applications hide database errors and query output.

That doesn’t automatically make them immune to SQL Injection.

Attackers may still infer information by observing application behavior.

Boolean-Based Blind SQL Injection

This technique compares how an application responds to conditions that evaluate to true or false.

Time-Based Blind SQL Injection

This technique relies on differences in response times.

Since network latency and server load can introduce noise, timing observations require careful interpretation.

3. Out-of-Band SQL Injection

Out-of-band techniques attempt to use a separate communication channel to retrieve information.

These attacks depend on particular database capabilities, network access, and configuration choices.

Not every database environment supports the conditions they require.

4. Second-Order SQL Injection

Second-order SQL Injection often surprises developers.

An application first stores untrusted input in its database.

Later, another part of the application retrieves that stored value and inserts it into a new SQL query without proper parameter binding.

The vulnerability appears during the second operation rather than the original request.

This illustrates an important security principle: data doesn’t automatically become trustworthy because it came from your own database.

How to Prevent SQL Injection

The most effective approach starts with one rule:

Keep SQL commands separate from data values.

OWASP recommends parameterized queries as a primary defense.

Let’s examine what that means in practice.

1. Use Prepared Statements and Parameterized Queries

Java provides the PreparedStatement interface through JDBC.

Instead of embedding a value inside SQL text, we define the SQL statement and bind the value separately.

Here’s a safer version of our customer lookup.

public Customer findByEmail(
        Connection connection,
        String email
) throws SQLException {

    String sql = """
        SELECT id, email, full_name
        FROM customers
        WHERE email = ?
        """;

    try (PreparedStatement statement =
             connection.prepareStatement(sql)) {

        statement.setString(1, email);

        try (ResultSet rs =
                 statement.executeQuery()) {

            if (rs.next()) {
                return new Customer(
                    rs.getLong("id"),
                    rs.getString("email"),
                    rs.getString("full_name")
                );
            }

            return null;
        }
    }
}

Two details matter here.

First, the ? placeholder represents a data value.

Second, setString() binds the supplied email address to that placeholder.

The JDBC driver handles the value through the database parameter-binding mechanism.

Even if someone enters:

' OR '1'='1'

the database treats that input as a string value to search for rather than additional SQL logic.

Why Do Prepared Statements Work?

Prepared statements establish a boundary between the SQL statement’s structure and its data values.

The application defines the query.

The user supplies the values.

That difference prevents data-bound input from changing the query’s intended syntax.

It also handles legitimate values containing special characters, such as a customer’s name with an apostrophe.

One limitation matters, though: prepared statements protect the values you bind. They cannot protect SQL fragments that you still construct through unsafe concatenation.

2. Use Allow-Lists for Dynamic Query Structure

Parameterized queries work for data values, but they don’t replace SQL identifiers or keywords.

For example, a JDBC ? placeholder cannot represent a column name in an ORDER BY clause.

Suppose your online store allows customers to sort products by price or name.

Avoid this approach:

String sql =
    "SELECT id, name, price FROM products ORDER BY "
    + sortBy;

Here, the application inserts untrusted input into the SQL statement’s structure.

Instead, define which options the application supports.

public String buildProductQuery(
        String sortBy,
        String direction
) {
    String column = switch (sortBy) {
        case "name"  -> "name";
        case "price" -> "price";
        default -> throw new
            IllegalArgumentException(
                "Invalid sorting field"
            );
    };

    String order = switch (direction) {
        case "asc"  -> "ASC";
        case "desc" -> "DESC";
        default -> throw new
            IllegalArgumentException(
                "Invalid sorting direction"
            );
    };

    return """
        SELECT id, name, price
        FROM products
        ORDER BY %s %s
        """.formatted(column, order);
}

The application now controls the SQL identifiers and keywords.

Users can request only the choices the developer explicitly supports.

Continue using prepared statements for the query’s ordinary data values.

3. Validate Input According to Business Rules

Input validation helps applications reject unexpected values.

For example, an application can require a product identifier to contain a positive integer or limit the length of a search term.

Those checks improve reliability and reduce unnecessary processing.

However, input validation alone doesn’t prevent SQL Injection.

Blocking quotation marks or maintaining a blacklist of suspicious SQL keywords cannot reliably address every SQL dialect and query context.

Use validation for business rules and parameter binding for database query safety.

4. Apply the Principle of Least Privilege

Database accounts should have only the permissions their applications actually need.

For example, a service that reads product information shouldn’t require permission to modify every customer record.

A PostgreSQL administrator could restrict table permissions with statements such as:

REVOKE ALL ON TABLE products
FROM product_reader;

GRANT SELECT ON TABLE products
TO product_reader;

These statements control permissions on the specified table. A production security review must also consider role inheritance, schema permissions, and access to other database objects.

Least privilege doesn’t remove a SQL Injection vulnerability.

Instead, it reduces the potential damage if an attacker successfully exploits one.

For larger architectures, consider different database roles for services with different responsibilities.

A reporting service and an order-processing service rarely need identical database privileges.

5. Don’t Assume Your ORM Makes Every Query Safe

Developers often use Hibernate, JPA, or similar frameworks to avoid writing repetitive SQL code.

These frameworks support parameterized query APIs, but developers can still introduce vulnerabilities through unsafe query construction.

Consider this JPQL example:

String jpql =
    "SELECT u FROM User u WHERE u.email = '"
    + email + "'";

var query = entityManager.createQuery(
    jpql,
    User.class
);

The application still inserts untrusted input into the query text.

A better approach uses a named parameter.

var query = entityManager.createQuery(
    """
    SELECT u
    FROM User u
    WHERE u.email = :email
    """,
    User.class
);

query.setParameter("email", email);

List<User> users = query.getResultList();

Here, the ORM treats the email value as a parameter.

Review dynamically constructed JPQL, HQL, native SQL, and sorting expressions with the same attention you give to ordinary JDBC queries.

6. Review Stored Procedures Carefully

Stored procedures can help organize database logic.

However, they don’t automatically eliminate SQL Injection.

A procedure that constructs dynamic SQL by concatenating untrusted values can create the same vulnerability as application-level string concatenation.

Use bound arguments when calling procedures.

Inside the procedure, prefer static SQL or parameterized dynamic SQL.

When a procedure needs a dynamic identifier, restrict the selection to trusted, application-approved choices.

7. Handle Database Errors and Logs Safely

Database error messages help developers diagnose problems.

But detailed SQL errors can reveal useful information to attackers.

Production applications should avoid returning raw database exceptions to clients.

Keep technical error details in protected logs, and provide clients with appropriate generic error responses.

Make sure your logging strategy also protects sensitive information.

Passwords, authentication tokens, and private customer data shouldn’t appear in ordinary diagnostic logs.

How Can Developers Test for SQL Injection?

You should test only applications and environments you own or have explicit authorization to assess.

Start with code review and repeatable automated checks.

Review Query Construction

Look for places where the application builds SQL statements dynamically.

Pay particular attention to:

  • String concatenation inside SQL queries
  • Dynamic JDBC Statement execution
  • Native SQL constructed through ORM frameworks
  • Dynamic JPQL or HQL expressions
  • Stored procedures that assemble SQL text
  • User-controlled sorting fields and identifiers

The presence of Statement alone doesn’t prove a vulnerability. The risk arises when untrusted data can modify SQL syntax.

Combine SAST and DAST

Static Application Security Testing (SAST) tools inspect source code for potential vulnerabilities.

Dynamic Application Security Testing (DAST) tools examine a running application’s behavior.

Tools such as OWASP ZAP can support an authorized testing process.

Neither category guarantees complete coverage.

Use automated testing alongside code review and carefully designed regression tests.

Write a Java Regression Test

A simple JUnit 5 test can verify that your repository treats suspicious input as ordinary data.

@Test
void shouldNotInterpretInputAsSql()
        throws SQLException {

    String input = "' OR '1'='1";

    Customer customer =
        repository.findByEmail(
            connection,
            input
        );

    assertNull(customer);
}

For this test, use an isolated database with known records and no customer whose email equals the supplied test string.

You can strengthen the test by inserting a legitimate customer first and verifying that the suspicious input doesn’t return that record.

This doesn’t replace broader security testing.

It does, however, help prevent a previously corrected vulnerability from returning during a future code change.

A Real-World Example: Securing an E-Commerce Product Search

An e-commerce application exposes many database-driven operations.

Product searches, price filters, order histories, customer profiles, and administrative reports all deserve attention.

Let’s implement a simple product search using Java and JDBC.

The method accepts a keyword and a maximum price.

public List<Product> searchProducts(
        Connection connection,
        String keyword,
        BigDecimal maxPrice
) throws SQLException {

    String sql = """
        SELECT id, name, price
        FROM products
        WHERE LOWER(name) LIKE LOWER(?)
          AND price <= ?
        """;

    List<Product> products =
        new ArrayList<>();

    try (PreparedStatement statement =
             connection.prepareStatement(sql)) {

        statement.setString(
            1,
            "%" + keyword + "%"
        );

        statement.setBigDecimal(
            2,
            maxPrice
        );

        try (ResultSet rs =
                 statement.executeQuery()) {

            while (rs.next()) {
                products.add(
                    new Product(
                        rs.getLong("id"),
                        rs.getString("name"),
                        rs.getBigDecimal("price")
                    )
                );
            }
        }
    }

    return products;
}

This example binds both user-controlled values.

The first parameter searches for products containing the requested keyword.

The second parameter limits the maximum price.

Notice that the code adds % characters to the Java string before binding it.

That’s safe from SQL Injection because the application constructs a parameter value rather than adding SQL syntax to the query.

There is one additional detail worth understanding.

SQL LIKE treats % and _ as wildcard characters. If your application expects literal text matching, you should handle those characters according to your database’s escaping rules.

That’s a search-behavior concern rather than an SQL Injection vulnerability, but it demonstrates why secure code must also follow the application’s business requirements.

Frequently Asked Questions About SQL Injection

Is SQL Injection still a problem in modern applications?

Yes. Modern frameworks provide safer database APIs, but developers can still introduce SQL Injection vulnerabilities when they concatenate untrusted values into query text.

Does SQL Injection affect only login forms?

No. Search fields, filters, reports, API parameters, and other database-driven operations may expose vulnerable queries.

Can prepared statements completely prevent SQL Injection?

Correctly parameterized data values cannot change the SQL statement’s structure. However, developers can still create vulnerabilities by constructing other query fragments from untrusted input.

Does Hibernate prevent SQL Injection automatically?

No. Hibernate supports secure parameter binding, but unsafe dynamically constructed HQL, JPQL, or native SQL can introduce vulnerabilities.

Should I remove special characters from user input?

Not as a general SQL Injection defense. Legitimate values may contain apostrophes and other special characters. Use parameter binding instead of relying on character filtering.

Can a Web Application Firewall stop SQL Injection?

A WAF may detect or block some malicious requests, but it cannot guarantee protection and doesn’t fix the underlying code defect. Treat it as an additional security layer.

What’s the difference between SQL Injection and XSS?

SQL Injection targets the structure of database queries. Cross-Site Scripting (XSS) targets how browsers interpret untrusted content. Both involve trust boundaries, but they affect different execution contexts.

Final Thoughts: Make Safe Queries Your Default

SQL Injection becomes possible when an application allows external data to change the structure of a database query.

You don’t need an elaborate security system to address the core problem.

Start by writing parameterized queries. Use explicit allow-lists when users need to influence query structure. Limit database privileges and include security checks in your development workflow.

If you’re writing Java, make PreparedStatement your default choice for JDBC queries that include variable data.

If you’re using an ORM, learn how its parameter-binding APIs work and avoid unsafe dynamic query construction.

Most importantly, keep one principle in mind:

Your application defines the SQL. Your users provide the data.

That separation is the foundation of SQL Injection prevention.

References and Further Reading

  1. OWASP — SQL Injection Prevention Cheat Sheet
    https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
  2. OWASP — Top 10:2025, A05 Injection
    https://top10.owasp.org/2025/A05_2025-Injection/
  3. OWASP — Web Security Testing Guide
    https://owasp.org/www-project-web-security-testing-guide/
  4. Oracle — Using Prepared Statements
    https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html

Bir sonraki ne okunmalı?

SQL Injection Nedir? Saldırıyı Anlamak ve Önlemek İçin Pratik Rehber

Bir web uygulamasının giriş ekranına kullanıcı adınızı yazdığınızı düşünün. Uygulama bu bilgiyi veritabanına gönderiyor ve hesabınızı arıyor. Buraya kadar her şey normal. Peki, kullanıcı…

13 dk okuma süresi
Sonraki yazıyı oku

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir