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

146

J'essaie de mettre en place une injection anti SQL en java et je trouve très difficile de travailler avec la fonction de chaîne "replaceAll". En fin de compte, j'ai besoin d'une fonction qui convertira tout existant \en \\, tout "en \", tout 'en \'et tout en \nde \\nsorte que lorsque la chaîne est évaluée par des injections SQL MySQL soient bloquées.

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

Scott Bonner
la source
1
D'accord, j'en suis venu à la conclusion que PreparedStatements est la voie à suivre, mais en fonction des objectifs actuels, je dois procéder comme prévu à l'origine et simplement mettre un filtre en place pour le moment et une fois le jalon actuel atteint, je peux revenir en arrière et refactoriser la base de données pour la déclaration préparée. En attendant, pour maintenir l'élan, quelqu'un a-t-il une solution pour échapper efficacement aux caractères ci-dessus pour MySQL étant donné que Java et son système d'expression régulière sont une douleur absolue pour calculer le nombre d'échappées nécessaires ...
Scott Bonner
2
Toutes les instructions SQL ne sont pas paramétrables, par exemple "SET ROLE role_name" ou "LISTEN channel_name"
Neil McGuigan
1
@NeilMcGuigan Oui. La plupart des pilotes refuseront également de paramétrer quelque chose comme étant CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?donné que l'instruction principale est une instruction DDL , même si la partie que vous essayez de paramétrer est en fait DML .
SeldomNeedy

Réponses:

249

Les PreparedStatements sont la voie à suivre, car ils rendent l'injection SQL impossible. Voici un exemple simple prenant l'entrée de l'utilisateur comme paramètres:

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 aucun cas 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 setIntméthode. La documentation PreparedStatement répertorie toutes les différentes méthodes disponibles pour définir et obtenir des données.

Kaleb Brasee
la source
1
via cette méthode pouvez-vous traiter chaque paramètre comme une chaîne tout en étant en sécurité? J'essaye de trouver un moyen de mettre à jour mon architecture existante pour être sûr sans avoir à reconstruire toute la couche de base de données ...
Scott Bonner
1
Tout dynqmic SQL n'est que des chaînes, ce n'est donc pas la question à se poser. Je ne suis pas familier avec PrepareStatement, donc la vraie question est de savoir s'il génère une requête paramétrée qui peut ensuite être exécutée avec ExecuteUpdate. Si oui, c'est bien. Si non, cela cache simplement le problème et vous n'aurez peut-être aucune option sécurisée à l'exception de la refonte de la couche de base de données. La gestion de l'injection SQL est l'une de ces choses que vous devez concevoir dès le début; ce n'est pas quelque chose que vous pouvez ajouter facilement plus tard.
Cylon Cat
2
Si vous insérez dans un champ INTEGER, vous voudrez utiliser un 'setInt'. De même, d'autres champs de base de données numériques utiliseraient d'autres setters. J'ai publié un lien vers les documents PreparedStatement qui répertorient tous les types de setter.
Kaleb Brasee
2
Oui Cylon, PreparedStatements génère des requêtes paramétrées.
Kaleb Brasee
2
@Kaleb Brasee, merci. C'est bon à savoir. Les outils sont différents dans chaque environnement, mais passer aux requêtes paramétrées est la réponse fondamentale.
Cylon Cat
46

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

Utilisez donc des paramètres pour toutes les clauses d'entrée, de mise à jour et where. Le SQL dynamique est simplement une porte ouverte pour les pirates informatiques, et cela inclut le SQL dynamique dans les procédures stockées. Paramétrer, paramétrer, paramétrer.

Chat Cylon
la source
11
Et même SQL paramétré n'est pas une garantie à 100%. Mais c'est un très bon début.
duffymo
2
@duffymo, je suis d'accord que rien n'est jamais sûr à 100%. Avez-vous un exemple d'injection SQL qui fonctionnera même avec SQL paramétré?
Cylon Cat
3
@Cylon Cat: Bien sûr, quand un morceau de SQL (comme @WhereClause ou @tableName) est passé comme paramètre, concaténé dans le SQL et exécuté dynamiquement. L'injection SQL se produit lorsque vous laissez les utilisateurs écrire votre code. Peu importe que vous capturiez leur code en tant que paramètre ou non.
Steve Kass
16
BTW, je ne sais pas pourquoi cela n'est pas mentionné plus, mais travailler avec PreparedStatements est également beaucoup plus facile et beaucoup plus lisible. Cela seul en fait probablement la valeur par défaut pour tous les programmeurs qui les connaissent.
Edan Maor
3
Veuillez noter que les PreparedStatements pour certaines bases de données sont TRÈS coûteux à créer, donc si vous devez en faire beaucoup, mesurez les deux types.
Thorbjørn Ravn Andersen
36

Si vraiment vous ne pouvez pas utiliser l' option de défense 1: instructions préparées (requêtes paramétrées) ou l' option de défense 2: procédures stockées , ne créez pas votre propre outil, utilisez l' API de sécurité d'entreprise OWASP . Depuis l' ESAPI OWASP 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 une perte de temps et des failles de sécurité massives. Les boîtes à outils de l'API de sécurité d'entreprise OWASP (ESAPI) aident les développeurs de logiciels à se prémunir contre les failles de conception et d'implémentation liées à la sécurité.

Pour plus de détails, consultez Prévention des injections SQL dans Java et Aide-mémoire sur la prévention des injections SQL .

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

Pascal Thivent
la source
4
L'ESAPI semble aujourd'hui caduque. Sur AWS, il existe WAF qui peut aider contre l'injection SQL, XSS, etc. existe-t-il d'autres alternatives à ce stade?
ChrisOdney
@ChrisOdney Un WAF peut être facilement contourné. La plupart des Frameworks implémentent déjà leur propre prévention d'injection SQL dans laquelle ils échappent automatiquement aux paramètres par leurs propres moyens. Alternatives pour les projets hérités: owasp.org/index.php/…
Javan R.
19

