Java - string de escape para impedir a injeção de SQL


146

Estou tentando colocar alguma injeção anti-sql no java e estou com muita dificuldade em trabalhar com a função de cadeia "replaceAll". Por fim, preciso de uma função que converta qualquer existente \para \\, qualquer "para \", qualquer 'para \'e qualquer \npara, \\npara que quando a string for avaliada pelas injeções de SQL do MySQL sejam bloqueadas.

Eu criei um código com o qual estava trabalhando e todas \\\\\\\\\\\as funções estão fazendo meus olhos enlouquecerem. Se alguém tiver um exemplo disso, eu apreciaria muito.


1
Ok, cheguei à conclusão de que PreparedStatements é o caminho a seguir, no entanto, com base nos objetivos atuais, preciso prosseguir conforme planejado originalmente e apenas colocar um filtro no momento e, assim que o marco atual for atingido, posso volte e refatorar o banco de dados para a declaração preparada. Nesse meio tempo, para manter a dinâmica, alguém tem uma solução para escapar efetivamente dos caracteres acima para o MySQL, considerando o Java e seu sistema de expressão regular é uma dor absoluta para calcular o número de fugas necessárias ...
Scott Bonner

2
Nem todas as instruções SQL são parametrizável, por exemplo "ROLE_NAME SET ROLE" ou "ouvir channel_name"
Neil McGuigan

1
@NeilMcGuigan Yep. A maioria dos drivers também se recusará a parametrizar algo como, CREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?já que a instrução principal é uma declaração DDL , mesmo que a parte que você está tentando parametrizar seja realmente DML .
SeldomNeedy 22/02

Respostas:


249

PreparedStatements são o caminho a seguir, porque tornam impossível a injeção de SQL. Aqui está um exemplo simples, considerando a entrada do usuário 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
      }
   }
}

Não importa quais caracteres estejam no nome e no email, esses caracteres serão colocados diretamente no banco de dados. Eles não afetarão a instrução INSERT de forma alguma.

Existem diferentes métodos de conjunto para diferentes tipos de dados - o que você usa depende dos campos de seu banco de dados. Por exemplo, se você tiver uma coluna INTEGER no banco de dados, use um setIntmétodo A documentação PreparedStatement lista todos os diferentes métodos disponíveis para definir e obter dados.


1
através desse método, você pode tratar todos os parâmetros como uma string e ainda estar seguro? Eu estou tentando descobrir uma maneira de atualizar a minha arquitetura existente para ser seguro sem ter que reconstruir a camada de banco de dados inteiro ...
Scott Bonner

1
Todo SQL dinâmico é apenas string, então essa não é a pergunta a ser feita. Não estou familiarizado com o PrepareStatement, então a verdadeira questão é gerar uma consulta parametrizada que pode ser executada com o ExecuteUpdate. Se sim, isso é bom. Se não, simplesmente está ocultando o problema, e você pode não ter nenhuma opção segura, exceto redesenhar a camada do banco de dados. Lidar com a injeção de SQL é uma daquelas coisas que você precisa projetar desde o início; não é algo que você possa adicionar facilmente mais tarde.
Cylon Cat

2
Se você estiver inserindo em um campo INTEGER, convém usar um 'setInt'. Da mesma forma, outros campos numéricos do banco de dados usariam outros setters. Publiquei um link para os documentos de PreparedStatement que listam todos os tipos de setter.
Kaleb Brasee 28/11/2009

2
Sim Cylon, PreparedStatements gera consultas parametrizadas.
Kaleb Brasee 28/11/2009

2
@ Kaleb Brasee, obrigado. É bom saber disso. As ferramentas são diferentes em todos os ambientes, mas a busca parametrizada é a resposta fundamental.
Cylon Cat

46

A única maneira de impedir a injeção de SQL é com SQL parametrizado. Simplesmente não é possível criar um filtro mais inteligente do que as pessoas que usam o SQL para viver.

Portanto, use parâmetros para todas as entradas, atualizações e cláusulas where. O SQL dinâmico é simplesmente uma porta aberta para hackers, e isso inclui SQL dinâmico em procedimentos armazenados. Parametrizar, parametrizar, parametrizar.


11
E mesmo o SQL parametrizado não é uma garantia de 100%. Mas é um começo muito bom.
31410 duffymo

2
@ Duffymo, concordo que nada é 100% seguro. Você tem um exemplo de injeção de SQL que funcionará mesmo com SQL parametrizado?
Cylon Cat

