Java - Escape-Zeichenfolge zur Verhinderung der SQL-Injection

146

Ich versuche, eine Anti-SQL-Injektion in Java zu installieren, und finde es sehr schwierig, mit der String-Funktion "replaceAll" zu arbeiten. Letztendlich brauche ich eine Funktion, die alle vorhandenen \in \\, alle "in \", alle 'in \'und alle \nin konvertiert , \\ndamit bei der Auswertung der Zeichenfolge durch MySQL SQL-Injektionen blockiert werden.

Ich habe einen Code aufgebockt, mit dem ich gearbeitet habe, und alle \\\\\\\\\\\Funktionen machen meine Augen verrückt. Wenn jemand ein Beispiel dafür hat, würde ich es sehr schätzen.

Scott Bonner
quelle
1
Okay, ich bin zu dem Schluss gekommen, dass PreparedStatements der richtige Weg sind, aber basierend auf den aktuellen Zielen muss ich wie ursprünglich geplant vorgehen und vorerst nur einen Filter einsetzen, und sobald der aktuelle Meilenstein erreicht ist, kann ich Gehen Sie zurück und überarbeiten Sie die Datenbank für die vorbereitete Angabe. Hat jemand in der Zwischenzeit, um die Dynamik aufrechtzuerhalten, eine Lösung, um die oben genannten Zeichen für MySQL angesichts von Java effektiv zu umgehen, und sein System für reguläre Ausdrücke ist ein absoluter Schmerz, um die Anzahl der erforderlichen Fluchtwege zu ermitteln ...
Scott Bonner
2
Nicht alle SQL-Anweisungen sind parametrierbar, zum Beispiel "SET ROLE Rollenname" oder "LISTEN Kanalname"
Neil McGuigan
1
@NeilMcGuigan Ja. Die meisten Treiber lehnen auch die Parametrisierung ab, CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?da die Hauptanweisung eine DDL- Anweisung ist, obwohl der Teil, den Sie parametrisieren möchten, tatsächlich DML ist .
Selten Needy

Antworten:

249

PreparedStatements sind der richtige Weg, da sie eine SQL-Injection unmöglich machen. Hier ist ein einfaches Beispiel, in dem die Benutzereingaben als Parameter verwendet werden:

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
      }
   }
}

Unabhängig davon, welche Zeichen in Name und E-Mail enthalten sind, werden diese Zeichen direkt in der Datenbank abgelegt. Sie haben keinerlei Auswirkungen auf die INSERT-Anweisung.

Es gibt verschiedene Set-Methoden für verschiedene Datentypen. Welche Sie verwenden, hängt von Ihren Datenbankfeldern ab. Wenn Sie beispielsweise eine INTEGER-Spalte in der Datenbank haben, sollten Sie eine setIntMethode verwenden. In der PreparedStatement-Dokumentation sind alle verschiedenen Methoden zum Festlegen und Abrufen von Daten aufgeführt.

Kaleb Brasee
quelle
1
Können Sie mit dieser Methode jeden Parameter als Zeichenfolge behandeln und trotzdem sicher sein? Ich versuche einen Weg zu finden, um meine vorhandene Architektur sicher zu aktualisieren, ohne die gesamte Datenbankschicht neu erstellen zu müssen ...
Scott Bonner
1
Alle dynqmic SQL sind nur Zeichenfolgen, das ist also nicht die Frage, die gestellt werden muss. PrepareStatement ist mir nicht bekannt. Die eigentliche Frage ist also, ob eine parametrisierte Abfrage generiert wird, die dann mit ExecuteUpdate ausgeführt werden kann. Wenn ja, ist das gut. Wenn nein, wird das Problem einfach ausgeblendet, und Sie haben möglicherweise keine sichere Option außer der Neugestaltung der Datenbankebene. Der Umgang mit SQL-Injection ist eines der Dinge, die Sie von Anfang an entwerfen müssen. Es ist nicht etwas, das Sie später einfach hinzufügen können.
Zylonische Katze
2
Wenn Sie in ein INTEGER-Feld einfügen, möchten Sie ein 'setInt' verwenden. Ebenso würden andere numerische Datenbankfelder andere Setter verwenden. Ich habe einen Link zu den PreparedStatement-Dokumenten gepostet, in denen alle Setter-Typen aufgelistet sind.
Kaleb Brasee
2
Ja, Cylon, PreparedStatements generieren parametrisierte Abfragen.
Kaleb Brasee
2
@ Kaleb Brasee, danke. Das ist gut zu wissen. Die Tools sind in jeder Umgebung unterschiedlich, aber es ist die grundlegende Antwort, sich auf parametrisierte Abfragen zu beschränken.
Zylonische Katze
46

