Database
 sql >> Database >  >> RDS >> Database

Come proteggere un'applicazione JDBC contro SQL injection

Panoramica

In un Relational Database Management System (RDBMS), esiste un linguaggio specifico, chiamato SQL (Structured Query Language), che viene utilizzato per comunicare con il database. Le istruzioni di query scritte in SQL vengono utilizzate per manipolare il contenuto e la struttura del database. Un'istruzione SQL specifica che crea e modifica la struttura del database è chiamata istruzione DDL (Data Definition Language) e le istruzioni che manipolano il contenuto del database sono chiamate istruzione DML (Data Manipulation Language). Il motore associato al pacchetto RDBMS analizza e interpreta l'istruzione SQL e restituisce il risultato di conseguenza. Questo è il tipico processo di comunicazione con RDBMS:attiva un'istruzione SQL e recupera il risultato, tutto qui. Il sistema non giudica l'intenzione di alcuna affermazione che aderisce alla sintassi e alla struttura semantica del linguaggio. Ciò significa anche che non ci sono processi di autenticazione o convalida per verificare chi ha attivato l'istruzione e il privilegio che si ha per ottenere l'output. Un utente malintenzionato può semplicemente lanciare un'istruzione SQL con intenti dannosi e recuperare informazioni che non dovrebbe ottenere. Ad esempio, un utente malintenzionato può eseguire un'istruzione SQL con un payload dannoso con la query dall'aspetto innocuo per controllare il server di database di un'applicazione Web.

Come funziona

Un utente malintenzionato può sfruttare questa vulnerabilità e usarla a proprio vantaggio. Ad esempio, è possibile aggirare il meccanismo di autenticazione e autorizzazione di un'applicazione e recuperare i cosiddetti contenuti protetti dall'intero database. È possibile utilizzare un'iniezione SQL per creare, aggiornare ed eliminare record dal database. Si può, quindi, formulare una query limitata alla propria immaginazione con SQL.

In genere, un'applicazione attiva frequentemente query SQL al database per numerosi scopi, sia per recuperare determinati record, creare report, autenticare utenti, transazioni CRUD e così via. L'attaccante deve semplicemente trovare una query di input SQL all'interno di un modulo di input dell'applicazione. La query preparata dal modulo può quindi essere utilizzata per intrecciare il contenuto dannoso in modo che, quando l'applicazione attiva la query, trasporti anche il payload iniettato.

Una delle situazioni ideali è quando un'applicazione chiede all'utente un input come nome utente o ID utente. L'applicazione ha aperto un punto vulnerabile lì. L'istruzione SQL può essere eseguita inconsapevolmente. Un utente malintenzionato ne trae vantaggio iniettando un payload che deve essere utilizzato come parte della query SQL ed elaborato dal database. Ad esempio, lo pseudocodice lato server per un'operazione POST per un modulo di accesso può essere:

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

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

database.execute(stmtSQL);

Il codice precedente è vulnerabile all'attacco SQL injection perché l'input fornito all'istruzione SQL tramite le variabili 'uname' e 'pass' può essere manipolato in modo da alterare la semantica dell'istruzione.

Ad esempio, possiamo modificare la query in modo che venga eseguita sul server del database, come in MySQL.

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

Ciò si traduce nella modifica dell'istruzione SQL originale in una misura che consente di ignorare l'autenticazione. Questa è una grave vulnerabilità e deve essere prevenuta dall'interno del codice.

Difesa da un attacco SQL injection

Uno dei modi per ridurre la possibilità di un attacco SQL injection è garantire che le stringhe di testo non filtrate non debbano essere aggiunte all'istruzione SQL prima dell'esecuzione. Ad esempio, possiamo utilizzare PreparedStatement per eseguire le attività di database richieste. L'aspetto interessante di PreparedStatement è che invia un'istruzione SQL precompilata al database, anziché una stringa. Ciò significa che query e dati vengono inviati separatamente al database. Ciò impedisce la causa principale dell'attacco SQL injection, perché in SQL injection l'idea è di mescolare codice e dati in cui i dati sono effettivamente una parte del codice sotto forma di dati. In PreparedStatement , ci sono più setXYZ() metodi, come setString() . Questi metodi vengono utilizzati per filtrare caratteri speciali come una citazione contenuta nelle istruzioni SQL.

Ad esempio, possiamo eseguire un'istruzione SQL nel modo seguente.

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

Invece di mettere, diciamo, eno=10125 come numero di dipendente nell'input, possiamo modificare la query con l'input come:

eno = 10125 OR 1=1

Questo cambia completamente il risultato restituito dalla query.

Un esempio

Nel codice di esempio seguente, abbiamo mostrato come PreparedStatement può essere utilizzato per eseguire attività di database.

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

Uno sguardo a PreparedStatement

Questi lavori possono essere eseguiti anche con una Dichiarazione JDBC interfaccia, ma il problema è che a volte può essere piuttosto insicuro, specialmente quando viene eseguita un'istruzione SQL dinamica per interrogare il database in cui i valori di input dell'utente sono concatenati con le query SQL. Questa può essere una situazione pericolosa, come abbiamo visto. Nella maggior parte delle circostanze ordinarie, Dichiarazione è abbastanza innocuo, ma PreparedStatement sembra essere l'opzione migliore tra i due. Impedisce la concatenazione di stringhe dannose a causa del suo diverso approccio nell'invio dell'istruzione al database. Dichiarazione preparata utilizza la sostituzione delle variabili anziché la concatenazione. Mettere un punto interrogativo (?) nella query SQL significa che una variabile sostitutiva prenderà il suo posto e fornirà il valore quando la query viene eseguita. La posizione della variabile di sostituzione prende il suo posto in base alla posizione dell'indice del parametro assegnato in setXYZ() metodi.

Questa tecnica impedisce l'attacco SQL injection.

Inoltre, PreparedStatement implementa AutoCloseable. Ciò gli consente di scrivere nel contesto di un prova con le risorse si blocca e si chiude automaticamente quando esce dall'ambito.

Conclusione

Un attacco SQL injection può essere prevenuto solo scrivendo il codice in modo responsabile. In effetti, in qualsiasi soluzione software la sicurezza viene per lo più violata a causa di cattive pratiche di codifica. Qui abbiamo descritto cosa evitare e come PreparedStatement può aiutarci a scrivere codice sicuro. Per un'idea completa sull'iniezione SQL, fare riferimento ai materiali appropriati; Internet ne è pieno e, per PreparedStatement , esamina la documentazione dell'API Java per una spiegazione più dettagliata.