sql >> Database >  >> RDS >> Database

Hoe een JDBC-toepassing te beschermen tegen SQL-injectie?

Overzicht

In een relationeel databasebeheersysteem (RDBMS) is er een specifieke taal, SQL (Structured Query-taal) genaamd, die wordt gebruikt om met de database te communiceren. De query-instructies die in SQL zijn geschreven, worden gebruikt om de inhoud en structuur van de database te manipuleren. Een specifieke SQL-instructie die de structuur van de database creëert en wijzigt, wordt een DDL-instructie (Data Definition Language) genoemd en de instructies die de inhoud van de database manipuleren, wordt een DML-instructie (Data Manipulation Language) genoemd. De engine die is gekoppeld aan het RDBMS-pakket, analyseert en interpreteert de SQL-instructie en retourneert het resultaat dienovereenkomstig. Dit is het typische communicatieproces met RDBMS:een SQL-instructie afvuren en het resultaat terugkrijgen, dat is alles. Het systeem beoordeelt niet de bedoeling van een uitspraak die voldoet aan de syntaxis en semantische structuur van de taal. Dit betekent ook dat er geen authenticatie- of validatieprocessen zijn om te controleren wie de verklaring heeft afgevuurd en het voorrecht dat men heeft bij het verkrijgen van de uitvoer. Een aanvaller kan eenvoudig een SQL-instructie afvuren met kwade bedoelingen en informatie terugkrijgen die hij niet zou moeten krijgen. Een aanvaller kan bijvoorbeeld een SQL-instructie uitvoeren met een kwaadaardige lading met de onschuldig ogende query om de databaseserver van een webtoepassing te besturen.

Hoe het werkt

Een aanvaller kan deze kwetsbaarheid misbruiken en in zijn eigen voordeel gebruiken. Men kan bijvoorbeeld het authenticatie- en autorisatiemechanisme van een applicatie omzeilen en zogenaamde beveiligde inhoud uit de hele database halen. Een SQL-injectie kan worden gebruikt om records uit de database te maken, bij te werken en te verwijderen. Men kan dus met SQL een vraag formuleren die beperkt is tot de eigen verbeelding.

Gewoonlijk stuurt een toepassing regelmatig SQL-query's naar de database voor tal van doeleinden, of het nu gaat om het ophalen van bepaalde records, het maken van rapporten, het verifiëren van gebruikers, CRUD-transacties, enzovoort. De aanvaller hoeft alleen maar een SQL-invoerquery te vinden in een invoerformulier van een toepassing. De query die door het formulier wordt opgesteld, kan vervolgens worden gebruikt om de kwaadaardige inhoud te verstrengelen, zodat, wanneer de toepassing de query uitvoert, deze ook de geïnjecteerde payload draagt.

Een van de ideale situaties is wanneer een toepassing de gebruiker om invoer vraagt, zoals een gebruikersnaam of gebruikers-ID. De applicatie opende daar een kwetsbare plek. De SQL-instructie kan onbewust worden uitgevoerd. Een aanvaller maakt misbruik door een payload te injecteren die wordt gebruikt als onderdeel van de SQL-query en die door de database wordt verwerkt. De pseudo-code aan de serverzijde voor een POST-bewerking voor een aanmeldingsformulier kan bijvoorbeeld zijn:

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

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

database.execute(stmtSQL);

De voorgaande code is kwetsbaar voor SQL-injectieaanvallen omdat de invoer die aan de SQL-instructie wordt gegeven via de variabelen 'uname' en 'pass' kan worden gemanipuleerd op een manier die de semantiek van de instructie zou veranderen.

We kunnen bijvoorbeeld de query aanpassen om tegen de databaseserver te worden uitgevoerd, zoals in MySQL.

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

Dit resulteert in het wijzigen van de oorspronkelijke SQL-instructie in een mate die het mogelijk maakt om authenticatie te omzeilen. Dit is een ernstige kwetsbaarheid en moet vanuit de code worden voorkomen.