3
@ Cat Cylon: Claro, quando um pedaço de SQL (como @WhereClause ou @tableName) é passado como parâmetro, concatenado no SQL e executado dinamicamente. A injeção de SQL ocorre quando você permite que os usuários escrevam seu código. Não importa se você captura o código como parâmetro ou não.
27411 Steve Kass

16
BTW, não sei por que isso não é mencionado mais, mas trabalhar com PreparedStatements também é muito mais fácil e muito mais legível. Só isso provavelmente os torna o padrão para todos os programadores que os conhecem.
Edan Maor

3
Observe que as PreparedStatements para alguns bancos de dados são MUITO caras de criar; portanto, se você precisar fazer muitos deles, meça os dois tipos.
Thorbjørn Ravn Andersen

36

Se você realmente não pode usar a Opção 1 de Defesa: Instruções Preparadas (Consultas Parametrizadas) ou a Opção 2 de Defesa: Procedimentos Armazenados , não crie sua própria ferramenta, use a API de Segurança Corporativa da OWASP . No ESAPI da OWASP hospedado no Google Code:

Não escreva seus próprios controles de segurança! Reinventar a roda quando se trata de desenvolver controles de segurança para todos os aplicativos ou serviços da Web leva a perda de tempo e grandes falhas de segurança. Os kits de ferramentas da API de segurança corporativa da OWASP (ESAPI) ajudam os desenvolvedores de software a se protegerem contra falhas de projeto e implementação relacionadas à segurança.

Para obter mais detalhes, consulte Prevenção de injeção SQL em Java e Folha de dicas de prevenção de injeção SQL .

Preste atenção especial à Opção 3 de Defesa: Escapando Todas as Entradas Fornecidas pelo Usuário que apresenta o projeto ESAPI da OWASP ).


4
A ESAPI parece extinta a partir de hoje. Na AWS, existe o WAF que pode ajudar contra a injeção de SQL, XSS, etc. Existem outras alternativas neste momento?
ChrisOdney

@ChrisOdney Um WAF pode ser facilmente ignorado. A maioria das estruturas já implementa sua própria prevenção de injeção de SQL, na qual eles escapam automaticamente dos parâmetros por conta própria. Alternativas para projetos legados: owasp.org/index.php/…
Javan R.

19

(Isso é uma resposta ao comentário do OP na pergunta original; concordo plenamente que o PreparedStatement é a ferramenta para este trabalho, não as expressões regulares.)

Quando você diz \n, você quer dizer a sequência \+ nou um caractere de avanço de linha real? Se for \+ n, a tarefa é bem simples:

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

Para corresponder a uma barra invertida na entrada, coloque quatro delas na sequência de caracteres regex. Para colocar uma barra invertida na saída, coloque quatro delas na cadeia de substituição. Isso pressupõe que você esteja criando as regexes e substituições na forma de literais Java String. Se você criá-los de outra maneira (por exemplo, lendo-os de um arquivo), você não precisa fazer tudo isso duas vezes.

Se você tiver um caractere de avanço de linha na entrada e desejar substituí-lo por uma sequência de escape, poderá fazer uma segunda passagem sobre a entrada com este:

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

Ou talvez você queira duas barras invertidas (não estou muito claro sobre isso):

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

1
Obrigado pelo comentário, eu gosto do jeito que você fez todos os personagens em um, eu estava falando sobre isso com a maneira menos regular de substituir todos por cada um ... Não sei como atribuir a resposta a esta pergunta agora . Em última análise, PreparedStatements é a resposta, mas, para o meu objetivo atual, sua resposta é a resposta que preciso, você ficaria chateado se eu desse a resposta a uma das respostas da declaração preparada anteriormente ou há uma maneira de compartilhar a resposta entre um casal?
23430 Scott Bonner

1
Como esse é apenas um argumento temporário, vá em frente e aceite uma das respostas do PreparedStatement.
Alan Moore

12

As PreparedStatements são o caminho a seguir na maioria dos casos, mas não em todos os casos. Às vezes, você se encontra em uma situação em que uma consulta, ou parte dela, precisa ser criada e armazenada como uma string para uso posterior. Consulte a Folha de dicas sobre prevenção de injeção de SQL no site da OWASP para obter mais detalhes e APIs em diferentes linguagens de programação.


