Java: stringa di escape per impedire l'iniezione SQL


146

Sto cercando di mettere in atto un'iniezione anti sql in java e sto trovando molto difficile lavorare con la funzione di stringa "replaceAll". Alla fine ho bisogno di una funzione che converta qualsiasi esistente \in \\, qualsiasi "in \", 'in \'e qualsiasi \nin \\nmodo che quando la stringa viene valutata da iniezioni di MySQL SQL verrà bloccata.

Ho preso un po 'di codice con cui stavo lavorando e tutte \\\\\\\\\\\le funzioni che mi stanno facendo impazzire. Se qualcuno dovesse avere un esempio di questo, lo apprezzerei molto.


1
Va bene, sono giunto alla conclusione che PreparedStatements è la strada da percorrere, tuttavia, sulla base degli obiettivi attuali, devo procedere come previsto inizialmente e mettere un filtro in atto per il momento e una volta raggiunto l'attuale traguardo posso tornare indietro e riformattare il database per la preparazione. Nel frattempo, per mantenere lo slancio, qualcuno ha una soluzione per sfuggire efficacemente ai suddetti caratteri per MySQL, dato che Java e il suo sistema di espressione regolare sono un dolore assoluto per capire il numero di fughe necessarie ....
Scott Bonner,

2
Non tutte le istruzioni SQL sono parametrizzabili, ad esempio "SET ROLE role_name" o "LISTEN channel_name"
Neil McGuigan

1
@NeilMcGuigan Yep. La maggior parte dei driver rifiuterà anche di parametrizzare qualcosa di simile CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?poiché l'istruzione principale è una dichiarazione DDL , anche se la parte che si sta tentando di parametrizzare è in realtà DML .
SeldomNeedy,

Risposte:


249

PreparedStatements è la strada da percorrere, perché rende impossibile l'iniezione di SQL. Ecco un semplice esempio prendendo l'input dell'utente come parametri:

public insertUser(String name, String email) {
   Connection conn = null;
   PreparedStatement stmt = null;
   try {
      conn = setupTheDatabaseConnectionSomehow();
      stmt = conn.prepareStatement("INSERT INTO person (name, email) values (?, ?)");
      stmt.setString(1, name);
      stmt.setString(2, email);
      stmt.executeUpdate();
   }
   finally {
      try {
         if (stmt != null) { stmt.close(); }
      }
      catch (Exception e) {
         // log this error
      }
      try {
         if (conn != null) { conn.close(); }
      }
      catch (Exception e) {
         // log this error
      }
   }
}

Indipendentemente da quali caratteri siano presenti nel nome e nell'e-mail, tali caratteri verranno inseriti direttamente nel database. Non influiranno in alcun modo sull'istruzione INSERT.

Esistono diversi metodi di impostazione per diversi tipi di dati, quello che usi dipende dai campi del tuo database. Ad esempio, se nel database è presente una colonna INTEGER, è necessario utilizzare un setIntmetodo. La documentazione di PreparedStatement elenca tutti i diversi metodi disponibili per impostare e ottenere dati.


1
con questo metodo puoi trattare ogni parametro come una stringa ed essere ancora al sicuro? Sto cercando di trovare un modo per aggiornare la mia architettura esistente in modo sicuro senza dover ricostruire l'intero livello del database ...
Scott Bonner,

1
Tutti i SQL dinqmic sono solo stringhe, quindi questa non è la domanda da porre. Non ho familiarità con PrepareStatement, quindi la vera domanda è: genera una query con parametri che può quindi essere eseguita con ExecuteUpdate. Se sì, va bene. In caso contrario, sta semplicemente nascondendo il problema e potresti non avere alcuna opzione sicura tranne riprogettare il livello del database. Gestire l'iniezione SQL è una di quelle cose che devi progettare fin dall'inizio; non è qualcosa che puoi aggiungere facilmente in seguito.
Cylon Cat,

2
Se stai inserendo un campo INTEGER, ti consigliamo di usare un 'setInt'. Allo stesso modo, altri campi di database numerici utilizzerebbero altri setter. Ho pubblicato un collegamento ai documenti PreparedStatement che elencano tutti i tipi di setter.
Kaleb Brasee,

2
Sì Cylon, PreparedStatements generano query con parametri.
Kaleb Brasee,

2
@Kaleb Brasee, grazie. Buono a sapersi. Gli strumenti sono diversi in ogni ambiente, ma scendere alle query con parametri è la risposta fondamentale.
Cylon Cat,

46

