Java: cadena de escape para evitar la inyección de SQL

146

Estoy tratando de poner un poco de inyección anti SQL en Java y me resulta muy difícil trabajar con la función de cadena "replaceAll". En última instancia, necesito una función que convierta cualquier existente \a \\, any "a \", any 'a \'y any \na \\npara que cuando la cadena sea evaluada por MySQL, las inyecciones SQL se bloqueen.

He acumulado un código con el que estaba trabajando y todo \\\\\\\\\\\en la función me está volviendo loco. Si alguien tiene un ejemplo de esto, lo agradecería mucho.

Scott Bonner
fuente
1
De acuerdo, he llegado a la conclusión de que las declaraciones preparadas son el camino a seguir, sin embargo, en base a los objetivos actuales, tengo que proceder como se planeó originalmente y solo poner un filtro en el lugar por el momento y una vez que se alcanza el hito actual, puedo regrese y refactorice la base de datos para la declaración preparada. Mientras tanto, para mantener el impulso, ¿alguien tiene una solución para escapar de manera efectiva de los caracteres anteriores para MySQL dado el Java y su sistema de expresión regular es un dolor absoluto para calcular la cantidad de escapes necesarios ...
Scott Bonner
2
No todas las sentencias SQL son parametrizables, por ejemplo, "SET ROLE role_name" o "LISTEN channel_name"
Neil McGuigan el
1
@NeilMcGuigan Sí. La mayoría de los controladores también se negarán a parametrizar algo como, CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?ya que la declaración principal es una declaración DDL , aunque la parte que está tratando de parametrizar es en realidad DML .
SeldomNeedy

Respuestas:

249

Las declaraciones preparadas son el camino a seguir, porque hacen que la inyección de SQL sea imposible. Aquí hay un ejemplo simple que toma la entrada del usuario como parámetros:

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

No importa qué caracteres estén en el nombre y el correo electrónico, esos caracteres se colocarán directamente en la base de datos. No afectarán la declaración INSERT de ninguna manera.

Existen diferentes métodos establecidos para diferentes tipos de datos: el que use dependerá de los campos de su base de datos. Por ejemplo, si tiene una columna INTEGER en la base de datos, debe usar un setIntmétodo. La documentación de PreparedStatement enumera todos los diferentes métodos disponibles para configurar y obtener datos.

Kaleb Brasee
fuente
1
mediante este método, ¿puede tratar cada parámetro como una cadena y aún así estar seguro? Estoy tratando de encontrar una manera de actualizar mi arquitectura existente para que sea seguro sin tener que reconstruir toda la capa de base de datos ...
a Scott Bonner
1
Todos los SQL dinámicos son solo cadenas, por lo que esa no es la pregunta que debe hacerse. No estoy familiarizado con PrepareStatement, por lo que la verdadera pregunta es si genera una consulta parametrizada que luego se puede ejecutar con ExecuteUpdate. Si es así, eso está bien. Si no, entonces simplemente está ocultando el problema, y ​​es posible que no tenga ninguna opción segura, excepto rediseñar la capa de la base de datos. Tratar con la inyección SQL es una de esas cosas que debe diseñar desde el principio; no es algo que pueda agregar fácilmente más adelante.
Cylon Cat
2
Si está insertando en un campo INTEGER, querrá usar un 'setInt'. Del mismo modo, otros campos de bases de datos numéricas utilizarían otros establecedores. Publiqué un enlace a los documentos de PreparedStatement que enumeran todos los tipos de setter.
Kaleb Brasee
2
Sí Cylon, PreparedStatements genera consultas parametrizadas.
Kaleb Brasee
2
@ Kaleb Brasee, gracias. Es bueno saberlo. Las herramientas son diferentes en cada entorno, pero llegar a las consultas parametrizadas es la respuesta fundamental.
Cylon Cat
46

La única forma de evitar la inyección de SQL es con SQL parametrizado. Simplemente no es posible construir un filtro que sea más inteligente que las personas que piratean SQL para ganarse la vida.

Por lo tanto, use parámetros para todas las entradas, actualizaciones y cláusulas where. Dynamic SQL es simplemente una puerta abierta para los hackers, y eso incluye SQL dinámico en los procedimientos almacenados. Parametrizar, parametrizar, parametrizar.

