web-dev-qa-db-fra.com

Java - chaîne d'échappement empêchant l'injection SQL

J'essaie de mettre en place une injection anti-SQL dans Java et je trouve très difficile de travailler avec la fonction de chaîne "replaceAll". Finalement, j'ai besoin d'une fonction qui convertira n'importe quel \ existant en \\, tout " en \", tout ' en \', et tout \n à \\n afin que, lorsque la chaîne est évaluée par MySQL, les injections SQL soient bloquées.

J'ai gravé du code avec lequel je travaillais et tous les \\\\\\\\\\\ de la fonction me rendent fou des yeux. Si quelqu'un avait un exemple de cela, je l'apprécierais beaucoup.

142
Scott Bonner

PreparedStatements est la solution, car ils rendent l'injection SQL impossible. Voici un exemple simple prenant l'entrée de l'utilisateur comme paramètre:

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

Quels que soient les caractères du nom et de l'e-mail, ces caractères seront placés directement dans la base de données. Ils n'affecteront en aucune façon l'instruction INSERT.

Il existe différentes méthodes de définition pour différents types de données - celle que vous utilisez dépend des champs de votre base de données. Par exemple, si vous avez une colonne INTEGER dans la base de données, vous devez utiliser une méthode setInt. La documentation PreparedStatement répertorie toutes les différentes méthodes disponibles pour définir et obtenir des données.

241
Kaleb Brasee

Le seul moyen d'empêcher l'injection SQL est d'utiliser du SQL paramétré. Il est tout simplement impossible de créer un filtre plus intelligent que les personnes qui piratent SQL pour gagner sa vie.

Utilisez donc des paramètres pour toutes les clauses input, updates et where. Le SQL dynamique est simplement une porte ouverte pour les pirates, et cela inclut le SQL dynamique dans les procédures stockées. Paramétrer, paramétrer, paramétrer.

45
Cylon Cat

Si vraiment vous ne pouvez pas utiliser Option de défense 1: instructions préparées (requêtes paramétrées) ou Option de défense 2: procédures stockées , ne créez pas votre propre outil, utilisez le - API de sécurité d'entreprise OWASP . Depuis le OWASP ESAPI hébergé sur Google Code:

N'écrivez pas vos propres contrôles de sécurité! Réinventer la roue lorsqu'il s'agit de développer des contrôles de sécurité pour chaque application Web ou service Web entraîne des pertes de temps et des failles de sécurité énormes. Les boîtes à outils ESAPI (OWASP Enterprise Security API) aident les développeurs de logiciels à se prémunir contre les failles de conception et de mise en œuvre liées à la sécurité.

Pour plus de détails, voir Prévention de l'injection SQL en Java et Aide-mémoire de prévention de l'injection SQL .

Portez une attention particulière à Option de défense 3: Échapper à toutes les entrées fournies par l'utilisateur qui introduit le projet OWASP ESAPI ).

34
Pascal Thivent

(Ceci est en réponse au commentaire du PO sous la question initiale; je suis tout à fait d'accord sur le fait que PreparedStatement est l'outil pour ce travail, pas les expressions rationnelles.)

Lorsque vous dites \n, voulez-vous dire la séquence \ + n ou un caractère de saut de ligne? Si c'est \ + n, la tâche est assez simple:

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

Pour faire correspondre une barre oblique inversée dans l'entrée, vous devez en insérer quatre dans la chaîne regex. Pour insérer une barre oblique inversée dans la sortie, vous devez en insérer quatre dans la chaîne de remplacement. Cela suppose que vous créez les expressions rationnelles et les remplacements sous la forme de littéraux Java. Si vous les créez de toute autre manière (par exemple, en les lisant dans un fichier), vous n'avez pas à faire tout ce double échappement.

Si vous avez un caractère de saut de ligne dans l'entrée et que vous souhaitez le remplacer par une séquence d'échappement, vous pouvez effectuer un second passage sur l'entrée avec ceci:

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

