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}