Поиск по сайту:

SQL-инъекция в Java и как ее легко предотвратить


Что такое SQL-инъекция?

SQL-инъекция — одна из 10 основных уязвимостей веб-приложений. Проще говоря, SQL Injection означает внедрение/вставку кода SQL в запрос через данные, введенные пользователем. Это может произойти в любых приложениях, использующих реляционные базы данных, такие как Oracle, MySQL, PostgreSQL и SQL Server.

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

Влияние SQL-инъекций

  • Злоумышленник может получить несанкционированный доступ к вашему приложению и украсть данные.
  • Они могут изменить, удалить данные в вашей базе данных и отключить ваше приложение.
  • Хакер также может получить контроль над системой, в которой работает сервер базы данных, выполнив специальные системные команды базы данных.

Как работает SQL-инъекция?

Предположим, у нас есть таблица базы данных с именем tbluser, в которой хранятся данные о пользователях приложения. UserId — это основной столбец таблицы. У нас есть функционал в приложении, который позволяет получать информацию через userId. Значение userId получено из запроса пользователя.

Давайте посмотрим на приведенный ниже пример кода.

String userId = {get data from end user}; 
String sqlQuery = "select * from tbluser where userId = " + userId;

1. Действительный пользовательский ввод

Когда приведенный выше запрос выполняется с допустимыми данными, т. Е. Значением userId 132, он будет выглядеть так, как показано ниже.

Входные данные: 132

Выполненный запрос: выберите * из tbluser, где userId=132

Результат: запрос вернет данные пользователя с userId 132. В этом случае SQL-инъекция не происходит.

2. Пользовательский ввод хакера

Хакер может изменять пользовательские запросы, используя такие инструменты, как Postman, cURL и т. д., чтобы отправлять код SQL в виде данных и, таким образом, обходить любые проверки на стороне пользовательского интерфейса.

Входные данные: 2 или 1=1

Выполненный запрос: выберите * из tbluser, где userId=2 или 1=1

Результат: Теперь приведенный выше запрос имеет два условия с выражением SQL ИЛИ.

  • userId=2: эта часть будет соответствовать строкам таблицы со значением userId равным «2».
  • 1=1: эта часть всегда будет оцениваться как истинная. Таким образом, запрос вернет все строки таблицы.

Типы SQL-инъекций

Давайте рассмотрим четыре типа SQL-инъекций.

1. Логическая инъекция SQL

Приведенный выше пример является случаем внедрения SQL на основе логических значений. Он использует логическое выражение, которое оценивается как истинное или ложное. Его можно использовать для получения дополнительной информации из базы данных. Например;

Входные данные: 2 или 1=1

SQL-запрос: выберите first_name, last_name из tbl_employee, где empId=2 или 1=1

2. SQL-инъекция на основе объединения

Оператор объединения SQL объединяет данные из двух разных запросов с одинаковым количеством столбцов. В этом случае оператор объединения используется для получения данных из других таблиц.

Входные данные: 2 union select имя пользователя, пароль от tbluser

Запрос: выберите first_name, last_name из tbl_employee, где empId=2 union выберите имя пользователя и пароль из tbluser

Используя SQL-инъекцию на основе объединения, злоумышленник может получить учетные данные пользователя.

3. SQL-инъекция на основе времени

При внедрении SQL на основе времени в запрос вводятся специальные функции, которые могут приостановить выполнение на указанное время. Эта атака замедляет работу сервера базы данных. Это может привести к остановке вашего приложения, повлияв на производительность сервера базы данных. Например, в MySQL:

Входные данные: 2 + СОН(5)

Запрос: выберите emp_id, first_name, last_name из tbl_employee, где empId=2 + SLEEP(5)

В приведенном выше примере выполнение запроса будет приостановлено на 5 секунд.

4. SQL-инъекция на основе ошибок

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

Пример SQL-инъекции Java

Мы будем использовать простое веб-приложение Java для демонстрации SQL Injection. У нас есть Login.html – базовая страница входа, которая берет имя пользователя и пароль от пользователя и отправляет их в LoginServlet.

LoginServlet получает имя пользователя и пароль из запроса и проверяет их на соответствие значениям базы данных. Если аутентификация прошла успешно, Servlet перенаправляет пользователя на домашнюю страницу, в противном случае он вернет ошибку.

Логин.html Код:

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Sql Injection Demo</title>
    </head>
    <body>
    <form name="frmLogin" method="POST" action="https://localhost:8080/Web1/LoginServlet">
        <table>
            <tr>
                <td>Username</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td>Password</td>
                <td><input type="password" name="password"></td>
            </tr>
            <tr>
                <td colspan="2"><button type="submit">Login</button></td>
            </tr>
        </table>
    </form>
    </body>
</html>

Код LoginServlet.java:

