001package edu.pdx.cs.joy.jdbc;
002
003import java.math.BigDecimal;
004import java.sql.*;
005
006/**
007 * Demonstrates the security vulnerability of using Statement versus PreparedStatement
008 * for database queries. This example shows how SQL injection attacks work and how
009 * PreparedStatement protects against them.
010 */
011public class SQLInjectionExample {
012
013  /**
014   * Simple Employee class to hold employee data.
015   */
016  static class Employee {
017    private final String name;
018    private final String email;
019    private final BigDecimal salary;
020    private final String password;
021
022    public Employee(String name, String email, BigDecimal salary, String password) {
023      this.name = name;
024      this.email = email;
025      this.salary = salary;
026      this.password = password;
027    }
028
029    public String getName() {
030      return name;
031    }
032
033    public String getEmail() {
034      return email;
035    }
036
037    public BigDecimal getSalary() {
038      return salary;
039    }
040
041    public String getPassword() {
042      return password;
043    }
044
045    @Override
046    public String toString() {
047      return "Employee{" +
048        "name='" + name + '\'' +
049        ", email='" + email + '\'' +
050        ", salary=" + salary +
051        ", password='" + password + '\'' +
052        '}';
053    }
054  }
055
056  /**
057   * Creates the employees table in the database.
058   */
059  private static void createTable(Connection connection) throws SQLException {
060    try (Statement statement = connection.createStatement()) {
061      statement.execute(
062        "CREATE TABLE employees (" +
063        "  id IDENTITY PRIMARY KEY," +
064        "  name VARCHAR(255) NOT NULL," +
065        "  email VARCHAR(255) NOT NULL," +
066        "  salary DECIMAL(10, 2) NOT NULL," +
067        "  password VARCHAR(255) NOT NULL" +
068        ")"
069      );
070    }
071  }
072
073  /**
074   * Inserts an employee into the database.
075   */
076  private static void insertEmployee(Connection connection, Employee employee) throws SQLException {
077    String sql = "INSERT INTO employees (name, email, salary, password) VALUES (?, ?, ?, ?)";
078    try (PreparedStatement statement = connection.prepareStatement(sql)) {
079      statement.setString(1, employee.getName());
080      statement.setString(2, employee.getEmail());
081      statement.setBigDecimal(3, employee.getSalary());
082      statement.setString(4, employee.getPassword());
083      statement.executeUpdate();
084    }
085  }
086
087  /**
088   * VULNERABLE: Uses Statement with string concatenation, allowing SQL injection.
089   *
090   * This method is intentionally vulnerable to demonstrate the security risk.
091   * A malicious user can use the username "Dave --'" to comment out the password check,
092   * gaining unauthorized access to the data.
093   */
094  private static Employee getEmployeeDataWithStatement(Connection connection, String name, String password) throws SQLException {
095    // SECURITY VULNERABILITY: Building SQL with string concatenation
096    String sql = "SELECT name, email, salary, password FROM employees WHERE name = '" + name + "' AND password = '" + password + "'";
097
098    System.out.println("\nExecuting SQL with Statement:");
099    System.out.println("  SQL: " + sql);
100
101    try (Statement statement = connection.createStatement();
102         ResultSet resultSet = statement.executeQuery(sql)) {
103
104      if (resultSet.next()) {
105        return createEmployee(resultSet);
106      }
107    }
108
109    return null;
110  }
111
112  /**
113   * SECURE: Uses PreparedStatement with parameter binding, preventing SQL injection.
114   *
115   * This method properly uses PreparedStatement, which treats user input as data
116   * rather than SQL code, preventing SQL injection attacks.
117   */
118  private static Employee getEmployeeDataWithPreparedStatement(Connection connection, String name, String password) throws SQLException {
119    String sql = "SELECT name, email, salary, password FROM employees WHERE name = ? AND password = ?";
120
121    System.out.println("\nExecuting SQL with PreparedStatement:");
122    System.out.println("  SQL: " + sql);
123    System.out.println("  Parameter 1 (name): " + name);
124    System.out.println("  Parameter 2 (password): " + password);
125
126    try (PreparedStatement statement = connection.prepareStatement(sql)) {
127      statement.setString(1, name);
128      statement.setString(2, password);
129
130      try (ResultSet resultSet = statement.executeQuery()) {
131        if (resultSet.next()) {
132          return createEmployee(resultSet);
133        }
134      }
135    }
136
137    return null;
138  }
139
140  private static Employee createEmployee(ResultSet resultSet) throws SQLException {
141    return new Employee(
142      resultSet.getString("name"),
143      resultSet.getString("email"),
144      resultSet.getBigDecimal("salary"),
145      resultSet.getString("password")
146    );
147  }
148
149  public static void main(String[] args) throws SQLException {
150    System.out.println("=== SQL Injection Demonstration ===\n");
151
152    // 1) Create an in-memory H2 database
153    Connection connection = H2DatabaseHelper.createInMemoryConnection("sqlInjectionDemo");
154
155    // Create the employees table
156    createTable(connection);
157
158    // 2) Persist four employees with unique data
159    insertEmployee(connection, new Employee("Dave", "dave@example.com", new BigDecimal("85000.00"), "securePass123"));
160    insertEmployee(connection, new Employee("Alice", "alice@example.com", new BigDecimal("90000.00"), "aliceSecret"));
161    insertEmployee(connection, new Employee("Bob", "bob@example.com", new BigDecimal("78000.00"), "bobPassword"));
162    insertEmployee(connection, new Employee("Carol", "carol@example.com", new BigDecimal("92000.00"), "carolPass456"));
163
164    System.out.println("Created employees table and inserted 4 employees.\n");
165
166    // Malicious input: Using SQL injection to bypass password check
167    String maliciousUsername = "Dave --'";
168    String incorrectPassword = "wrongPassword";
169
170    System.out.println("Attempting to access Dave's data with:");
171    System.out.println("  Username: \"" + maliciousUsername + "\"");
172    System.out.println("  Password: \"" + incorrectPassword + "\" (incorrect)");
173
174    // 3) Demonstrate SQL injection vulnerability with Statement
175    System.out.println("\n--- Using Statement (VULNERABLE) ---");
176    try {
177      Employee employee = getEmployeeDataWithStatement(connection, maliciousUsername, incorrectPassword);
178      if (employee != null) {
179        System.out.println("\n️SQL INJECTION SUCCESSFUL! Unauthorized access granted:");
180        System.out.println("  Name: " + employee.getName());
181        System.out.println("  Email: " + employee.getEmail());
182        System.out.println("  Salary: $" + String.format("%.2f", employee.getSalary()));
183        System.out.println("  Password: " + employee.getPassword());
184        System.out.println("\nThe SQL comment '--' caused the password check to be ignored!");
185      } else {
186        System.out.println("\nAccess denied (unexpected)");
187      }
188    } catch (SQLException e) {
189      System.out.println("\nError: " + e.getMessage());
190    }
191
192    // 4) Demonstrate protection with PreparedStatement
193    System.out.println("\n--- Using PreparedStatement (SECURE) ---");
194    try {
195      Employee employee = getEmployeeDataWithPreparedStatement(connection, maliciousUsername, incorrectPassword);
196      if (employee != null) {
197        System.out.println("\nAccess granted (unexpected):");
198        System.out.println("  " + employee);
199      } else {
200        System.out.println("\nAccess denied - No employee found with that name and password combination.");
201        System.out.println("PreparedStatement treated 'Dave --'' as a literal username, not SQL code.");
202      }
203    } catch (SQLException e) {
204      System.out.println("\nError: " + e.getMessage());
205    }
206
207    // Clean up
208    connection.close();
209
210    System.out.println("\n=== Summary ===");
211    System.out.println("Statement with string concatenation: VULNERABLE to SQL injection");
212    System.out.println("PreparedStatement with parameters: SECURE against SQL injection");
213  }
214}