Die einzige Möglichkeit, die SQL-Injection zu verhindern, ist die Parametrisierung von SQL. Es ist einfach nicht möglich, einen Filter zu erstellen, der intelligenter ist als die Leute, die SQL für ihren Lebensunterhalt hacken.

Verwenden Sie daher Parameter für alle Eingaben, Aktualisierungen und where-Klauseln. Dynamisches SQL ist einfach eine offene Tür für Hacker, und dazu gehört auch dynamisches SQL in gespeicherten Prozeduren. Parametrieren, parametrisieren, parametrisieren.

Zylon Cat
quelle
11
Und selbst parametrisiertes SQL ist keine 100% ige Garantie. Aber es ist ein sehr guter Anfang.
Duffymo
2
@duffymo, ich stimme zu, dass nichts jemals 100% sicher ist. Haben Sie ein Beispiel für eine SQL-Injection, die auch mit parametrisiertem SQL funktioniert?
Zylonische Katze
3
@Cylon Cat: Sicher, wenn ein Teil von SQL (wie @WhereClause oder @tableName) als Parameter übergeben, in SQL verkettet und dynamisch ausgeführt wird. SQL-Injection tritt auf, wenn Benutzer Ihren Code schreiben. Es spielt keine Rolle, ob Sie ihren Code als Parameter erfassen oder nicht.
Steve Kass
16
Übrigens, ich weiß nicht, warum dies nicht mehr erwähnt wird, aber die Arbeit mit PreparedStatements ist auch viel einfacher und viel lesbarer. Das allein macht sie wahrscheinlich zum Standard für jeden Programmierer, der sie kennt.
Edan Maor
3
Bitte beachten Sie, dass die Erstellung von PreparedStatements für einige Datenbanken SEHR teuer ist. Wenn Sie also viele davon ausführen müssen, messen Sie beide Typen.
Thorbjørn Ravn Andersen
36

Wenn Sie Defense Option 1: Vorbereitete Anweisungen (parametrisierte Abfragen) oder Defense Option 2: Gespeicherte Prozeduren wirklich nicht verwenden können , erstellen Sie kein eigenes Tool, sondern verwenden Sie die OWASP Enterprise Security API . Aus dem auf Google Code gehosteten OWASP ESAPI :

Schreiben Sie keine eigenen Sicherheitskontrollen! Die Neuerfindung des Rades bei der Entwicklung von Sicherheitskontrollen für jede Webanwendung oder jeden Webdienst führt zu Zeitverschwendung und massiven Sicherheitslücken. Die ESWI-Toolkits (OWASP Enterprise Security API) helfen Softwareentwicklern, sich vor sicherheitsrelevanten Design- und Implementierungsfehlern zu schützen.

Weitere Informationen finden Sie unter Verhindern von SQL Injection in Java und SQL Injection Prevention Cheat Sheet .

Achten Sie besonders auf Verteidigungsoption 3: Entkommen aller vom Benutzer bereitgestellten Eingaben , mit denen das OWASP ESAPI- Projekt eingeführt wird.

Pascal Thivent
quelle
4
Die ESAPI scheint heute nicht mehr vorhanden zu sein. Auf AWS gibt es WAF, die gegen SQL-Injection, XSS usw. helfen kann. Gibt es derzeit andere Alternativen?
ChrisOdney
@ChrisOdney Ein WAF kann leicht umgangen werden. Die meisten Frameworks implementieren bereits ihre eigene SQL-Injection-Verhinderung, bei der sie Parameter automatisch selbst entziehen. Alternativen für Legacy-Projekte: owasp.org/index.php/…
Javan R.
19

(Dies ist eine Antwort auf den Kommentar des OP unter der ursprünglichen Frage. Ich stimme voll und ganz zu, dass PreparedStatement das Werkzeug für diesen Job ist und keine regulären Ausdrücke.)

