sql >> Databáze >  >> RDS >> Database

Jak chránit aplikaci JDBC proti vkládání SQL

Přehled

V systému správy relačních databází (RDBMS) existuje specifický jazyk – nazývaný SQL (Structured Query language) – který se používá ke komunikaci s databází. Příkazy dotazu napsané v SQL se používají k manipulaci s obsahem a strukturou databáze. Specifický příkaz SQL, který vytváří a upravuje strukturu databáze, se nazývá příkaz DDL (Data Definition Language) a příkazy, které manipulují s obsahem databáze, se nazývají příkazy DML (Data Manipulation Language). Modul přidružený k balíčku RDBMS analyzuje a interpretuje příkaz SQL a odpovídajícím způsobem vrátí výsledek. Toto je typický proces komunikace s RDBMS – spusťte příkaz SQL a získejte zpět výsledek, to je vše. Systém neposuzuje záměr žádného výroku, který se drží syntaxe a sémantické struktury jazyka. To také znamená, že neexistují žádné ověřovací nebo ověřovací procesy, které by kontrolovaly, kdo spustil příkaz, a oprávnění, které má jeden k získání výstupu. Útočník může jednoduše spustit příkaz SQL se zlým úmyslem a získat zpět informace, které by neměl dostat. Útočník může například provést příkaz SQL se škodlivým obsahem pomocí neškodně vyhlížejícího dotazu k ovládání databázového serveru webové aplikace.

Jak to funguje

Útočník může tuto zranitelnost využít a využít ji ve svůj prospěch. Například lze obejít autentizační a autorizační mechanismus aplikace a získat takzvaný zabezpečený obsah z celé databáze. SQL injection lze použít k vytvoření, aktualizaci a odstranění záznamů z databáze. Je tedy možné formulovat dotaz omezený na vlastní představivost pomocí SQL.

Aplikace obvykle často spouští dotazy SQL do databáze pro různé účely, ať už jde o načítání určitých záznamů, vytváření sestav, ověřování uživatele, transakce CRUD a tak dále. Útočník jednoduše potřebuje najít vstupní dotaz SQL v nějakém vstupním formuláři aplikace. Dotaz připravený formulářem pak lze použít k provázání škodlivého obsahu tak, že když aplikace spustí dotaz, přenese také vložené užitečné zatížení.

Jednou z ideálních situací je, když aplikace požádá uživatele o vstup, jako je uživatelské jméno nebo ID uživatele. Aplikace tam otevřela zranitelné místo. Příkaz SQL může být spuštěn nevědomě. Útočník využívá výhody tím, že vloží užitečné zatížení, které má být použito jako součást dotazu SQL a zpracováno databází. Například pseudokód na straně serveru pro operaci POST pro přihlašovací formulář může být:

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

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

database.execute(stmtSQL);

Předchozí kód je zranitelný vůči útoku SQL injection, protože vstup zadaný příkazu SQL prostřednictvím proměnných ‚uname‘ a ‚pass‘ lze manipulovat způsobem, který by změnil sémantiku příkazu.

Můžeme například upravit dotaz tak, aby běžel proti databázovému serveru, jako v MySQL.

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

Výsledkem je úprava původního příkazu SQL do takové míry, že lze obejít autentizaci. Toto je závažná chyba zabezpečení a je třeba jí zabránit v kódu.

Obrana proti útoku SQL Injection

Jedním ze způsobů, jak snížit pravděpodobnost útoku SQL injection, je zajistit, aby se nefiltrované textové řetězce nesměly připojit k příkazu SQL před provedením. Můžeme například použít PreparedStatement k provádění požadovaných databázových úloh. Zajímavý aspekt PreparedStatement spočívá v tom, že do databáze odešle předkompilovaný příkaz SQL, nikoli řetězec. To znamená, že dotaz a data jsou do databáze odesílány samostatně. To zabraňuje hlavní příčině útoku SQL injection, protože v SQL injection je myšlenkou smíchat kód a data, přičemž data jsou ve skutečnosti součástí kódu v masce dat. V PreparedStatement , existuje více setXYZ() metody, jako je setString() . Tyto metody se používají k filtrování speciálních znaků, jako jsou citace obsažené v příkazech SQL.

Například můžeme provést příkaz SQL následujícím způsobem.

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

Místo zadání řekněme eno=10125 jako číslo zaměstnance ve vstupu můžeme upravit dotaz pomocí vstupu jako:

eno = 10125 OR 1=1

To zcela změní výsledek vrácený dotazem.

Příklad

V následujícím příkladu kódu jsme ukázali, jak PreparedStatement lze použít k provádění databázových úloh.

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

Nahlédnutí do PreparedStatement

Tyto úlohy lze také provést pomocí Prohlášení JDBC rozhraní, ale problém je v tom, že to může být občas docela nejisté, zvláště když je spuštěn dynamický SQL příkaz k dotazu na databázi, kde jsou uživatelské vstupní hodnoty zřetězeny s SQL dotazy. To může být nebezpečná situace, jak jsme viděli. Za nejběžnějších okolností Prohlášení je docela neškodný, ale PreparedStatement Zdá se, že je to lepší volba mezi těmito dvěma. Zabraňuje zřetězení škodlivých řetězců kvůli odlišnému přístupu při odesílání výpisu do databáze. PreparedStatement používá proměnnou substituci spíše než zřetězení. Umístění otazníku (?) do SQL dotazu znamená, že náhradní proměnná nahradí jeho místo a dodá hodnotu, když je dotaz vykonán. Pozice substituční proměnné zaujímá své místo podle přiřazeného parametru index pozice v setXYZ() metody.

Tato technika zabraňuje útoku SQL injection.

Dále PreparedStatement implementuje AutoCloseable. To mu umožňuje psát v kontextu zkuste se zdroji blokovat a automaticky se zavře, když přejde mimo rozsah.

Závěr

Útoku SQL injection lze zabránit pouze odpovědným psaním kódu. Ve skutečnosti je bezpečnost v jakémkoli softwarovém řešení většinou narušena kvůli špatným praktikám kódování. Zde jsme popsali, čemu se vyhnout a jak PreparedStatement nám může pomoci při psaní zabezpečeného kódu. Úplnou představu o SQL injection naleznete v příslušných materiálech; je jich plný internet a pro PreparedStatement , podívejte se do dokumentace Java API pro podrobnější vysvětlení.


  1. Mám použít pravidlo CASCADE DELETE?

  2. Jak zapisovat pomocí BCP na vzdálený SQL Server?

  3. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:Selhání komunikačního odkazu

  4. Jak přidat oddělovač do zřetězeného řetězce v MySQL – CONCAT_WS()