Oversigt
I et RDBMS (Relational Database Management System) er der et specifikt sprog - kaldet SQL (Structured Query language) - som bruges til at kommunikere med databasen. Forespørgselssætningerne skrevet i SQL bruges til at manipulere indholdet og strukturen af databasen. En specifik SQL-sætning, der opretter og ændrer strukturen af databasen, kaldes en DDL-sætning (Data Definition Language), og de sætninger, der manipulerer indholdet af databasen, kaldes en DML-sætning (Data Manipulation Language). Motoren, der er knyttet til RDBMS-pakken, analyserer og fortolker SQL-sætningen og returnerer resultatet i overensstemmelse hermed. Dette er den typiske kommunikationsproces med RDBMS - affyr en SQL-sætning og få resultatet tilbage, det er alt. Systemet bedømmer ikke hensigten med et udsagn, der overholder sprogets syntaks og semantiske struktur. Dette betyder også, at der ikke er nogen godkendelses- eller valideringsprocesser til at kontrollere, hvem der affyrede erklæringen, og det privilegium, man har til at få output. En angriber kan ganske enkelt affyre en SQL-sætning med ondsindet hensigt og få information tilbage, som den ikke skal få. For eksempel kan en angriber udføre en SQL-sætning med en ondsindet nyttelast med den harmløse forespørgsel for at kontrollere en webapplikations databaseserver.
Sådan virker det
En angriber kan udnytte denne sårbarhed og bruge den til sin egen fordel. For eksempel kan man omgå en applikations autentificerings- og autorisationsmekanisme og hente såkaldt sikkert indhold fra hele databasen. En SQL-injektion kan bruges til at oprette, opdatere og slette poster fra databasen. Man kan derfor formulere en forespørgsel begrænset til ens egen fantasi med SQL.
Typisk affyrer en applikation ofte SQL-forespørgsler til databasen til adskillige formål, det være sig til at hente bestemte poster, oprette rapporter, godkende bruger, CRUD-transaktioner og så videre. Angriberen skal blot finde en SQL-inputforespørgsel i en applikationsinputform. Forespørgslen, der er udarbejdet af formularen, kan derefter bruges til at sno det ondsindede indhold, så det, når applikationen udløser forespørgslen, også bærer den indsprøjtede nyttelast.
En af de ideelle situationer er, når en applikation beder brugeren om input såsom brugernavn eller bruger-id. Applikationen åbnede et sårbart sted dér. SQL-sætningen kan køres ubevidst. En angriber drager fordel ved at injicere en nyttelast, der skal bruges som en del af SQL-forespørgslen og behandles af databasen. For eksempel kan pseudokoden på serversiden for en POST-handling for en loginformular være:
uname = getRequestString("username"); pass = getRequestString("passwd"); stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "'"; database.execute(stmtSQL);
Den foregående kode er sårbar over for SQL-injektionsangreb, fordi input givet til SQL-sætningen gennem variablen 'uname' og 'pass' kan manipuleres på en måde, der ville ændre sætningens semantik.
For eksempel kan vi ændre forespørgslen til at køre mod databaseserveren, som i MySQL.
stmtSQL = "SELECT * FROM users WHERE user_name = '" + uname + "' AND passwd = '" + pass + "' OR 1=1";
Dette resulterer i at ændre den originale SQL-sætning i en grad, der gør det muligt at omgå godkendelse. Dette er en alvorlig sårbarhed og skal forhindres i koden.
Forsvar mod et SQL-injektionsangreb
En af måderne til at reducere chancen for SQL-injektionsangreb er at sikre, at de ufiltrerede tekststrenge ikke må føjes til SQL-sætningen før udførelse. For eksempel kan vi bruge PreparedStatement til at udføre de nødvendige databaseopgaver. Det interessante aspekt af PreparedStatement er, at den sender en forudkompileret SQL-sætning til databasen i stedet for en streng. Det betyder, at forespørgsel og data sendes separat til databasen. Dette forhindrer hovedårsagen til SQL-injektionsangrebet, for i SQL-injektion er ideen at blande kode og data, hvor dataene faktisk er en del af koden i skikkelse af data. I PreparedStatement , der er flere setXYZ() metoder, såsom setString() . Disse metoder bruges til at filtrere specialtegn, såsom et citat indeholdt i SQL-sætningerne.
For eksempel kan vi udføre en SQL-sætning på følgende måde.
String sql = "SELECT * FROM employees WHERE emp_no = "+eno;
I stedet for at sætte f.eks. eno=10125 som et medarbejdernummer i inputtet, kan vi ændre forespørgslen med input som:
eno = 10125 OR 1=1
Dette ændrer fuldstændigt resultatet, der returneres af forespørgslen.
Et eksempel
I følgende eksempelkode har vi vist hvordan PreparedStatement kan bruges til at udføre databaseopgaver.
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(); } } }
Et glimt af Prepared Statement
Disse job kan også udføres med en JDBC erklæring interface, men problemet er, at det til tider kan være ret usikkert, især når en dynamisk SQL-sætning udføres for at forespørge databasen, hvor brugerinputværdier er sammenkædet med SQL-forespørgslerne. Dette kan være en farlig situation, som vi har set. I de fleste almindelige omstændigheder, Erklæring er ret harmløs, men PreparedStatement synes at være den bedre mulighed mellem de to. Det forhindrer ondsindede strenge i at blive sammenkædet på grund af dens forskellige tilgang til at sende erklæringen til databasen. Forberedt erklæring bruger variabel substitution frem for sammenkædning. Anbringelse af et spørgsmålstegn (?) i SQL-forespørgslen betyder, at en erstatningsvariabel vil træde i stedet og levere værdien, når forespørgslen udføres. Placeringen af substitutionsvariablen indtager sin plads i henhold til den tildelte parameterindeksposition i setXYZ() metoder.
Denne teknik forhindrer den i SQL-injektionsangreb.
Yderligere, PreparedStatement implementerer AutoCloseable. Dette gør det muligt for den at skrive inden for konteksten af en prøv-med-ressourcer blokerer og lukker automatisk, når den går uden for rækkevidde.
Konklusion
Et SQL-injektionsangreb kan kun forhindres ved ansvarligt at skrive koden. Faktisk er sikkerheden i enhver softwareløsning for det meste brudt på grund af dårlig kodningspraksis. Her har vi beskrevet, hvad du skal undgå, og hvordan PreparedStatement kan hjælpe os med at skrive sikker kode. For en komplet idé om SQL-injektion, se passende materialer; Internettet er fyldt med dem, og for PreparedStatement , se i Java API-dokumentationen for en mere detaljeret forklaring.