sql >> Databasteknik >  >> RDS >> Database

Hur man skyddar en JDBC-applikation mot SQL-injektion

Översikt

I ett RDBMS (Relational Database Management System) finns ett specifikt språk – kallat SQL (Structured Query language) – som används för att kommunicera med databasen. Frågesatserna skrivna i SQL används för att manipulera databasens innehåll och struktur. En specifik SQL-sats som skapar och modifierar strukturen i databasen kallas en DDL-sats (Data Definition Language) och de satser som manipulerar innehållet i databasen kallas en DML-sats (Data Manipulation Language). Motorn som är associerad med RDBMS-paketet analyserar och tolkar SQL-satsen och returnerar resultatet därefter. Detta är den typiska processen för kommunikation med RDBMS – avfyra en SQL-sats och få tillbaka resultatet, det är allt. Systemet bedömer inte avsikten med något uttalande som ansluter sig till språkets syntax och semantiska struktur. Detta betyder också att det inte finns några autentiserings- eller valideringsprocesser för att kontrollera vem som avfyrade uttalandet och privilegiet man har för att få utdata. En angripare kan helt enkelt avfyra en SQL-sats med uppsåt och få tillbaka information som den inte är tänkt att få. Till exempel kan en angripare köra en SQL-sats med en skadlig nyttolast med den ofarliga frågan för att kontrollera en webbapplikations databasserver.

Hur det fungerar

En angripare kan utnyttja denna sårbarhet och använda den till sin egen fördel. Till exempel kan man kringgå en applikations autentiserings- och auktoriseringsmekanism och hämta så kallat säkert innehåll från hela databasen. En SQL-injektion kan användas för att skapa, uppdatera och ta bort poster från databasen. Man kan därför formulera en fråga begränsad till sin egen fantasi med SQL.

Vanligtvis skickar en applikation ofta SQL-frågor till databasen för många syften, vare sig det är för att hämta vissa poster, skapa rapporter, autentisera användare, CRUD-transaktioner och så vidare. Angriparen behöver helt enkelt hitta en SQL-inmatningsfråga i något applikationsinmatningsformulär. Frågan som förbereds av formuläret kan sedan användas för att tvinna ihop det skadliga innehållet så att det även bär den injicerade nyttolasten när applikationen aktiverar frågan.

En av de idealiska situationerna är när en applikation ber användaren om input som användarnamn eller användar-ID. Applikationen öppnade upp en sårbar plats där. SQL-satsen kan köras omedvetet. En angripare utnyttjar genom att injicera en nyttolast som ska användas som en del av SQL-frågan och bearbetas av databasen. Till exempel kan pseudokoden på serversidan för en POST-operation för ett inloggningsformulär vara:

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

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

database.execute(stmtSQL);

Den föregående koden är sårbar för SQL-injektionsattack eftersom indata som ges till SQL-satsen genom variablerna 'uname' och 'pass' kan manipuleras på ett sätt som skulle ändra satsens semantik.

Till exempel kan vi modifiera frågan så att den körs mot databasservern, som i MySQL.

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

Detta resulterar i att den ursprungliga SQL-satsen ändras i en grad som gör att man kan kringgå autentisering. Detta är en allvarlig sårbarhet och måste förhindras från koden.

Försvar mot en SQL-injektionsattack

Ett av sätten att minska risken för SQL-injektionsattack är att säkerställa att de ofiltrerade textsträngarna inte får läggas till SQL-satsen innan de körs. Till exempel kan vi använda PreparedStatement för att utföra nödvändiga databasuppgifter. Den intressanta aspekten av PreparedStatement är att den skickar en förkompilerad SQL-sats till databasen, snarare än en sträng. Detta innebär att fråga och data skickas separat till databasen. Detta förhindrar grundorsaken till SQL-injektionsattacken, för i SQL-injektion är tanken att blanda kod och data där data faktiskt är en del av koden i skepnad av data. I PreparedStatement , det finns flera setXYZ() metoder, såsom setString() . Dessa metoder används för att filtrera specialtecken såsom ett citat i SQL-satserna.

Till exempel kan vi köra en SQL-sats på följande sätt.

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

Istället för att säg eno=10125 som ett anställdsnummer i inmatningen kan vi modifiera frågan med inmatningen som:

eno = 10125 OR 1=1

Detta ändrar helt resultatet som returneras av frågan.

Ett exempel

I följande exempelkod har vi visat hur PreparedStatement kan användas för att utföra databasuppgifter.

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

En glimt av PreparedStatement

Dessa jobb kan också utföras med ett JDBC uttalande gränssnittet, men problemet är att det ibland kan vara ganska osäkert, särskilt när en dynamisk SQL-sats exekveras för att fråga databasen där användarinmatningsvärden är sammanlänkade med SQL-frågorna. Detta kan vara en farlig situation, som vi har sett. I de flesta vanliga omständigheter, Statement är ganska ofarligt, men PreparedStatement verkar vara det bättre alternativet mellan de två. Det förhindrar skadliga strängar från att sammanfogas på grund av dess olika tillvägagångssätt när det gäller att skicka uttalandet till databasen. PreparedStatement använder variabel substitution snarare än sammanlänkning. Att placera ett frågetecken (?) i SQL-frågan betyder att en ersättningsvariabel kommer att ta dess plats och ge värdet när frågan exekveras. Positionen för substitutionsvariabeln tar sin plats enligt den tilldelade parameterindexpositionen i setXYZ() metoder.

Denna teknik förhindrar den från SQL-injektionsattack.

Vidare, PreparedStatement implementerar AutoCloseable. Detta gör det möjligt för den att skriva inom ramen för en prova-med-resurser blockera och stängs automatiskt när det går utanför räckvidden.

Slutsats

En SQL-injektionsattack kan endast förhindras genom att skriva koden på ett ansvarsfullt sätt. Faktum är att säkerheten i alla mjukvarulösningar till största delen bryts på grund av dålig kodning. Här har vi beskrivit vad du bör undvika och hur PreparedStatement kan hjälpa oss att skriva säker kod. För en fullständig idé om SQL-injektion, se lämpligt material; Internet är fullt av dem, och för PreparedStatement , titta i Java API-dokumentationen för en mer detaljerad förklaring.


  1. Views SELECT innehåller en underfråga i FROM-satsen

  2. Köra SQL-databasunderhållsuppgifter med SQLCMD

  3. 15 Användbara MySQL/MariaDB-prestandajusteringar och optimeringstips

  4. 12c Adaptiva planer i SQL-utvecklare