Gato Cylon
fuente
11
E incluso el SQL parametrizado no es una garantía del 100%. Pero es un muy buen comienzo.
duffymo
2
@duffymo, estoy de acuerdo en que nada es 100% seguro. ¿Tiene un ejemplo de inyección SQL que funcionará incluso con SQL parametrizado?
Cylon Cat
3
@Cylon Cat: Claro, cuando un fragmento de SQL (como @WhereClause o @tableName) se pasa como parámetro, se concatena en el SQL y se ejecuta dinámicamente. La inyección de SQL ocurre cuando dejas que los usuarios escriban tu código. No importa si captura su código como parámetro o no.
Steve Kass
16
Por cierto, no sé por qué esto no se menciona más, pero trabajar con PreparedStatements también es mucho más fácil y mucho más legible. Eso solo probablemente los convierte en el valor predeterminado para cada programador que los conoce.
Edan Maor
3
Tenga en cuenta que las declaraciones preparadas para algunas bases de datos son MUY caras de crear, por lo que si necesita hacer muchas de ellas, mida ambos tipos.
Thorbjørn Ravn Andersen
36

Si realmente no puede usar la Opción de defensa 1: Declaraciones preparadas (consultas parametrizadas) o la Opción de defensa 2: Procedimientos almacenados , no cree su propia herramienta, use la API de seguridad empresarial de OWASP . Desde el OWASP ESAPI alojado en Google Code:

¡No escriba sus propios controles de seguridad! Reinventar la rueda cuando se trata de desarrollar controles de seguridad para cada aplicación web o servicio web conduce a una pérdida de tiempo y agujeros de seguridad masivos. Los kits de herramientas API de seguridad empresarial de OWASP (ESAPI) ayudan a los desarrolladores de software a protegerse contra fallas de diseño e implementación relacionadas con la seguridad.

Para obtener más detalles, consulte Prevención de la inyección de SQL en Java y la hoja de trucos de prevención de inyección de SQL .

Preste especial atención a la Opción de defensa 3: Escapar de todas las entradas proporcionadas por el usuario que presenta el proyecto OWASP ESAPI ).

Pascal Thivent
fuente
44
El ESAPI parece difunto a partir de hoy. En AWS hay WAF que puede ayudar contra la inyección SQL, XSS, etc. ¿Hay alguna otra alternativa en este momento?
ChrisOdney
@ChrisOdney Un WAF se puede omitir fácilmente. La mayoría de los Frameworks ya implementan su propia prevención de inyección SQL en la que escapan parámetros automáticamente por su cuenta. Alternativas para proyectos heredados: owasp.org/index.php/…
Javan R.
19

(Esto responde al comentario del OP bajo la pregunta original; estoy completamente de acuerdo en que PreparedStatement es la herramienta para este trabajo, no expresiones regulares).

Cuando dices \n, ¿te refieres a la secuencia \+ no un carácter de salto de línea real? Si es \+ n, la tarea es bastante sencilla:

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

Para hacer coincidir una barra invertida en la entrada, debe colocar cuatro de ellas en la cadena de expresiones regulares. Para poner una barra diagonal inversa en la salida, coloque cuatro de ellas en la cadena de reemplazo. Esto supone que está creando expresiones regulares y reemplazos en forma de literales de Java String. Si los crea de otra manera (por ejemplo, al leerlos desde un archivo), no tiene que hacer todo ese doble escape.

Si tiene un carácter de salto de línea en la entrada y desea reemplazarlo con una secuencia de escape, puede hacer un segundo pase sobre la entrada con esto:

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

O tal vez quieras dos barras invertidas (no estoy muy claro en eso):

s = s.replaceAll("\n", "\\\\\\\\n");
Alan Moore
fuente
1
Gracias por el comentario, me gusta la forma en que hiciste todos los personajes en uno, lo hice con la forma de expresión menos regular de reemplazar todos para cada uno ... No estoy seguro de cómo asignar la respuesta a esta pregunta ahora . En última instancia, PreparedStatements es la respuesta, pero para mi objetivo actual, su respuesta es la respuesta que necesito, ¿estaría molesto si di la respuesta a una de las respuestas de la declaración preparada anteriormente, o hay una manera de compartir la respuesta entre una pareja?
Scott Bonner, el
1
Dado que esto es solo un error temporal, continúe y acepte una de las respuestas de PreparedStatement.
Alan Moore
12

Las declaraciones preparadas son el camino a seguir en la mayoría, pero no en todos los casos. A veces se encontrará en una situación en la que una consulta, o una parte de ella, debe construirse y almacenarse como una cadena para su uso posterior. Consulte la hoja de trucos de prevención de inyección de SQL en el sitio OWASP para obtener más detalles y API en diferentes lenguajes de programación.


fuente
1
Las fichas de OWASP se han trasladado a GitHub. La hoja de trucos de inyección SQL ahora está aquí: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/…
theferrit32
10