L'unico modo per prevenire l'iniezione di SQL è con SQL parametrizzato. Semplicemente non è possibile costruire un filtro più intelligente delle persone che hackerano SQL per vivere.

Quindi usa i parametri per tutti gli input, gli aggiornamenti e le clausole where. Dynamic SQL è semplicemente una porta aperta per gli hacker e include SQL dinamico nelle procedure memorizzate. Parametrizzare, parametrizzare, parametrizzare.


11
E anche l'SQL parametrizzato non è una garanzia al 100%. Ma è un ottimo inizio.
Duffymo,

2
@duffymo, sono d'accordo che nulla è mai sicuro al 100%. Hai un esempio di iniezione SQL che funzionerà anche con SQL parametrizzato?
Cylon Cat,

3
@Cylon Cat: Certo, quando un pezzo di SQL (come @WhereClause o @tableName) viene passato come parametro, concatenato in SQL ed eseguito in modo dinamico. L'iniezione SQL si verifica quando si consente agli utenti di scrivere il codice. Non importa se acquisisci il loro codice come parametro o meno.
Steve Kass,

16
A proposito, non so perché questo non sia più menzionato, ma lavorare con PreparedStatements è anche molto più facile e molto più leggibile. Solo questo probabilmente li rende predefiniti per ogni programmatore che li conosce.
Edan Maor,

3
Si noti che PreparedStatements per alcuni database è MOLTO costoso da creare, quindi se è necessario eseguirne molti, misurare entrambi i tipi.
Thorbjørn Ravn Andersen,

36

Se davvero non è possibile utilizzare l' opzione di difesa 1: istruzioni preparate (query con parametri) o l' opzione di difesa 2: procedure memorizzate , non creare il proprio strumento, utilizzare l' API OWASP Enterprise Security . Dal OWASP ESAPI ospitato su Google Code:

Non scrivere i tuoi controlli di sicurezza! Reinventare la ruota quando si tratta di sviluppare controlli di sicurezza per ogni applicazione Web o servizio Web comporta perdite di tempo e enormi falle di sicurezza. I toolkit OWASP Enterprise Security API (ESAPI) aiutano gli sviluppatori di software a proteggersi dai difetti di progettazione e implementazione relativi alla sicurezza.

Per ulteriori dettagli, consultare Prevenzione dell'iniezione di SQL in Java e Cheat Sheet di Prevenzione dell'iniezione di SQL .

Presta particolare attenzione all'opzione Difesa 3: fuga da tutti gli input forniti dall'utente che introduce il progetto ESAPI OWASP ).


4
L'ESAPI sembra oggi defunto. Su AWS c'è WAF che può aiutare contro l'iniezione di SQL, XSS ecc. Ci sono altre alternative a questo punto?
ChrisOdney,

@ChrisOdney Un WAF può essere facilmente bypassato. La maggior parte dei framework implementa già la propria prevenzione SQL Injection in cui sfuggono automaticamente ai parametri. Alternative per progetti legacy: owasp.org/index.php/…
Javan R.

19

(Questo è in risposta al commento del PO sotto la domanda originale; sono completamente d'accordo sul fatto che PreparedStatement è lo strumento per questo lavoro, non regex.)

Quando dici \n, intendi la sequenza \+ no un carattere di avanzamento riga? Se è \+ n, l'attività è piuttosto semplice:

s = s.replaceAll("['\"\\\\]", "\\\\$0");

Per abbinare una barra rovesciata nell'input, ne inserisci quattro nella stringa regex. Per inserire una barra rovesciata nell'output, ne inserisci quattro nella stringa di sostituzione. Questo presuppone che tu stia creando le regex e le sostituzioni sotto forma di valori letterali String Java. Se li crei in un altro modo (ad esempio, leggendoli da un file), non devi fare tutto questo doppio escape.

Se si dispone di un carattere di avanzamento riga nell'input e si desidera sostituirlo con una sequenza di escape, è possibile effettuare un secondo passaggio sull'input con questo:

s = s.replaceAll("\n", "\\\\n");

O forse vuoi due barre rovesciate (non sono troppo chiaro su questo):

s = s.replaceAll("\n", "\\\\\\\\n");

1
Grazie per il commento, mi piace il modo in cui hai fatto tutti i personaggi in uno, lo stavo facendo nel modo di espressione meno regolare di un sostituto di tutti per ciascuno ... Non sono sicuro di come assegnare la risposta a questa domanda ora . In definitiva PreparedStatements è la risposta, ma per il mio obiettivo attuale la tua risposta è la risposta di cui ho bisogno, saresti arrabbiato se dessi la risposta a una delle risposte della precedente dichiarazione preparata, o c'è un modo per condividere la risposta tra una coppia?
Scott Bonner,

1
Poiché questo è solo un kludge temporaneo, vai avanti e accetta una delle risposte PreparedStatement.
Alan Moore,

12

PreparedStatements è la strada da percorrere nella maggior parte, ma non in tutti i casi. A volte ti troverai in una situazione in cui una query, o parte di essa, deve essere costruita e archiviata come stringa per un uso successivo. Consulta il Cheat Sheet di SQL Injection Prevention sul sito OWASP per maggiori dettagli e API in diversi linguaggi di programmazione.


1
I cheat sheet di OWASP sono stati spostati su GitHub. Il cheat sheet di SQL Injection è ora qui: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/…
theferrit32

10

Le dichiarazioni preparate sono la soluzione migliore, ma se hai davvero bisogno di farlo manualmente puoi anche usare la StringEscapeUtilsclasse dalla libreria Apache Commons-Lang. Ha un escapeSql(String)metodo, che puoi usare:

import org.apache.commons.lang.StringEscapeUtils; … String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);


