Database
 sql >> Baza danych >  >> RDS >> Database

Jak chronić aplikację JDBC przed wstrzyknięciem SQL?

Przegląd

W systemie zarządzania relacyjnymi bazami danych (RDBMS) istnieje specyficzny język — zwany SQL (język zapytań strukturalnych) — który jest używany do komunikacji z bazą danych. Instrukcje zapytania napisane w języku SQL służą do manipulowania zawartością i strukturą bazy danych. Określona instrukcja SQL, która tworzy i modyfikuje strukturę bazy danych, nazywana jest instrukcją DDL (język definicji danych), a instrukcje manipulujące zawartością bazy danych nazywane są instrukcją DML (język manipulacji danymi). Silnik powiązany z pakietem RDBMS analizuje i interpretuje instrukcję SQL i odpowiednio zwraca wynik. To typowy proces komunikacji z RDBMS — uruchom instrukcję SQL i odzyskaj wynik, to wszystko. System nie ocenia intencji żadnego stwierdzenia, które jest zgodne ze składnią i semantyczną strukturą języka. Oznacza to również, że nie ma procesów uwierzytelniania ani walidacji, które sprawdzałyby, kto uruchomił instrukcję i jakie uprawnienia ma do uzyskania wyniku. Atakujący może po prostu odpalić instrukcję SQL ze złośliwym zamiarem i odzyskać informacje, których nie powinien uzyskać. Na przykład atakujący może wykonać instrukcję SQL ze złośliwym ładunkiem z nieszkodliwie wyglądającym zapytaniem, aby kontrolować serwer bazy danych aplikacji internetowej.

Jak to działa

Atakujący może wykorzystać tę lukę i wykorzystać ją na swoją korzyść. Można na przykład ominąć mechanizm uwierzytelniania i autoryzacji aplikacji i pobrać tzw. bezpieczną zawartość z całej bazy danych. Wstrzyknięcie SQL może służyć do tworzenia, aktualizowania i usuwania rekordów z bazy danych. Można zatem sformułować zapytanie ograniczone do własnej wyobraźni za pomocą SQL.

Zazwyczaj aplikacja często uruchamia zapytania SQL do bazy danych w wielu celach, czy to w celu pobrania określonych rekordów, tworzenia raportów, uwierzytelniania użytkownika, transakcji CRUD i tak dalej. Atakujący musi po prostu znaleźć zapytanie wejściowe SQL w formularzu wejściowym aplikacji. Zapytanie przygotowane przez formularz może następnie zostać użyte do splotu złośliwej zawartości, tak aby po uruchomieniu zapytania aplikacja zawierała również wstrzyknięty ładunek.

Jedną z idealnych sytuacji jest sytuacja, gdy aplikacja prosi użytkownika o wprowadzenie danych, takich jak nazwa użytkownika lub identyfikator użytkownika. Aplikacja otworzyła tam słaby punkt. Instrukcję SQL można uruchomić nieświadomie. Atakujący korzysta z tego, wstrzykując ładunek, który ma być użyty jako część zapytania SQL i przetworzony przez bazę danych. Na przykład pseudokod po stronie serwera dla operacji POST dla formularza logowania może mieć postać:

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

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

database.execute(stmtSQL);

Powyższy kod jest podatny na atak typu SQL injection, ponieważ dane wejściowe podane w instrukcji SQL poprzez zmienną „uname” i „pass” można manipulować w sposób, który zmieni semantykę instrukcji.

Na przykład możemy zmodyfikować zapytanie, aby było uruchamiane na serwerze bazy danych, tak jak w MySQL.

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

Powoduje to zmodyfikowanie oryginalnej instrukcji SQL w stopniu umożliwiającym ominięcie uwierzytelniania. Jest to poważna luka w zabezpieczeniach, której należy zapobiegać z poziomu kodu.

Obrona przed atakiem typu SQL Injection

Jednym ze sposobów zmniejszenia prawdopodobieństwa ataku typu SQL injection jest zapewnienie, że niefiltrowane ciągi tekstu nie mogą być dołączane do instrukcji SQL przed wykonaniem. Na przykład możemy użyć PreparedStatement do wykonywania wymaganych zadań bazy danych. Ciekawy aspekt PreparedStatement polega na tym, że do bazy danych wysyła wstępnie skompilowaną instrukcję SQL, a nie ciąg. Oznacza to, że zapytanie i dane są osobno przesyłane do bazy danych. Zapobiega to pierwotnej przyczynie ataku SQL injection, ponieważ w SQL injection chodzi o mieszanie kodu i danych, w których dane są w rzeczywistości częścią kodu pod postacią danych. W PreparedStatement , istnieje wiele setXYZ() metody, takie jak setString() . Te metody są używane do filtrowania znaków specjalnych, takich jak cytaty zawarte w instrukcjach SQL.

Na przykład możemy wykonać instrukcję SQL w następujący sposób.

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

Zamiast wstawiać, powiedzmy eno=10125 jako numer pracownika we wpisie możemy zmodyfikować zapytanie za pomocą danych wejściowych takich jak:

eno = 10125 OR 1=1

To całkowicie zmienia wynik zwrócony przez zapytanie.

Przykład

W poniższym przykładowym kodzie pokazaliśmy, jak PreparedStatement może być używany do wykonywania zadań związanych z bazą danych.

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();
      }
   }
}

Przegląd PreparedStatement

Te zadania można również wykonać za pomocą Oświadczenia JDBC interfejs, ale problem polega na tym, że czasami może być dość niepewny, zwłaszcza gdy wykonywane jest dynamiczne polecenie SQL w celu zapytania bazy danych, w której wartości wejściowe użytkownika są łączone z zapytaniami SQL. Jak widzieliśmy, może to być niebezpieczna sytuacja. W większości przypadków Oświadczenie jest całkiem nieszkodliwy, ale PreparedStatement wydaje się być lepszą opcją między tymi dwoma. Zapobiega to łączeniu złośliwych ciągów ze względu na inne podejście do wysyłania instrukcji do bazy danych. Przygotowane oświadczenie używa podstawienia zmiennych zamiast łączenia. Umieszczenie znaku zapytania (?) w zapytaniu SQL oznacza, że ​​zmienna zastępcza zajmie jej miejsce i poda wartość w momencie wykonania zapytania. Pozycja zmiennej podstawienia zajmuje swoje miejsce zgodnie z przypisaną pozycją indeksu parametru w setXYZ() metody.

Ta technika zapobiega atakom SQL injection.

Ponadto Przygotowane oświadczenie implementuje Automatyczne zamykanie. Dzięki temu może pisać w kontekście try-with-resources blokuje się i automatycznie zamyka, gdy wychodzi poza zakres.

Wniosek

Atakowi typu SQL injection można zapobiec tylko poprzez odpowiedzialne pisanie kodu. W rzeczywistości w każdym rozwiązaniu programowym bezpieczeństwo jest w większości łamane z powodu złych praktyk kodowania. W tym miejscu opisaliśmy, czego należy unikać i jak PreparedStatement może nam pomóc w napisaniu bezpiecznego kodu. Aby uzyskać pełny pomysł na wstrzykiwanie SQL, zapoznaj się z odpowiednimi materiałami; Internet jest ich pełen, a dla PreparedStatement , zajrzyj do dokumentacji Java API, aby uzyskać bardziej szczegółowe wyjaśnienie.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Połączenie krzyżowe SQL

  2. Testowanie obciążenia sieciowego za pomocą iPerf

  3. Notacja IDEF1X

  4. Poziomy zgodności i podstawa szacowania kardynalności

  5. Klauzula SQL UNION dla początkujących