SQL Injection

Never Build SQL by Concatenation

Example

If user input is pasted into the SQL string, the user can change the query itself. Watch what a search box receives from an attacker:

public class Main {
    static String unsafeLogin(String email, String password) {
        return "select * from users where email = '" + email + "' and password = '" + password + "'";
    }

    public static void main(String[] args) {
        System.out.println("Normal user:");
        System.out.println(unsafeLogin("[email protected]", "Pass@123"));

        System.out.println("Attacker:");
        System.out.println(unsafeLogin("[email protected]' --", "anything"));

        System.out.println("Attacker 2:");
        System.out.println(unsafeLogin("x' OR '1'='1", "x' OR '1'='1"));
    }
}

Output

Normal user:
select * from users where email = '[email protected]' and password = 'Pass@123'
Attacker:
select * from users where email = '[email protected]' --' and password = 'anything'
Attacker 2:
select * from users where email = 'x' OR '1'='1' and password = 'x' OR '1'='1'

In the first attack, -- starts an SQL comment, so the password check disappears and the attacker logs in as admin. In the second, OR '1'='1' is always true and every user row is returned.

The Fix: Parameters

// SAFE: the driver sends the value separately from the SQL text
PreparedStatement ps = conn.prepareStatement(
    "select * from users where email = ? and password_hash = ?");
ps.setString(1, email);          // "[email protected]' --" is treated as plain text
ps.setString(2, hash);
  • With ? parameters the input can never become SQL code.
  • Hibernate and Spring Data JPA always use parameters for find, persist, derived queries and JPQL with :name parameters.
  • You can still create injection in JPA if you concatenate strings into JPQL or native SQL. Always bind parameters.

Note: Also never store plain passwords; store a BCrypt hash. See the Spring Security material.