TL;DR
Os ataques de SQL Injection (SQLi) continuam a ser uma ameaça significativa, mesmo em aplicações web modernas. Eles exploram vulnerabilidades na forma como as aplicações interagem com bases de dados, muitas vezes através de entradas de utilizador mal tratadas. Embora os ORMs (Object Relational Mappers) reduzam o risco, não o eliminam completamente — especialmente quando os programadores os contornam para criar queries manuais. A melhor proteção é usar instruções preparadas e implementar filtragem de entradas. Além disso, deves sempre armazenar dados sensíveis, como palavras-passe, de forma segura, para prevenir que ataques bem-sucedidos possam lê-los.
Ataques de SQL Injection
As aplicações web normalmente armazenam e gerem dados numa base de dados. Para interagir com esses dados — quer seja para recuperar informações de utilizadores, armazenar detalhes de clientes ou processar encomendas — utilizam-se queries SQL (Structured Query Language). Estas queries permitem que a aplicação comunique com a base de dados e realize operações como selecionar, atualizar ou eliminar registos.
O SQL Injection (SQLi) é um método de ataque que explora vulnerabilidades nessas queries SQL. Ocorre quando um atacante manipula campos de entrada (como formulários de login ou barras de pesquisa) para injetar código SQL malicioso na query. Se a entrada não for devidamente sanitizada, a base de dados executa o código injetado, permitindo potencialmente ao atacante ler, modificar ou eliminar dados sensíveis.
Por exemplo, considere a seguinte query SQL em PHP:
Neste exemplo, a query recebe a entrada do utilizador ($search
) e insere-a diretamente na instrução SQL. Se um atacante submeter algo como ' OR '1'='1'--
, a query resultante será:
SELECT * FROM products WHERE name LIKE '%' OR '1'='1';--%';
Esta query agora contorna a funcionalidade pretendida e devolve todos os registos da base de dados, expondo potencialmente informações sensíveis.
Outros Exemplos
Este tipo de vulnerabilidade pode ser encontrado em qualquer linguagem ou framework, como Node.js e ASP.NET, quando as entradas de utilizador não são devidamente sanitizadas e são colocadas diretamente em queries SQL. Usando o mesmo payload ' OR '1'='1'--
, pode-se ver que um atacante também seria capaz de manipular o comportamento da base de dados nos exemplos a seguir.
Exemplo em ASP.NET:
Exemplo em Node.js:
Impacto dos Ataques SQLi
Embora estes exemplos sejam simples, sem qualquer impacto real, os ataques de SQL Injection podem ter consequências graves para as organizações e indivíduos, pois podem levar ao controlo completo sobre a base de dados e seus dados, dependendo das permissões concedidas ao utilizador da base de dados.
Seguindo os mesmos exemplos anteriores, imagine que um atacante insere o seguinte texto no campo de entrada do utilizador da aplicação:
' UNION SELECT 1,concat(username,':',password),1,1,1 FROM users -- -
A query resultante seria:
SELECT * FROM products WHERE name LIKE '%' UNION SELECT 1,concat(username,':',password),1,1,1 FROM users -- -%';
Isto resultaria na extração de todos os nomes de utilizador e palavras-passe da base de dados, como demonstrado pelo exemplo de laboratório abaixo (que corresponde ao exemplo em PHP dado anteriormente):
Além deste cenário de exploração, as vulnerabilidades de SQL Injection também podem levar à alteração/eliminação de registos importantes da base de dados, ou até ao comprometimento completo do sistema, nos casos em que seja possível abusar das permissões concedidas ao utilizador da base de dados para elevar privilégios e ganhar controlo do servidor.
SQL Injection nos Dias de Hoje
Um equívoco comum é que as práticas de desenvolvimento web modernas, especialmente o uso de ORMs (Object Relational Mappers), protegem automaticamente contrO SQL Injection. Embora os ORMs possam ajudar a reduzir a probabilidade de SQLi, ao abstrair as interações com a base de dados, eles não são infalíveis.
Por exemplo, durante algumas avaliações de Penetration Testing que realizei ao longo dos anos, notei que, embora as empresas muitas vezes confiem em ORMs para queries simples, recorrem a queries SQL manuais quando operações mais complexas são necessárias. Se não forem tratadas com cuidado, isto pode acabar por introduzir vulnerabilidades de SQLi novamente.
Por exemplo, aqui está um exemplo de código vulnerável numa aplicação que usa Sequelize, em Node.js:
Neste caso, se o parâmetro snippet
não for sanitizado, um atacante poderia introduzir SQL malicioso, explorando uma vulnerabilidade de SQLi na tua aplicação.
Corrigindo e Prevenindo Falhas de SQL Injection
Existem duas principais recomendações para corrigir e prevenir vulnerabilidades de SQL Injection. Estas técnicas devem ser implementadas em conjunto para proporcionar uma melhor proteção para a aplicação.
Para cada exemplo apresentado abaixo, também haverá um trecho de código disponível, que pode ser visualizado e copiado se desejar, clicando no ícone de alternância ao lado do nome da linguagem.
Prepared Statements
A forma mais eficaz de prevenir falhas de SQL Injection é usar prepared statements. Prepared statements separam o código SQL dos dados sendo inseridos, garantindo que a entrada do usuário seja tratada como dados em vez de código executável.
Os seguintes trechos de código demonstram como os exemplos vulneráveis anteriores foram modificados para corrigir a vulnerabilidade de SQL Injection usando Prepared Statements:
PHP:
if (isset($_GET['search'])) {
$search = '%' . $_GET['search'] . '%';
$stmt = $conn->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', $search);
} else {
$stmt = $conn->prepare("SELECT * FROM products");
}
$stmt->execute();
$result = $stmt->fetchAll();
ASP.NET:
string name = Request.*queriestring*["name"];
string query = "SELECT * FROM customers WHERE name=@name";
SqlCommand cmd = new SqlCommand(query, conn);
cmd.Parameters.AddWithValue("@name", name);
SqlDataReader reader = cmd.ExecuteReader();
Node.js:
const name = req.query.name;
const query = "SELECT * FROM customers WHERE name=?";
db.query(query, [name], (error, results) => {
if (error) throw error;
res.send(results);
})
Sequelize ORM (using Node.js):
function findItems(req, resp)
{
try {
sequelize.query(
"SELECT Desc FROM Items WHERE Desc like ?",
{ replacements: [ '%' + req.params.snippet + '%'],
type: sequelize.QueryTypes.SELECT }
)
.spread(function(results, metadata) {
});
catch {
}
}
}
Filtros de Input com Whitelists
Além disso, deve-se implementar filtros nos inputs. Recomenda-se uma abordagem de whitelist, o que significa permitir apenas caracteres específicos nas entradas dos usuários. Por exemplo, você pode restringir um campo de nome de usuário para permitir apenas caracteres alfanuméricos.
Embora isso esteja sendo mostrado para SQL Injection, na verdade é uma excelente forma de prevenir muitos tipos de ataques de injeção, pois bloqueará a maioria dos caracteres maliciosos possíveis se configurado corretamente.
Os seguintes trechos de código demonstram como os exemplos vulneráveis anteriores foram modificados para corrigir a vulnerabilidade de SQL Injection usando Filtros de Input com uma abordagem de whitelist de caracteres:
PHP:
if (isset($_GET['search'])) {
$search = trim($_GET['search']);
if (!preg_match('/^[a-zA-Z0-9]+$/', $search)) {
echo 'Invalid search parameter';
exit;
}
$search = '%' . $search . '%';
$stmt = $conn->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->bindParam(':search', $search);
} else {
$stmt = $conn->prepare("SELECT * FROM products");
}
$stmt->execute();
$result = $stmt->fetchAll();
ASP.NET:
// You must import the System.Text.RegularExpressions library in order to use Regex
var commentText = context.Request.Form["comment"];
// If Regex.IsMatch not alphanumerical, shows error message
string pattern = @"^[a-zA-Z0-9\s]*$";
if (!Regex.IsMatch(commentText, pattern))
{
return context.Response.WriteAsync("Invalid comment. Please try again.");
}
else
{
comments.Add(new Comment { Text = commentText });
Node.js:
// Only allow alphanumerical characters and valid email format
const pattern = /^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+$/;
const input = '[email protected]'; // User input to validate
if (pattern.test(input)) {
// Valid email format and alphanumerical characters
} else {
// Invalid Input
}
Dicas Adicionais
Aqui estão algumas práticas recomendadas de segurança a serem mantidas em mente:
- Nunca armazene senhas em texto claro ou em formatos reversíveis. Use algoritmos de hashing seguros (por exemplo, bcrypt, Argon2) para garantir que, mesmo se ocorrer uma violação, as senhas não sejam comprometidas imediatamente.
- Minimize os privilégios do banco de dados para a sua aplicação. Por exemplo, se a sua aplicação web precisa apenas ler dados, não dê permissão para escrever ou excluir dados.
- Atualize regularmente as dependências em sua aplicação, especialmente bibliotecas ORM, drivers de banco de dados e pacotes de segurança. Falhas de segurança são frequentemente descobertas, e atualizações regulares ajudam a corrigir vulnerabilidades.