Wenn Sie sagen \n, meinen Sie die Sequenz \+ noder ein tatsächliches Zeilenvorschubzeichen? Wenn es \+ ist n, ist die Aufgabe ziemlich einfach:

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

Um einem Backslash in der Eingabe zu entsprechen, fügen Sie vier davon in die Regex-Zeichenfolge ein. Um einen Backslash in die Ausgabe einzufügen, fügen Sie vier davon in die Ersatzzeichenfolge ein. Dies setzt voraus, dass Sie die regulären Ausdrücke und Ersetzungen in Form von Java-String-Literalen erstellen. Wenn Sie sie auf andere Weise erstellen (z. B. indem Sie sie aus einer Datei lesen), müssen Sie nicht all diese doppelten Escapezeichen ausführen.

Wenn die Eingabe ein Zeilenvorschubzeichen enthält und Sie es durch eine Escape-Sequenz ersetzen möchten, können Sie die Eingabe folgendermaßen durchlaufen:

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

Oder vielleicht möchten Sie zwei Backslashes (da bin ich mir nicht ganz sicher):

s = s.replaceAll("\n", "\\\\\\\\n");
Alan Moore
quelle
1
Vielen Dank für den Kommentar. Ich mag die Art und Weise, wie Sie alle Zeichen in einem gemacht haben. Ich habe es mit dem weniger regulären Ausdruck gemacht, alle für jeden zu ersetzen. Ich bin mir nicht sicher, wie ich die Antwort auf diese Frage jetzt zuweisen soll . Letztendlich ist PreparedStatements die Antwort, aber für mein aktuelles Ziel ist Ihre Antwort die Antwort, die ich brauche. Wären Sie verärgert, wenn ich die Antwort auf eine der Antworten der zuvor vorbereiteten Erklärung geben würde, oder gibt es eine Möglichkeit, die Antwort zwischen einem Paar zu teilen?
Scott Bonner
1
Da dies nur ein vorübergehender Fehler ist, akzeptieren Sie eine der PreparedStatement-Antworten.
Alan Moore
12

PreparedStatements sind in den meisten, aber nicht in allen Fällen der richtige Weg. Manchmal befinden Sie sich in einer Situation, in der eine Abfrage oder ein Teil davon erstellt und zur späteren Verwendung als Zeichenfolge gespeichert werden muss. Weitere Informationen und APIs in verschiedenen Programmiersprachen finden Sie im SQL Injection Prevention Cheat Sheet auf der OWASP-Site .


quelle
1
OWASP-Cheatsheets wurden auf GitHub verschoben. Das SQL Injection Cheat Sheet ist jetzt hier: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/…
theferrit32
10

Vorbereitete Anweisungen sind die beste Lösung. Wenn Sie dies jedoch wirklich manuell ausführen müssen, können Sie auch die StringEscapeUtilsKlasse aus der Apache Commons-Lang-Bibliothek verwenden. Es gibt eine escapeSql(String)Methode, die Sie verwenden können:

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

ToBe_HH
quelle
2
Als Referenz: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… Diese Methode entgeht jedoch nur Anführungszeichen und scheint SQL Injection-Angriffe nicht zu verhindern.
Paco Abato
11
Dies wurde aus den neuesten Versionen entfernt, da es nur einfachen Anführungszeichen entging
Pini Cheyni
2
Diese Antwort sollte gelöscht werden, da sie die SQL-Injektion nicht verhindert.
Javan R.
9

Die Verwendung eines regulären Ausdrucks zum Entfernen von Text, der eine SQL-Injection verursachen könnte, klingt so, als würde die SQL-Anweisung über a Statementund nicht über a an die Datenbank gesendet PreparedStatement.

Eine der einfachsten Möglichkeiten, eine SQL-Injection zu verhindern, ist die Verwendung von a PreparedStatement, das Daten akzeptiert, die mithilfe von Platzhaltern in eine SQL-Anweisung eingesetzt werden sollen. Dies beruht nicht auf Zeichenfolgenverkettungen, um eine SQL-Anweisung zum Senden an die Datenbank zu erstellen.