1
As folhas de dicas do OWASP foram movidas para o GitHub. A folha de fraude SQL Injection agora está aqui: github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/...
theferrit32

10

As instruções preparadas são a melhor solução, mas se você realmente precisar fazer isso manualmente, também poderá usar a StringEscapeUtilsclasse da biblioteca Apache Commons-Lang. Tem um escapeSql(String)método, que você pode usar:

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


2
Para referência: commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/… De qualquer forma, esse método apenas escapa aspas e não parece impedir ataques de injeção de SQL.
Paco Abato

11
Este foi removido das versões mais recentes porque ele só estava escapando aspas simples
Pini Cheyni

2
Esta resposta deve ser excluída porque não impede a injeção de sql.
Javan R.

9

O uso de uma expressão regular para remover texto que possa causar uma injeção de SQL parece que a instrução SQL está sendo enviada ao banco de dados por StatementumPreparedStatement .

Uma das maneiras mais fáceis de evitar uma injeção de SQL em primeiro lugar é usar um PreparedStatement , que aceita dados para substituir uma instrução SQL usando espaços reservados, que não depende de concatenações de cadeias para criar uma instrução SQL para enviar ao banco de dados.

Para obter mais informações, o uso de instruções preparadas nos tutoriais Java seria um bom ponto de partida.


6

Caso você esteja lidando com um sistema legado ou tenha muitos lugares para mudar para PreparedStatements em muito pouco tempo - por exemplo, se houver um obstáculo ao uso da melhor prática sugerida por outras respostas, tente o AntiSQLFilter


5

Você precisa do seguinte código abaixo. À primeira vista, isso pode parecer com qualquer código antigo que eu criei. No entanto, o que fiz foi verificar o código fonte de http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatement. java . Depois disso, examinei cuidadosamente o código de setString (int parameterIndex, String x) para encontrar os caracteres dos quais ele escapa e personalizo isso para minha própria classe, para que possa ser usado para os fins que você precisa. Afinal, se essa é a lista de caracteres dos quais o Oracle escapa, saber que isso é realmente reconfortante em termos de segurança. Talvez a Oracle precise de um empurrão para adicionar um método semelhante a este para a próxima versão principal do 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
Eu acho que esse código é a versão descompilada do código-fonte no link acima. Agora, em mais recentes mysql-connector-java-xxx, o case '\u00a5'e case '\u20a9'declarações parecem ter sido removido
ZHY

Eu tentei o sqlmap com seu código e ele não me protegeu do primeiro ataque `Tipo: blind baseado em booleano Título: AND blind baseado em booleano - cláusula WHERE ou HAVING Carga útil: q = 1% 'AND 5430 = 5430 AND'% ' = ''
shareef 9/09/16

Desculpe o seu trabalho, mas estava vendo os últimos resultados da sessão armazenados .. i manteve o comentário para o futuro semelhante ..
shareef

Você pode usar org.ostermiller.utils.StringHelper.escapeSQL()ou com.aoindustries.sql.SQLUtility.escapeSQL().
Mohamed Ennahdi El Idrissi 04/10

1
É importante observar que a licença GPLv2 no código original foi copiada para qualquer pessoa que se depare com isso. Não sou advogado, mas recomendo não usar esta resposta em seu projeto, a menos que esteja totalmente ciente das implicações da inclusão desse código licenciado.
quer

0

Depois de pesquisar em um monte de soluções de teste para evitar que o sqlmap seja injetado no sql, no caso de um sistema legado que não pode aplicar declarações preparadas em todos os lugares.

tópico java-security-cross-site-scripting-xss-and-sql-injection foi a solução

Tentei a solução de @Richard, mas não funcionou no meu caso. eu usei um filtro

O objetivo desse filtro é envolver a solicitação em um invólucro MyHttpRequestWrapper com código próprio que transforma:

os parâmetros HTTP com caracteres especiais (<,>, ',…) nos códigos HTML por meio do método org.springframework.web.util.HtmlUtils.htmlEscape (…). Nota: Existe uma classe semelhante no Apache Commons: org.apache.commons.lang.StringEscapeUtils.escapeHtml (…) os caracteres de injeção SQL (', “,…) por meio da classe org.apache.commons.lang.StringEscapeUtils do Apache Commons. 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;
    }
}

Isso é bom java-security-cross-site-scripting-xss-and-sql-injection topic? Estou tentando encontrar uma solução para um aplicativo herdado.
caot 12/06

0

De: [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;

}


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.