Database
 sql >> база данни >  >> RDS >> Database

Как да защитим JDBC приложение от SQL инжекция

Общ преглед

В системата за управление на релационни бази данни (RDBMS) има специфичен език – наречен SQL (език на структурирани запитвания), който се използва за комуникация с базата данни. Изразите за заявка, написани на SQL, се използват за манипулиране на съдържанието и структурата на базата данни. Конкретен SQL оператор, който създава и променя структурата на базата данни, се нарича DDL (език за дефиниране на данни), а операторите, които манипулират съдържанието на базата данни, се наричат ​​DML (език за манипулиране на данни). Машината, свързана с пакета RDBMS, анализира и интерпретира SQL израза и съответно връща резултата. Това е типичният процес на комуникация с RDBMS – задействайте SQL оператор и върнете резултата, това е всичко. Системата не преценява намерението на всяко твърдение, което се придържа към синтаксиса и семантичната структура на езика. Това също означава, че няма процеси за удостоверяване или валидиране, за да се провери кой е задействал изявлението и привилегията, която има при получаване на изхода. Нападателят може просто да задейства SQL изявление със злонамерено намерение и да получи обратно информация, която не би трябвало да получи. Например, нападателят може да изпълни SQL израз със злонамерен полезен товар с безобидно изглеждаща заявка за контрол на сървъра на база данни на уеб приложение.

Как работи

Нападателят може да използва тази уязвимост и да я използва в своя полза. Например, човек може да заобиколи механизма за удостоверяване и оторизация на приложението и да извлече така нареченото защитено съдържание от цялата база данни. SQL инжекция може да се използва за създаване, актуализиране и изтриване на записи от базата данни. Следователно човек може да формулира заявка, ограничена до собственото си въображение с SQL.

Обикновено едно приложение често задейства SQL заявки към базата данни за множество цели, било то за извличане на определени записи, създаване на отчети, удостоверяване на потребител, CRUD транзакции и т.н. Нападателят просто трябва да намери SQL заявка за въвеждане в някаква форма за въвеждане на приложение. След това заявката, подготвена от формуляра, може да се използва за преплитане на злонамерено съдържание, така че, когато приложението задейства заявката, то носи и инжектирания полезен товар.

Една от идеалните ситуации е, когато приложение поиска от потребителя въвеждане като потребителско име или потребителски идентификатор. Приложението отвори уязвимо място там. SQL операторът може да се изпълнява несъзнателно. Нападателят се възползва, като инжектира полезен товар, който да се използва като част от SQL заявката и да се обработва от базата данни. Например псевдокодът от страна на сървъра за POST операция за формуляр за вход може да бъде:

uname = getRequestString("username");
pass = getRequestString("passwd");

stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "'";

database.execute(stmtSQL);

Предходният код е уязвим за атака с инжектиране на SQL, тъй като входът, даден на SQL израза чрез променливите „uname“ и „pass“, може да бъде манипулиран по начин, който би променил семантиката на оператора.

Например, можем да модифицираме заявката да се изпълнява срещу сървъра на базата данни, както в MySQL.

stmtSQL = "SELECT * FROM users WHERE
   user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";

Това води до промяна на оригиналния SQL израз до степен, която позволява да се заобиколи удостоверяването. Това е сериозна уязвимост и трябва да бъде предотвратена от кода.

Защита срещу атака с инжектиране на SQL

Един от начините за намаляване на вероятността от атака с инжектиране на SQL е да се гарантира, че нефилтрираните низове от текст не трябва да бъдат допускани да се добавят към SQL израза преди изпълнение. Например, можем да използваме PreparedStatement за изпълнение на необходимите задачи на базата данни. Интересният аспект на PreparedStatement е, че изпраща предварително компилиран SQL израз към базата данни, а не низ. Това означава, че заявката и данните се изпращат отделно към базата данни. Това предотвратява основната причина за атаката с инжектиране на SQL, тъй като при SQL инжекцията идеята е да се смесват код и данни, при което данните всъщност са част от кода под прикритието на данни. В PreparedStatement , има множество setXYZ() методи, като setString() . Тези методи се използват за филтриране на специални символи, като цитат, съдържащ се в SQL изразите.

Например, можем да изпълним SQL оператор по следния начин.

String sql = "SELECT * FROM employees WHERE emp_no = "+eno;