2
Per riferimento: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… Comunque, questo metodo sfugge solo alle virgolette e non sembra impedire gli attacchi SQL Injection.
Paco Abato,

11
Questo è stato rimosso dalle ultime versioni perché sfuggiva solo alle virgolette singole
Pini Cheyni,

2
Questa risposta dovrebbe essere eliminata perché non impedisce l'iniezione sql.
Javan R.,

9

L'uso di un'espressione regolare per rimuovere il testo che potrebbe causare un'iniezione SQL sembra che l'istruzione SQL venga inviata al database tramite un Statementanziché un PreparedStatement.

Uno dei modi più semplici per prevenire un'iniezione SQL in primo luogo è utilizzare a PreparedStatement, che accetta i dati per sostituirli in un'istruzione SQL utilizzando segnaposto, che non si basa su concatenazioni di stringhe per creare un'istruzione SQL da inviare al database.

Per ulteriori informazioni, l' utilizzo di istruzioni preparate da The Java Tutorials sarebbe un buon punto di partenza.


6

Nel caso in cui tu abbia a che fare con un sistema legacy o hai troppi posti in cui passare a PreparedStatements in un tempo troppo breve, ad esempio se c'è un ostacolo all'utilizzo delle migliori pratiche suggerite da altre risposte, puoi provare AntiSQLFilter


5

È necessario il seguente codice di seguito. A prima vista, potrebbe sembrare un vecchio codice che ho inventato. Tuttavia, quello che ho fatto è stato guardare il codice sorgente per http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Quindi, ho esaminato attentamente il codice di setString (int parameterIndex, String x) per trovare i caratteri che sfugge e personalizzarlo sulla mia classe in modo che possa essere utilizzato per gli scopi di cui hai bisogno. Dopotutto, se questo è l'elenco di personaggi a cui Oracle sfugge, sapere che questo è davvero confortante dal punto di vista della sicurezza. Forse Oracle ha bisogno di una spinta per aggiungere un metodo simile a questo per la prossima versione di Java.

public class SQLInjectionEscaper {

    public static String escapeString(String x, boolean escapeDoubleQuotes) {
        StringBuilder sBuilder = new StringBuilder(x.length() * 11/10);

        int stringLength = x.length();

        for (int i = 0; i < stringLength; ++i) {
            char c = x.charAt(i);

            switch (c) {
            case 0: /* Must be escaped for 'mysql' */
                sBuilder.append('\\');
                sBuilder.append('0');

                break;

            case '\n': /* Must be escaped for logs */
                sBuilder.append('\\');
                sBuilder.append('n');

                break;

            case '\r':
                sBuilder.append('\\');
                sBuilder.append('r');

                break;

            case '\\':
                sBuilder.append('\\');
                sBuilder.append('\\');

                break;

            case '\'':
                sBuilder.append('\\');
                sBuilder.append('\'');

                break;

            case '"': /* Better safe than sorry */
                if (escapeDoubleQuotes) {
                    sBuilder.append('\\');
                }

                sBuilder.append('"');

                break;

            case '\032': /* This gives problems on Win32 */
                sBuilder.append('\\');
                sBuilder.append('Z');

                break;

            case '\u00a5':
            case '\u20a9':
                // escape characters interpreted as backslash by mysql
                // fall through

            default:
                sBuilder.append(c);
            }
        }

        return sBuilder.toString();
    }
}