Las declaraciones preparadas son la mejor solución, pero si realmente necesita hacerlo manualmente, también puede usar la StringEscapeUtilsclase de la biblioteca Apache Commons-Lang. Tiene un escapeSql(String)método que puedes usar:

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

ToBe_HH
fuente
2
Como referencia: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… De todos modos, este método solo escapa a las comillas y no parece evitar los ataques de inyección SQL.
Paco Abato
11
Esto se eliminó de las últimas versiones porque solo se escapaba de las comillas simples
Pini Cheyni
2
Esta respuesta debe eliminarse porque no impide la inyección de sql.
Javan R.
9

El uso de una expresión regular para eliminar texto que podría causar una inyección SQL suena como si la instrucción SQL se enviara a la base de datos a través de un en Statementlugar de un PreparedStatement.

En primer lugar, una de las formas más fáciles de evitar una inyección de SQL es usar a PreparedStatement, que acepta datos para sustituir en una declaración SQL utilizando marcadores de posición, que no se basa en concatenaciones de cadenas para crear una declaración SQL para enviar a la base de datos.

Para obtener más información, Uso de declaraciones preparadas de tutoriales de Java sería un buen lugar para comenzar.

Coobird
fuente
6

En caso de que esté lidiando con un sistema heredado, o tenga demasiados lugares para cambiar a PreparedStatements en muy poco tiempo, es decir, si hay un obstáculo para usar la mejor práctica sugerida por otras respuestas, puede probar AntiSQLFilter

Bozho
fuente
5

Necesita el siguiente código a continuación. De un vistazo, esto puede parecerse a cualquier código antiguo que inventé. Sin embargo, lo que hice fue mirar el código fuente de http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Luego, después de eso, busqué cuidadosamente el código de setString (int parameterIndex, String x) para encontrar los caracteres de los que se escapa y lo personalicé en mi propia clase para que pueda usarse para los fines que usted necesita. Después de todo, si esta es la lista de personajes de los que Oracle escapa, entonces saber esto es realmente reconfortante en cuanto a seguridad. Tal vez Oracle necesite un empujón para agregar un método similar a este para la próxima versión principal 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();
    }
}
Ricardo
fuente
2
Creo que este código es la versión descompilada del código fuente en el enlace anterior. Ahora en los nuevos mysql-connector-java-xxx, el case '\u00a5'y case '\u20a9'declaraciones parecen haber sido eliminado
zhy
probé sqlmap con su código y no me protegió del primer ataque `Tipo: ciego basado en booleano Título: Y ciego basado en booleano - WHERE o HAVING cláusula Carga útil: q = 1% 'AND 5430 = 5430 AND'% ' = ''
shareef
Lo sentimos, pero su trabajo fue viendo los últimos resultados de la sesión almacenados .. me quedé con el comentario para el futuro similares ..
shareef
Puedes usar org.ostermiller.utils.StringHelper.escapeSQL()o com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi
1
Es importante tener en cuenta la licencia GPLv2 en el código original del que se copió para cualquiera que se encuentre con esto. No soy un abogado, pero recomendaría no utilizar esta respuesta en su proyecto a menos que sea plenamente consciente de las implicaciones de incluir este código con licencia.
Nick Spacek
0

Después de buscar una gran cantidad de soluciones de prueba para evitar que sqlmap sea inyectado por sqlmap, en el caso de sistemas heredados que no pueden aplicar declaraciones preparadas en todas partes.

java-security-cross-site-scripting-xss-and-sql-injection topic FUE LA SOLUCIÓN

Probé la solución de @Richard pero no funcionó en mi caso. utilicé un filtro

El objetivo de este filtro es envolver la solicitud en un contenedor con código propio MyHttpRequestWrapper que transforma:

los parámetros HTTP con caracteres especiales (<,>, ', ...) en códigos HTML a través del método org.springframework.web.util.HtmlUtils.htmlEscape (...). Nota: Hay una clase similar en Apache Commons: org.apache.commons.lang.StringEscapeUtils.escapeHtml (...) los caracteres de inyección SQL (', “, ...) a través de la clase 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
fuente
¿Es bueno el java-security-cross-site-scripting-xss-and-sql-injection topic? Estoy tratando de encontrar una solución para una aplicación heredada.
Caot
0

De: [Fuente]

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
fuente
0

Si está utilizando PL / SQL, también puede usar DBMS_ASSERT , puede desinfectar su entrada para que pueda usarlo sin preocuparse por las inyecciones de SQL.

vea esta respuesta por ejemplo: https://stackoverflow.com/a/21406499/1726419

yossico
fuente