Início‎ > ‎

Evitar "command injection"

Esta classe de ataques, muito comum em ambientes web, consiste na inserção de comandos nos dados passados como entrada para a aplicação de forma que estes comandos sejam executados pela ou através da aplicação. As subclasses mais comuns são SQL injection e Shell Command Injection. Injeção de SQL, ou SQL Injection, consiste em inserir comandos SQL nos dados enviados para a aplicação de modo a acessar o banco de dados subjacente. Por exemplo, suponhamos que uma aplicação web esteja vulnerável e contenha uma sentença SQL: "SELECT Login, Nome FROM Usuario WHERE Login = '" + sParam + "'". Se o adversário passar para esta aplicação a string "zé' OR '2'='2'", o resultado será: "SELECT Login, Nome FROM Usuario WHERE Login = 'zé' OR '2'='2'". Esta sentença SQL retornará a lista de todos os usuários da aplicação, que poderá ser usada posteriormente como subsídio para outros ataques. O adversário poderia também inserir comandos visando alterar ou apagar dados do banco, causando estragos ainda maiores. A injeção de comandos shell acontece de forma similar se a aplicação acessa o sistema operacional através de um interpretador de comandos.

A maneira mais simples de proteger uma aplicação contra injeção de comandos é evitar o uso de interpretadores externos, como um interpretador SQL ou um shell. Para evitar o uso de comandos de shell, devemos usar as bibliotecas do sistema que realizem as funções específicas necessárias.

Nos casos em que não há alternativa, tais como as chamadas a interpretadores SQL de bancos de dados, devemos validar criteriosamente os dados recebidos e certificar que estes dados não incluam nenhum comando ou conteúdo potencialmente nocivo. Devemos também estruturar as requisições de maneira que os parâmetros recebidos sejam efetivamente tratados como dados e não como comandos. No caso de Java, pode-se empregar também stored procedures e prepared statements (representados em Java pelas classes CallableStatement, quando são usadas stored procedures, e PreparedStatement, quando se usa SQL) para aumentar o nível de proteção à informação.

O exemplo abaixo ilustra o uso incorreto de PreparedStatement, pois insere os dados recebidos pela aplicação diretamente no comando SQL que será enviado para o banco de dados.

String name = request.getParameter("name");

PreparedStatement pstmt = conn.prepareStatement("insert into EMP (ENAME) values ('" + name + "')");

pstmt.execute();

pstmt.close();

A maneira correta seria:

PreparedStatement pstmt = conn.prepareStatement ("insert into

EMP (ENAME) values (?)");

String name = request.getParameter("name");

pstmt.setString (1, name);

pstmt.execute();

pstmt.close();

No segundo exemplo, o parâmetro name é tratado exclusivamente como dado (mais especificamente uma string), o que protege a aplicação contra a tentativa de injeção de código SQL.

Abaixo, temos um trecho de código onde o CallableStatement é usado de forma incorreta:

String name = request.getParameter("name");

String sql = "begin ? := GetPostalCode('" + name + "'); end;";

CallableStatement cs = conn.prepareCall(sql);

cs.registerOutParameter(1, Types.CHAR);

cs.executeUpdate();

String result = cs.getString(1);

cs.close();

No código acima, há a possibilidade que o código SQL enviado ao servidor seja (o texto em vermelho corresponde ao suposto conteúdo do parâmetro name):

begin ? := GetPostalCode(''); delete from users; commit; dummy(''); end;

A maneira correta de usar o CallableStatement está mostrada abaixo. É possível perceber que há o registro do parâmetro name, como dado contendo caracteres.

String name = request.getParameter("name");

CallableStatement cs = conn.prepareCall ("begin ? :=
GetStatePostalCode(?); end;");

cs.registerOutParameter(1,Types.CHAR);

cs.setString(2, name);

cs.executeUpdate();

String result = cs.getString(1);

cs.close();

Nos casos em que é usado o framework Hibernate, as manipulações de dados mais simples ou corriqueiras são tratadas diretamente pelo framework. No entanto, o Hibernate disponibiliza a linguagem HQL (ou Hibernate Query Language) para que os programadores possam definir consultas ou manipulações complexas dos dados da aplicação. Embora o HQL apresente algumas restrições no conteúdo dos comandos que diminuam o risco de ataques de injeção de comandos, os riscos não são totalmente eliminados. Assim, são necessários os mesmos cuidados que no caso do uso direto de SQL para acesso à base de dados.

O exemplo a seguir apresenta um comando HQL passível de ser atacado por injeção de código, através do parâmetro paymentIds:

Payment payment = (Payment) session.find("from com.example.Payment as payment where payment.id = " + paymentIds.get(i));

O mesmo trecho pode ser reescrito usando outras versões da função session.find que permitem definir explicitamente os tipos dos dados a serem passados para a base de dados. Desta forma, o risco de ataques por injeção de comandos é bastante reduzido, conforme demonstra o exemplo abaixo:

String pId = paymentIds.get(i);

TsPayment payment = (TsPayment) session.find("from com.example.Payment as payment where payment.id = ?", pId, StringType);

Comments