Verdediging tegen een SQL-injectie-aanval

Een van de manieren om de kans op een SQL-injectieaanval te verkleinen, is ervoor te zorgen dat de ongefilterde tekstreeksen niet mogen worden toegevoegd aan de SQL-instructie voordat deze wordt uitgevoerd. We kunnen bijvoorbeeld PreparedStatement . gebruiken om de vereiste databasetaken uit te voeren. Het interessante aspect van PreparedStatement is dat het een vooraf gecompileerde SQL-instructie naar de database stuurt, in plaats van een string. Dit betekent dat query en gegevens afzonderlijk naar de database worden verzonden. Dit voorkomt de hoofdoorzaak van de SQL-injectie-aanval, omdat bij SQL-injectie het idee is om code en gegevens te mixen, waarbij de gegevens feitelijk een deel van de code zijn in de gedaante van gegevens. In PreparedStatement , er zijn meerdere setXYZ() methoden, zoals setString() . Deze methoden worden gebruikt om speciale tekens te filteren, zoals een aanhalingsteken in de SQL-instructies.

We kunnen bijvoorbeeld een SQL-statement op de volgende manier uitvoeren.

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

In plaats van bijvoorbeeld eno=10125 . te zetten als een medewerkernummer in de invoer, kunnen we de vraag met de invoer wijzigen, zoals:

eno = 10125 OR 1=1

Dit verandert het resultaat van de zoekopdracht volledig.

Een voorbeeld

In de volgende voorbeeldcode hebben we laten zien hoe PreparedStatement kan worden gebruikt om databasetaken uit te voeren.

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

Een glimp van PreparedStatement

Deze taken kunnen ook worden uitgevoerd met een JDBC Statement interface, maar het probleem is dat het soms behoorlijk onveilig kan zijn, vooral wanneer een dynamische SQL-instructie wordt uitgevoerd om de database te doorzoeken waar gebruikersinvoerwaarden worden samengevoegd met de SQL-query's. Dit kan een gevaarlijke situatie zijn, zoals we hebben gezien. In de meeste gewone omstandigheden, Verklaring is vrij ongevaarlijk, maar PreparedStatement lijkt de betere optie tussen de twee te zijn. Het voorkomt dat kwaadaardige strings aaneengeschakeld worden vanwege de verschillende benadering bij het verzenden van de verklaring naar de database. PreparedStatement gebruikt variabele substitutie in plaats van aaneenschakeling. Het plaatsen van een vraagteken (?) in de SQL-query betekent dat een vervangende variabele zijn plaats zal innemen en de waarde zal leveren wanneer de query wordt uitgevoerd. De positie van de substitutievariabele neemt zijn plaats in volgens de toegewezen parameterindexpositie in de setXYZ() methoden.

Deze techniek voorkomt een SQL-injectieaanval.

Verder, PreparedStatement implementeert AutoCloseable. Hierdoor kan het schrijven binnen de context van een try-with-resources blok en sluit automatisch wanneer het buiten bereik gaat.

Conclusie

Een SQL-injectie-aanval kan alleen worden voorkomen door de code op een verantwoorde manier te schrijven. In feite wordt in elke softwareoplossing de beveiliging meestal geschonden door slechte coderingspraktijken. Hier hebben we beschreven wat u moet vermijden en hoe PreparedStatement kan ons helpen bij het schrijven van veilige code. Voor een volledig idee over SQL-injectie, raadpleeg de juiste materialen; het internet staat er vol mee, en voor PreparedStatement , kijk in de Java API-documentatie voor een meer gedetailleerde uitleg.


  1. De beste manier om miljoenen rijen op ID te verwijderen

  2. Hoe de gaten in velden voor automatisch verhogen op te vullen?

  3. ORACLE - ORA-01843:geen geldige maand

  4. Een CLOB-kolom opvragen in Oracle