Рубрики
Без рубрики

Оператор JDBC против PreparedStatement – Пример внедрения SQL

Заявление против подготовленного заявления. Пример SQL – инъекции. Разница между Заявлением и подготовленным заявлением. Заявления JDBC и подготовленные заявления.

Автор оригинала: Pankaj Kumar.

Сегодня мы рассмотрим инструкцию JDBC против PreparedStatement и некоторые примеры SQL-инъекций. При работе с JDBC для подключения к базе данных мы можем использовать Оператор или PreparedStatement для выполнения запросов. Эти запросы могут быть запросами операций CRUD или даже запросами DDL для создания или удаления таблиц.

Заявление против подготовленного заявления

Прежде чем сравнивать оператор с PreparedStatement, давайте посмотрим, почему нам следует избегать оператора JDBC. В заявлении JDBC есть некоторые серьезные проблемы, и их следует избегать во всех случаях, давайте рассмотрим это на простом примере.

У меня есть Пользователи таблица в моей локальной базе данных MySQL со следующими данными.

Ниже скрипт создаст таблицу и вставит данные для тестового использования.

CREATE TABLE `Users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '',
  `email` varchar(20) NOT NULL DEFAULT '',
  `country` varchar(20) DEFAULT 'USA',
  `password` varchar(20) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

INSERT INTO `Users` (`id`, `name`, `email`, `country`, `password`)
VALUES
	(1, 'Pankaj', 'pankaj@apple.com', 'India', 'pankaj123'),
	(4, 'David', 'david@gmail.com', 'USA', 'david123'),
	(5, 'Raman', 'raman@google.com', 'UK', 'raman123');

Служебный класс для создания соединения JDBC с нашей базой данных mysql.

Служебный класс для создания соединения JDBC с нашей базой данных mysql.

package com.journaldev.jdbc.statements;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnection {

	public final static String DB_DRIVER_CLASS = "com.mysql.jdbc.Driver";
	public final static String DB_URL = "jdbc:mysql://localhost:3306/UserDB";
	public final static String DB_USERNAME = "pankaj";
	public final static String DB_PASSWORD = "pankaj123";

	public static Connection getConnection() throws ClassNotFoundException, SQLException {

		Connection con = null;

		// load the Driver Class
		Class.forName(DB_DRIVER_CLASS);

		// create the connection now
		con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);

		System.out.println("DB Connection created successfully");
		return con;
	}
}

Теперь предположим, что у нас есть следующий класс, который просит пользователя ввести идентификатор электронной почты и пароль, и, если он совпадает, затем печатает данные пользователя. Я использую инструкцию JDBC для выполнения запроса.

Теперь предположим, что у нас есть следующий класс, который просит пользователя ввести идентификатор электронной почты и пароль, и, если он совпадает, затем печатает данные пользователя. Я использую инструкцию JDBC для выполнения запроса.

package com.journaldev.jdbc.statements;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;

public class GetUserDetails {
	
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		
		//read user entered data
		Scanner scanner = new Scanner(System.in);
		System.out.println("Please enter email id:");
		String id = scanner.nextLine();
		System.out.println("User id="+id);
		System.out.println("Please enter password to get details:");
		String pwd = scanner.nextLine();
		System.out.println("User password="+pwd);
		printUserData(id,pwd);
		
	}

	private static void printUserData(String id, String pwd) throws ClassNotFoundException, SQLException {
		
		Connection con = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
		con = DBConnection.getConnection();
		stmt = con.createStatement();
		String query = "select name, country, password from Users where email = '"+id+"' and password='"+pwd+"'";
		System.out.println(query);
		rs = stmt.executeQuery(query);
		
		while(rs.next()){
			System.out.println("Name="+rs.getString("name")+",country="+rs.getString("country")+",password="+rs.getString("password"));
		}
		}finally{
			if(rs != null) rs.close();
			stmt.close();
			con.close();
		}
		
	}

}

Давайте посмотрим, что происходит, когда мы передаем различные виды входных данных в вышеуказанную программу.

Действительный пользователь :

Please enter email id:
david@gmail.com
User id=david@gmail.com
Please enter password to get details:
david123
User password=david123
DB Connection created successfully
select name, country, password from Users where email = 'david@gmail.com' and password='david123'
Name=David,country=USA,password=david123

Таким образом, наша программа работает нормально, и действительный пользователь может ввести свои учетные данные и получить свои данные.

Теперь давайте посмотрим, как хакер может получить несанкционированный доступ к пользователю, потому что мы используем оператор для выполнения запросов.

SQL-инъекция :

Please enter email id:
david@gmail.com' or '1'='1
User id=david@gmail.com' or '1'='1
Please enter password to get details:

User password=
DB Connection created successfully
select name, country, password from Users where email = 'david@gmail.com' or '1'='1' and password=''
Name=David,country=USA,password=david123

Как вы можете видеть, мы можем получить данные пользователя даже без пароля. Ключевым моментом, который следует отметить здесь, является то, что запрос создается с помощью конкатенации строк, и если мы обеспечим надлежащий ввод, мы сможем взломать систему, как здесь, передав идентификатор пользователя как david@gmail.com' или '1'='1 .

Это пример SQL-инъекции , где плохое программирование делает наше приложение уязвимым для несанкционированного доступа к базе данных.

Одним из решений является чтение пользовательского ввода, а затем экранирование всех специальных символов, используемых MySQL, но это было бы неуклюжим и подверженным ошибкам. Вот почему JDBCAPI придумал PreparedStatement интерфейс, который расширяет Оператор и автоматически экранирует специальные символы перед выполнением запроса.

Давайте перепишем вышеописанный класс с помощью PreparedStatement и попробуем взломать систему.

Давайте перепишем вышеописанный класс с помощью PreparedStatement и попробуем взломать систему.

package com.journaldev.jdbc.statements;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class GetUserDetailsUsingPS {

	public static void main(String[] args) throws ClassNotFoundException, SQLException {

		// read user entered data
		Scanner scanner = new Scanner(System.in);
		System.out.println("Please enter email id:");
		String id = scanner.nextLine();
		System.out.println("User id=" + id);
		System.out.println("Please enter password to get details:");
		String pwd = scanner.nextLine();
		System.out.println("User password=" + pwd);
		printUserData(id, pwd);
	}

	private static void printUserData(String id, String pwd) throws ClassNotFoundException,
			SQLException {

		Connection con = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		String query = "select name, country, password from Users where email = ? and password = ?";
		try {
			con = DBConnection.getConnection();
			ps = con.prepareStatement(query);
			
			//set the parameter
			ps.setString(1, id);
			ps.setString(2, pwd);
			rs = ps.executeQuery();

			while (rs.next()) {
				System.out.println("Name=" + rs.getString("name") + ",country="
						+ rs.getString("country") + ",password="
						+ rs.getString("password"));
			}
		} finally {
			if (rs != null)
				rs.close();
			ps.close();
			con.close();
		}

	}
}

Теперь, если мы попытаемся взломать систему, давайте посмотрим, что произойдет.

SQL-инъекция :

Please enter email id:
david@gmail.com' or '1'='1
User id=david@gmail.com' or '1'='1
Please enter password to get details:

User password=
DB Connection created successfully

Таким образом, мы не можем взломать базу данных, это произошло потому, что фактический запрос, который выполняется,:

выберите имя, страну, пароль у Пользователей, где’

Когда мы запускаем запрос для выполнения для реляционной базы данных, он выполняет следующие действия.

  1. Синтаксический анализ SQL-запроса
  2. Компиляция SQL-запроса
  3. Планирование и оптимизация пути сбора данных
  4. Выполнение оптимизированного запроса и возврат полученных данных

Когда мы используем Оператор , он проходит все четыре шага, но с Подготовленным оператором первые три шага выполняются при создании подготовленного оператора. Таким образом, выполнение запроса занимает меньше времени и быстрее, чем выполнение инструкции.

Еще одним преимуществом использования PreparedStatement является то, что мы можем использовать пакетную обработку с помощью методов addBatch() и executeBatch () . Мы можем создать один подготовленный оператор и использовать его для выполнения нескольких запросов.

Некоторые моменты, которые следует помнить о JDBC PreparedStatement, заключаются в следующем:

  1. Подготовленный оператор помогает нам предотвращать атаки с использованием SQL-инъекций, поскольку он автоматически экранирует специальные символы.
  2. Подготовленный оператор позволяет нам выполнять динамические запросы с вводом параметров.
  3. Подготовленная инструкция предоставляет различные типы методов настройки для задания входных параметров запроса.
  4. Подготовленное утверждение выполняется быстрее, чем утверждение. Это становится более заметным, когда мы повторно используем PreparedStatement или используем его методы пакетной обработки для выполнения нескольких запросов.
  5. Подготовленный оператор помогает нам в написании объектно-ориентированного кода с помощью методов настройки, в то время как с помощью оператора мы должны использовать объединение строк для создания запроса. Если необходимо задать несколько параметров, написание запроса с использованием конкатенации строк выглядит очень некрасиво и подвержено ошибкам.
  6. Подготовленный оператор возвращает ТОЛЬКО ВПЕРЕД набор результатов, поэтому мы можем двигаться только в прямом направлении.
  7. В отличие от массивов Java или списка, индексация переменных PreparedStatement начинается с 1.
  8. Одним из ограничений PreparedStatement является то, что мы не можем использовать его для SQL-запросов в предложении, поскольку PreparedStatement не позволяет нам связывать несколько значений для одного заполнителя (?). Однако существует несколько альтернативных подходов к использованию PreparedStatement для пункта IN, подробнее читайте в JDBC PreparedStatement В пункте .

Это все для сравнения заявления JDBC с подготовленным заявлением. Вы всегда должны использовать PreparedStatement, потому что он быстрый, объектно-ориентированный, динамичный и более надежный.