Вместо да поставите, да речем, eno=10125 като номер на служител във входа, можем да модифицираме заявката с входа, като например:

eno = 10125 OR 1=1

Това напълно променя резултата, върнат от заявката.

Пример

В следващия примерен код ние показахме как PreparedStatement може да се използва за изпълнение на задачи с база данни.

package org.mano.example;

import java.sql.*;
import java.time.LocalDate;
public class App
{
   static final String JDBC_DRIVER =
      "com.mysql.cj.jdbc.Driver";
   static final String DB_URL =
      "jdbc:mysql://localhost:3306/employees";
   static final String USER = "root";
   static final String PASS = "secret";
   public static void main( String[] args )
   {
      String selectQuery = "SELECT * FROM employees
         WHERE emp_no = ?";
      String insertQuery = "INSERT INTO employees
         VALUES (?,?,?,?,?,?)";
      String deleteQuery = "DELETE FROM employees
         WHERE emp_no = ?";
      Connection connection = null;
      try {
         Class.forName(JDBC_DRIVER);
         connection = DriverManager.getConnection
            (DB_URL, USER, PASS);
      }catch(Exception ex) {
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(insertQuery);){
         pstmt.setInt(1,99);
         pstmt.setDate(2, Date.valueOf
            (LocalDate.of(1975,12,11)));
         pstmt.setString(3,"ABC");
         pstmt.setString(4,"XYZ");
         pstmt.setString(5,"M");
         pstmt.setDate(6,Date.valueOf(LocalDate.of(2011,1,1)));
         pstmt.executeUpdate();
         System.out.println("Record inserted successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(selectQuery);){
         pstmt.setInt(1,99);
         ResultSet rs = pstmt.executeQuery();
         while(rs.next()){
            System.out.println(rs.getString(3)+
               " "+rs.getString(4));
         }
      }catch(Exception ex){
         ex.printStackTrace();
      }
      try(PreparedStatement pstmt =
            connection.prepareStatement(deleteQuery);){
         pstmt.setInt(1,99);
         pstmt.executeUpdate();
         System.out.println("Record deleted
            successfully.");
      }catch(SQLException ex){
         ex.printStackTrace();
      }
      try{
         connection.close();
      }catch(Exception ex){
         ex.printStackTrace();
      }
   }
}

Поглед в PreparedStatement

Тези задачи също могат да бъдат извършени с JDBC Изявление интерфейс, но проблемът е, че понякога може да бъде доста несигурен, особено когато се изпълнява динамичен SQL израз за запитване на базата данни, където стойностите, въведени от потребителя, са свързани със SQL заявките. Това може да бъде опасна ситуация, както видяхме. При най-обикновени обстоятелства Изявление е доста безвреден, но PreparedStatement изглежда е по-добрият вариант между двете. Той предотвратява свързването на злонамерени низове поради различния си подход при изпращане на изявлението към базата данни. PreparedStatement използва заместване на променлива, а не конкатенация. Поставянето на въпросителен знак (?) в SQL заявката означава, че заместваща променлива ще заеме нейното място и ще предостави стойността, когато заявката бъде изпълнена. Позицията на заместващата променлива заема своето място според присвоената позиция на индекса на параметъра в setXYZ() методи.

Тази техника го предотвратява от атака с инжектиране на SQL.

Освен това PreparedStatement прилага AutoCloseable. Това му позволява да пише в контекста на опитайте с ресурси блокира и автоматично се затваря, когато излезе извън обхвата.

Заключение

Атаката с инжектиране на SQL може да бъде предотвратена само чрез отговорно писане на кода. Всъщност във всяко софтуерно решение сигурността е най-вече нарушена поради лоши практики на кодиране. Тук сме описали какво да избягвате и как PreparedStatement може да ни помогне да напишем защитен код. За пълна представа относно SQL инжектирането вижте подходящи материали; Интернет е пълен с тях, а за PreparedStatement , разгледайте документацията на Java API за по-подробно обяснение.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Преместване на съществуваща таблица от основна файлова група в друга файлова група

  2. Как да добавя дни към дата в T-SQL

  3. Съвпадение на модела:По-забавно, когато бях дете

  4. Вмъкване на DML с променлива за свързване:ИЗПОЛЗВАНЕ Клауза за изпълнение на незабавно изявление

  5. Модел на база данни за онлайн проучване. част 3