package com.journaldev.examples;
import java.io.IOException;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    static {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (Exception e) {}
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        boolean success = false;
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // Unsafe query which uses string concatenation
        String query = "select * from tbluser where username='" + username + "' and password = '" + password + "'";
        Connection conn = null;
        Statement stmt = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
            stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(query);
            if (rs.next()) {
                // Login Successful if match is found
                success = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                stmt.close();
                conn.close();
            } catch (Exception e) {}
        }
        if (success) {
            response.sendRedirect("home.html");
        } else {
            response.sendRedirect("login.html?error=1");
        }
    }
}

Запросы к базе данных [MySQL]:

create database user;

create table tbluser(username varchar(32) primary key, password varchar(32));

insert into tbluser (username,password) values ('john','secret');
insert into tbluser (username,password) values ('mike','pass10');

1. При вводе действительного имени пользователя и пароля со страницы входа

Введите имя пользователя: john

Введите имя пользователя: секрет

Запрос: выберите * из tbluser, где имя пользователя=«Джон» и пароль=«секрет»

Результат: имя пользователя и пароль существуют в базе данных, поэтому аутентификация прошла успешно. Пользователь будет перенаправлен на главную страницу.

2. Получение несанкционированного доступа к системе с помощью SQL Injection

Введите имя пользователя: манекен

Введите пароль: ’ или ‘1’=1

Запрос: выберите * из tbluser, где имя пользователя=«фиктивный» и пароль=«» или «1»=«1»

Результат: введенное имя пользователя и пароль не существуют в базе данных, но аутентификация прошла успешно. Почему?

Это связано с SQL-инъекцией, поскольку мы ввели ’ или ‘1’=1 в качестве пароля. В запросе 3 условия.

  1. username=‘dummy’: значение будет оценено как false, так как в таблице нет пользователя с фиктивным именем пользователя.
  2. password=‘’: будет оценено как false, так как в таблице нет пустого пароля.
  3. ‘1’=‘1’: будет оценено как истинное, так как это статическое сравнение строк.

Теперь объедините все 3 условия, т. е. false и false или true => окончательный результат будет истинным.

В приведенном выше сценарии мы использовали логическое выражение для выполнения SQL-инъекции. Есть и другие способы сделать SQL Injection. В следующем разделе мы увидим способы предотвращения SQL-инъекций в нашем Java-приложении.

Предотвращение SQL-инъекций в Java-коде

Самое простое решение — использовать PreparedStatement вместо Statement для выполнения запроса.

Вместо того, чтобы объединять имя пользователя и пароль в запрос, мы предоставляем их для запроса с помощью методов установки PreparedStatement.

Теперь значение имени пользователя и пароля, полученное из запроса, обрабатывается только как данные, поэтому SQL-инъекция не произойдет.

Давайте посмотрим на модифицированный код сервлета.

String query = "select * from tbluser where username=? and password = ?";
Connection conn = null;
PreparedStatement stmt = null;
try {
    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/user", "root", "root");
    stmt = conn.prepareStatement(query);
    stmt.setString(1, username);
    stmt.setString(2, password);
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
        // Login Successful if match is found
        success = true;
    }
    rs.close();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    try {
        stmt.close();
        conn.close();
    } catch (Exception e) {
    }
}

Разберемся, что происходит в этом случае.

Запрос: выберите * из tbluser, где имя пользователя =? и пароль=?

Знак вопроса (?) в приведенном выше запросе называется позиционным параметром. В приведенном выше запросе есть 2 позиционных параметра. Мы не объединяем имя пользователя и пароль для запроса. Мы используем методы, доступные в PreparedStatement, для обеспечения пользовательского ввода.

Мы установили первый параметр с помощью stmt.setString(1, имя пользователя) , а второй параметр — с помощью stmt.setString(2, пароль). Базовый API JDBC заботится об очистке значений, чтобы избежать внедрения SQL.

Рекомендации по предотвращению SQL-инъекций

  1. Проверяйте данные перед их использованием в запросе.
  2. Не используйте общеупотребительные слова в качестве названия таблицы или столбца. Например, многие приложения используют tbluser или tbaccount для хранения пользовательских данных. Электронная почта, имя, фамилия — это общие названия столбцов.
  3. Не объединяйте данные (полученные как вводимые пользователем) напрямую для создания SQL-запросов.
  4. Используйте такие платформы, как Spring Data JPA, для уровня данных приложения.
  5. Используйте позиционные параметры в запросе. Если вы используете обычный JDBC, используйте PreparedStatement для выполнения запроса.
  6. Ограничьте доступ приложения к базе данных с помощью разрешений и грантов.
  7. Не возвращайте конечному пользователю конфиденциальный код ошибки и сообщение.
  8. Проведите надлежащую проверку кода, чтобы ни один разработчик случайно не написал небезопасный код SQL.
  9. Используйте такие инструменты, как SQLMap, для поиска и устранения уязвимостей SQL-инъекций в вашем приложении.

Это все, что касается Java SQL Injection, надеюсь, здесь ничего важного не пропущено.

Вы можете скачать пример проекта веб-приложения Java по ссылке ниже.

Java-проект SQL-инъекции