(Ceci est en réponse au commentaire du PO sous la question d'origine; Je suis tout à fait d'accord que PreparedStatement est l'outil pour ce travail, pas les expressions régulières.)

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

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

Pour faire correspondre une barre oblique inverse dans l'entrée, vous en mettez quatre dans la chaîne de regex. Pour mettre une barre oblique inverse dans la sortie, vous en mettez quatre dans la chaîne de remplacement. Cela suppose que vous créez les expressions régulières et les remplacements sous la forme de littéraux Java String. Si vous les créez d'une autre manière (par exemple, en les lisant à partir d'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 deuxième passage sur l'entrée avec ceci:

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

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

s = s.replaceAll("\n", "\\\\\\\\n");
Alan Moore
la source
1
Merci pour le commentaire, j'aime la façon dont vous avez fait tous les personnages en un, je m'y prenais de la manière la moins régulière de remplacer tout pour chacun ... Je ne sais pas comment attribuer la réponse à cette question maintenant . En fin de compte, PreparedStatements est la réponse, mais pour mon objectif actuel, votre réponse est la réponse dont j'ai besoin, seriez-vous contrarié si je donnais la réponse à l'une des réponses préparées précédemment, ou y a-t-il un moyen de partager la réponse entre un couple?
Scott Bonner
1
Puisqu'il ne s'agit que d'un kludge temporaire, allez-y et acceptez l'une des réponses PreparedStatement.
Alan Moore
12

Les PreparedStatements 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 la feuille de triche de prévention des injections SQL sur le site OWASP pour plus de détails et des API dans différents langages de programmation.


la source
1
Les cheatsheets OWASP ont été déplacés vers GitHub. La feuille de triche SQL Injection est maintenant ici: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets
...
10

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

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

ToBe_HH
la source
2
Pour référence: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/... Quoi qu'il en soit, cette méthode n'échappe que les guillemets et ne semble pas empêcher les attaques par injection SQL.
Paco Abato
11
Cela a été supprimé des dernières versions car il n'échappait que des guillemets simples
Pini Cheyni
2
Cette réponse doit être supprimée car elle n'empêche pas l'injection SQL.
Javan R.
9

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

L'un des moyens les plus simples d'empêcher une injection SQL en premier lieu consiste à utiliser a PreparedStatement, qui accepte les données à remplacer dans une instruction SQL à l'aide d'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, utiliser les instructions préparées à partir des didacticiels Java serait un bon point de départ.

coobird
la source
6

Dans le cas où vous avez affaire à un système hérité, ou si vous avez trop d'endroits pour passer à PreparedStatements en trop peu de temps - c'est-à-dire s'il y a un obstacle à l'utilisation des meilleures pratiques suggérées par d'autres réponses, vous pouvez essayer AntiSQLFilter

Bozho
la source
5

Vous avez besoin du code suivant ci-dessous. En un coup d'œil, cela peut ressembler à n'importe quel ancien code que j'ai créé. Cependant, j'ai regardé 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 trouver les caractères qu'il échappe et les ai personnalisés dans ma propre classe afin qu'il puisse être utilisé aux fins dont vous avez besoin. Après tout, s'il s'agit de la liste des personnages qu'Oracle échappe, alors savoir que c'est vraiment réconfortant en termes de sécurité. Peut-être qu'Oracle a besoin d'un coup de pouce pour ajouter une méthode similaire à celle-ci pour la prochaine version majeure de 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();
    }
}
Richard
la source
2
Je pense que ce code est la version décompilée du code source dans le lien ci-dessus. Maintenant dans les plus récents mysql-connector-java-xxx, les déclarations case '\u00a5'et case '\u20a9'semblent avoir été supprimées
zhy
J'ai essayé sqlmap avec votre code et cela ne m'a pas protégé de la première attaque `Type: aveugle basé sur booléen Titre: AND aveugle basé sur booléen - WHERE ou HAVING clause Payload: q = 1% 'AND 5430 = 5430 AND'% ' = '`
shareef
Désolé, cela fonctionne, mais je visualisais les résultats de la dernière session stockée .. J'ai gardé le commentaire pour un futur similaire ..
shareef
Vous pouvez utiliser org.ostermiller.utils.StringHelper.escapeSQL()ou com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi
1
Il est important de noter la licence GPLv2 sur le code d'origine à partir duquel il a été copié pour toute personne rencontrant ce problème. Je ne suis pas avocat, mais je recommande vivement de ne pas utiliser cette réponse dans votre projet à moins que vous ne soyez pleinement conscient des implications de l'inclusion de ce code sous licence.
Nick Spacek
0

Après avoir recherché une solution de test pour empêcher sqlmap de l'injection SQL, dans le cas d'un système hérité qui ne peut pas appliquer les déclarations préparées partout.

La rubrique java-security-cross-site-scripting-xss-and-sql-injection ÉTAIT 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 requête dans un wrapper MyHttpRequestWrapper codé lui-même qui transforme:

les paramètres HTTP avec des caractères spéciaux (<,>, ',…) dans les 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;
    }
}
shareef
la source
Est-ce bon le java-security-cross-site-scripting-xss-and-sql-injection topic? J'essaye de trouver une solution pour une application héritée.
caot le
0

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;

}

Billcountry
la source
0

Si vous utilisez PL / SQL, vous pouvez également l'utiliser DBMS_ASSERT pour nettoyer votre entrée afin que vous puissiez l'utiliser sans vous soucier des injections SQL.

voir cette réponse par exemple: https://stackoverflow.com/a/21406499/1726419

yossico
la source