2
Penso che questo codice sia la versione decompilata del codice sorgente nel link sopra. Ora in una versione più recente mysql-connector-java-xxx, le dichiarazioni case '\u00a5'e case '\u20a9'sembrano essere state rimosse
circa il

ho provato sqlmap con il tuo codice e non mi ha protetto dall'attacco frist `Tipo: blind su base booleana Titolo: AND blind su base booleana - WHERE o HAVING clausola Payload: q = 1% 'AND 5430 = 5430 AND'% ' = ''
shareef

Mi dispiace che abbia funzionato ma stavo visualizzando gli ultimi risultati della sessione memorizzata .. Ho mantenuto il commento per futuri simili ..
Shareef

Puoi usare org.ostermiller.utils.StringHelper.escapeSQL()o com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi il

1
È importante notare la licenza GPLv2 sul codice originale da cui è stata copiata per chiunque si imbatta in questo. Non sono un avvocato ma consiglio vivamente di non utilizzare questa risposta nel progetto a meno che non siate pienamente consapevoli delle implicazioni dell'inclusione di questo codice di licenza.
Nick Spacek,

0

Dopo aver cercato un sacco di soluzioni per prevenire sqlmap dall'iniezione di sql, nel caso di un sistema legacy che non può applicare dichiarazioni preparate ovunque.

argomento java-security-cross-site-scripting-xss-and-sql-injection ERA LA SOLUZIONE

ho provato la soluzione di @Richard ma non ha funzionato nel mio caso. ho usato un filtro

L'obiettivo di questo filtro è avvolgere la richiesta in un wrapper MyHttpRequestWrapper con codifica propria che trasforma:

i parametri HTTP con caratteri speciali (<,>, ', ...) nei codici HTML tramite il metodo org.springframework.web.util.HtmlUtils.htmlEscape (...). Nota: esiste una classe simile in Apache Commons: org.apache.commons.lang.StringEscapeUtils.escapeHtml (...) i caratteri di iniezione SQL (', “, ...) tramite la classe Apache Commons org.apache.commons.lang.StringEscapeUtils. escapeSql (...)

<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>com.huo.filter.RequestWrappingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>




package com.huo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletReponse;
import javax.servlet.http.HttpServletRequest;

public class RequestWrappingFilter implements Filter{

    public void doFilter(ServletRequest req, ServletReponse res, FilterChain chain) throws IOException, ServletException{
        chain.doFilter(new MyHttpRequestWrapper(req), res);
    }

    public void init(FilterConfig config) throws ServletException{
    }

    public void destroy() throws ServletException{
    }
}




package com.huo.filter;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang.StringEscapeUtils;

public class MyHttpRequestWrapper extends HttpServletRequestWrapper{
    private Map<String, String[]> escapedParametersValuesMap = new HashMap<String, String[]>();

    public MyHttpRequestWrapper(HttpServletRequest req){
        super(req);
    }

    @Override
    public String getParameter(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        String escapedParameterValue = null; 
        if(escapedParameterValues!=null){
            escapedParameterValue = escapedParameterValues[0];
        }else{
            String parameterValue = super.getParameter(name);

            // HTML transformation characters
            escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

            // SQL injection characters
            escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

            escapedParametersValuesMap.put(name, new String[]{escapedParameterValue});
        }//end-else

        return escapedParameterValue;
    }

    @Override
    public String[] getParameterValues(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        if(escapedParameterValues==null){
            String[] parametersValues = super.getParameterValues(name);
            escapedParameterValue = new String[parametersValues.length];

            // 
            for(int i=0; i<parametersValues.length; i++){
                String parameterValue = parametersValues[i];
                String escapedParameterValue = parameterValue;

                // HTML transformation characters
                escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

                // SQL injection characters
                escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

                escapedParameterValues[i] = escapedParameterValue;
            }//end-for

            escapedParametersValuesMap.put(name, escapedParameterValues);
        }//end-else

        return escapedParameterValues;
    }
}

È buono il java-security-cross-site-scripting-xss-and-sql-injection topic? Sto cercando di trovare una soluzione per un'applicazione legacy.
Caot

0

Da: [Fonte]

public String MysqlRealScapeString(String str){
  String data = null;
  if (str != null && str.length() > 0) {
    str = str.replace("\\", "\\\\");
    str = str.replace("'", "\\'");
    str = str.replace("\0", "\\0");
    str = str.replace("\n", "\\n");
    str = str.replace("\r", "\\r");
    str = str.replace("\"", "\\\"");
    str = str.replace("\\x1a", "\\Z");
    data = str;
  }
return data;

}


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.