Weitere Informationen finden Sie unter Verwenden vorbereiteter Anweisungen aus den Java-Lernprogrammen .

Coobird
quelle
6

PreparedStatementWenn Sie es mit einem Legacy-System zu tun haben oder zu viele Orte haben, um in zu kurzer Zeit zu s zu wechseln - dh wenn die Verwendung der von anderen Antworten vorgeschlagenen Best Practice behindert wird , können Sie AntiSQLFilter ausprobieren

Bozho
quelle
5

Sie benötigen den folgenden Code. Auf den ersten Blick mag dies wie jeder alte Code aussehen, den ich erfunden habe. Ich habe mir jedoch den Quellcode für http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement angesehen. Java . Danach habe ich den Code von setString (int parameterIndex, String x) sorgfältig durchgesehen, um die Zeichen zu finden, denen es entgeht, und dies an meine eigene Klasse angepasst, damit es für die von Ihnen benötigten Zwecke verwendet werden kann. Wenn dies die Liste der Zeichen ist, denen Oracle entgeht, ist es in Bezug auf die Sicherheit wirklich beruhigend, dies zu wissen. Möglicherweise benötigt Oracle einen Anstoß, um eine ähnliche Methode für die nächste große Java-Version hinzuzufügen.

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();
    }
}
Richard
quelle
2
Ich denke, dieser Code ist die dekompilierte Version des Quellcodes im obigen Link. Jetzt in neueren scheinen mysql-connector-java-xxxdie case '\u00a5'und case '\u20a9'Anweisungen entfernt worden zu sein
zhy
Ich habe sqlmap mit Ihrem Code ausprobiert und es hat mich nicht vor dem ersten Angriff geschützt. Typ: Boolescher Blind Titel: UND Boolescher Blind - WHERE- oder HAVING-Klausel Nutzlast: q = 1% 'UND 5430 = 5430 UND'% ' = '`
shareef
Tut mir leid, es funktioniert, aber ich habe die zuletzt gespeicherten Sitzungsergebnisse
angezeigt
Sie können org.ostermiller.utils.StringHelper.escapeSQL()oder verwenden com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi
1
Es ist wichtig, die GPLv2-Lizenz auf dem Originalcode zu vermerken, von dem diese kopiert wurde, wenn jemand darauf stößt. Ich bin kein Anwalt, aber ich würde dringend empfehlen, diese Antwort nicht in Ihrem Projekt zu verwenden, es sei denn, Sie sind sich der Auswirkungen der Aufnahme dieses lizenzierten Codes voll bewusst.
Nick Spacek
0

Nach dem Durchsuchen einer Testlösung, um zu verhindern, dass SQLMap SQL-Injection ausführt, im Falle eines Legacy-Systems, das vorbereitete Anweisungen nicht überall anwenden kann.

Java-Security-Cross-Site-Scripting-xss-und-SQL-Injection-Thema Lösung

Ich habe @Richards Lösung ausprobiert, aber in meinem Fall nicht funktioniert. Ich habe einen Filter verwendet

Das Ziel dieses Filters ist es, die Anforderung in einen eigencodierten Wrapper MyHttpRequestWrapper zu verpacken, der Folgendes transformiert:

die HTTP-Parameter mit Sonderzeichen (<,>, ',…) in HTML-Codes über die Methode org.springframework.web.util.HtmlUtils.htmlEscape (…). Hinweis: In Apache Commons gibt es eine ähnliche Klasse: org.apache.commons.lang.StringEscapeUtils.escapeHtml (…) die SQL-Injection-Zeichen (', “,…) über die Apache Commons-Klasse 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;
    }
}
shareef
quelle
Ist es gut das java-security-cross-site-scripting-xss-and-sql-injection topic? Ich versuche eine Lösung für eine Legacy-Anwendung zu finden.
Caot
0

Von: [Quelle]

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;

}}

Billcountry
quelle
0

Wenn Sie PL / SQL verwenden, können Sie auch verwenden DBMS_ASSERT , um Ihre Eingabe zu bereinigen, sodass Sie es verwenden können, ohne sich um SQL-Injektionen sorgen zu müssen.

Siehe diese Antwort zum Beispiel: https://stackoverflow.com/a/21406499/1726419

Yossico
quelle