Ou peut-être que vous voulez deux barres obliques inverses (je ne suis pas trop clair à ce sujet):

s = s.replaceAll("\n", "\\\\\\\\n");
19
Alan Moore

Les déclarations préparées sont la voie à suivre dans la plupart des cas, mais pas dans tous. Parfois, vous vous retrouverez dans une situation où une requête, ou une partie de celle-ci, doit être construite et stockée sous forme de chaîne pour une utilisation ultérieure. Consultez le aide-mémoire pour la prévention de l'injection SQL sur le site OWASP pour plus de détails et les API dans différents langages de programmation.

13
user566057

Les instructions préparées sont la meilleure solution, mais si vous avez vraiment besoin de le faire manuellement, vous pouvez également utiliser la classe StringEscapeUtils de la bibliothèque Apache Commons-Lang. Il a une méthode escapeSql(String) , que vous pouvez utiliser:

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

9
ToBe_HH

L'utilisation d'une expression régulière pour supprimer du texte, ce qui pourrait provoquer une injection SQL, donne l'impression que l'instruction SQL est envoyée à la base de données via un Statement plutôt qu'un PreparedStatement .

L’un des moyens les plus simples d’empêcher une injection SQL est d’utiliser un PreparedStatement, qui accepte la substitution de données dans une instruction SQL utilisant des espaces réservés, qui ne repose pas sur des concaténations de chaînes pour créer une instruction SQL à envoyer à la base de données.

Pour plus d'informations, Utilisation d'instructions préparées à partir de Le Java Didacticiels serait un bon endroit pour commencer.

9
coobird

Vous avez besoin du code suivant ci-dessous. En un coup d'œil, cela peut ressembler à n'importe quel ancien code que j'ai composé. Cependant, ce que j’ai fait, c’est de regarder le code source de http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-Java/5.1.31/com/mysql/ jdbc/PreparedStatement.Java . Puis, après cela, j’ai soigneusement examiné le code de setString (int parameterIndex, String x) pour rechercher les caractères qu’il échappe et l’a personnalisé dans ma propre classe afin qu’il puisse être utilisé aux fins souhaitées. Après tout, s’il s’agit de la liste de caractères échappée par Oracle, il est alors rassurant de le faire pour la sécurité. Peut-être qu'Oracle a besoin d'un Nudge pour ajouter une méthode similaire à celle-ci pour la prochaine version majeure 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();
    }
}
6
Richard

Si vous avez affaire à un système existant, ou si vous avez trop d’endroits pour passer à PreparedStatements en trop peu de temps - c’est-à-dire qu’il existe un obstacle à l’utilisation de la meilleure pratique suggérée par d’autres réponses, vous pouvez essayer - AntiSQLFilter

6
Bozho

De: [Source]

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;

}

0

Je pense que escapeSql de StringEscapeUtils pourrait faire l'affaire. Pourtant, essayer mais cela semble être une option crédible.

0
akostadinov

Après avoir recherché plusieurs solutions de test pour éviter l’injection de sqlmap, en cas de système hérité qui ne peut pas appliquer les instructions préparées partout.

rubrique Java-sécurité-cross-site-scripting-xss-et-sql-injection ETAIT LA SOLUTION

j'ai essayé la solution de @Richard, mais cela n'a pas fonctionné dans mon cas. j'ai utilisé un filtre

Le but de ce filtre est d'encapsuler la demande dans un wrapper codé en propre, MyHttpRequestWrapper, qui transforme:

les paramètres HTTP avec des caractères spéciaux (<,>, ‘,…) en codes HTML via la méthode org.springframework.web.util.HtmlUtils.htmlEscape (…). Remarque: Il existe une classe similaire dans Apache Commons: org.Apache.commons.lang.StringEscapeUtils.escapeHtml (…) les caractères d'injection SQL (', “,…) via 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;
    }
}
0
shareef