SQL Injection é um tipo de ataque à base de dados de uma aplicação onde um atacante injeta código SQL malicioso nos parâmetros da consulta, obtendo acesso não autorizado aos dados ou a possibilidade de os modificar. Segundo a OWASP (2025), a SQL Injection continua a ser uma das vulnerabilidades críticas capaz de levar a um compromisso total da base de dados. Injeção de código SQL permite ao atacante ler, modificar e apagar registos e, em alguns casos, obter acesso ao sistema operativo do servidor.
Pontos principais
SQL Injection é uma vulnerabilidade que ocorre quando uma aplicação constrói consultas SQL através de concatenação de strings com dados do utilizador. Um atacante passa uma string especialmente concebida num parâmetro de consulta que altera a estrutura do comando SQL. Em vez de serem dados, a string injetada torna-se parte do código SQL, permitindo ao atacante executar consultas arbitrárias contra a base de dados. Segundo o Relatório de Violações de Dados da Verizon (2025), a SQL Injection está presente em 8% de todas as violações de dados investigadas.
Apesar de ser uma vulnerabilidade conhecida (mencionada pela primeira vez no final dos anos 90), a SQL Injection ainda é encontrada em aplicações modernas. A razão é o erro humano: os programadores escrevem código com concatenação de strings, o código legado não é refatorado e os frameworks ORM são usados incorretamente (por exemplo, consultas raw com interpolação de strings). Segundo a Veracode (2026), cerca de 14% de todas as aplicações analisadas contêm pelo menos uma vulnerabilidade SQLi.
Uma injeção SQL bem-sucedida dá ao atacante uma vasta gama de capacidades: ler qualquer tabela da base de dados, incluindo hashes de palavras-passe e dados pessoais dos utilizadores; modificar e apagar registos; executar operações administrativas (DROP TABLE, TRUNCATE); e, em algumas configurações, execução remota de comandos através de xp_cmdshell (MSSQL) ou INTO OUTFILE (MySQL). As consequências vão desde a fuga de dados do utilizador até à perda total de controlo do sistema.
As injeções SQL são classificadas pela forma como os dados são extraídos da base de dados. A escolha do método depende de como a aplicação processa os resultados das consultas e as mensagens de erro. A classificação OWASP identifica três tipos principais: In-band (dados extraídos através do mesmo canal), Inferential/Blind (inferências lógicas) e Out-of-band (dados transmitidos através de outro canal).
| Tipo | Método de extração | Complexidade | Frequência |
|---|---|---|---|
| In-band (clássico) | Diretamente através do resultado da consulta | Baixa | Alta |
| Blind SQLi | Inferências lógicas a partir de respostas do servidor | Alta | Média |
| Out-of-band | Através de um canal externo (DNS, HTTP) | Média | Baixa |
O tipo mais comum. Um atacante injeta código SQL num parâmetro de consulta, e o resultado da injeção é diretamente visível na resposta do servidor. Dois subtipos: Error-based (através de mensagens de erro da BD) e UNION-based (através do operador UNION SELECT). Error-based utiliza informações de mensagens de erro, como um erro de sintaxe do MySQL que pode revelar o nome da tabela ou a estrutura da consulta. UNION-based permite combinar resultados de consultas legítimas com dados de outras tabelas da base de dados.
Utilizada quando a aplicação não mostra os resultados da consulta nem as mensagens de erro. Um atacante faz perguntas de sim/não enviando consultas com condições lógicas e analisando as diferenças nas respostas do servidor (por exemplo, tempo de resposta ou conteúdo da página). Time-based Blind SQLi utiliza funções de atraso (SLEEP, WAITFOR DELAY) para confirmar condições — se a página demorar mais a carregar, a condição é verdadeira. Este método é muito lento — extrair um único registo pode levar horas.
# Exemplo de Blind SQL Injection (baseada em tempo)
# Se SQLi for vulnerável, SLEEP(2) executa se a condição for cumprida
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# Se a resposta chegar após >2 segundos — a primeira letra da palavra-passe é 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Os dados são transmitidos não através da resposta HTTP mas através de canais alternativos: consultas DNS, pedidos HTTP para um servidor externo, SMTP. Utilizada quando a aplicação não devolve resultados de consultas nem mostra erros. O MySQL suporta a função LOAD_FILE(), que pode iniciar uma consulta DNS, e o MSSQL tem xp_dirtree para enviar dados para um servidor SMB remoto. Out-of-band SQL Injection é eficaz mas requer configuração adicional do servidor atacante e funções específicas da BD.
O mecanismo da SQL Injection baseia-se no facto de o SQL utilizar aspas para literais de string. Se uma aplicação inserir a entrada do utilizador diretamente numa consulta SQL sem a escapar, um atacante pode “fechar” a string e adicionar código SQL arbitrário. Por exemplo, na consulta SELECT * FROM users WHERE name = '$input', inserir ' OR '1'='1 transforma-a em SELECT * FROM users WHERE name = '' OR '1'='1', que devolve todos os utilizadores.
Considere um formulário de início de sessão com a consulta SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Se um atacante introduzir admin' -- no campo de utilizador e deixar a palavra-passe em branco, a consulta resultante torna-se SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Os caracteres -- comentam o resto da consulta, desativando a verificação da palavra-passe. O servidor devolve o registo do utilizador admin, e o atacante inicia sessão sem conhecer a palavra-passe.
# Exemplo de SQL Injection — Evasão de autenticação
# CÓDIGO VULNERÁVEL: concatenação direta de strings
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" anula a verificação da palavra-passe
# VERSÃO SEGURA: consulta parametrizada
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
O operador UNION SELECT permite combinar resultados de duas consultas SELECT. Se um atacante encontrar um parâmetro vulnerável, adiciona UNION SELECT com uma consulta de outra tabela. Por exemplo: ' UNION SELECT username, password FROM admins --. A condição de sucesso é que o número de colunas em ambas as consultas deve coincidir. O número de colunas é determinado através de ORDER BY (inserindo ' ORDER BY 1--, depois 2, 3... até ocorrer um erro). Conhecendo o número de colunas, o atacante insere UNION SELECT com o mesmo número de campos.
As aplicações móveis enfrentam SQL Injection tanto no lado do servidor (API) como no lado do cliente — em bases de dados locais (SQLite, Realm). Embora a SQLi do lado do servidor nas APIs móveis seja semelhante às aplicações web, as bases de dados locais criam um vetor adicional. Se uma aplicação armazena dados em SQLite e executa consultas com concatenação de strings, os dados maliciosos que entram na base de dados local através da API podem desencadear SQLi durante o processamento posterior.
A base de dados local SQLite num dispositivo também é vulnerável a SQL Injection se a aplicação construir consultas concatenando strings. Os Content Providers no Android e o Core Data no iOS utilizam parametrização por defeito, mas as consultas raw requerem atenção do programador. O SQLite não suporta múltiplas consultas separadas por ponto e vírgula, o que limita as opções do atacante mas não protege contra a leitura de dados através de condições WHERE. Utilize sempre selectionArgs no Android e NSPredicate com parâmetros no iOS.
A API com a qual uma aplicação móvel comunica é vulnerável como qualquer servidor web. Os programadores móveis muitas vezes assumem que a SQLi é apenas um problema do backend, mas a vulnerabilidade ocorre no endpoint da API que aceita parâmetros do cliente. A separação de responsabilidades não protege: se o programador do backend se esqueceu de parametrizar a consulta, a aplicação móvel do utilizador torna-se um vetor de ataque. Exija ao backend que utilize ORM ou prepared statements.
// Exemplo de SQL Injection em SQLite local no Android
// CÓDIGO VULNERÁVEL: concatenação direta
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// VERSÃO SEGURA: parametrização através de selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
A proteção contra SQL Injection baseia-se num princípio simples: nunca confie na entrada do utilizador em consultas SQL. O único método fiável é a parametrização de consultas (prepared statements), onde o código SQL e os dados são passados separadamente. Todos os outros métodos — escaping, validação, WAF — são camadas de segurança adicionais mas não substituem a parametrização. Segundo a OWASP (2025), a parametrização previne 100% dos ataques de SQL Injection.
Ao usar prepared statements, a consulta SQL é primeiro compilada pelo servidor de BD sem dados, e depois os valores dos parâmetros são passados separadamente. A base de dados trata os parâmetros como dados, não como código executável. Mesmo que um atacante passe ' OR '1'='1, a base de dados interpreta-o como um valor de string, não como código SQL. Os prepared statements são suportados por todas as linguagens e frameworks modernos: PDO em PHP, PreparedStatement em Java, cursor.execute em Python.
Os ORM modernos (Hibernate, Entity Framework, SQLAlchemy, Room) utilizam automaticamente a parametrização ao executar consultas, a menos que o programador mude para consultas raw. No entanto, os ORM não protegem completamente: construções como @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) em JPA requerem a passagem de parâmetros através de parâmetros nomeados, não por concatenação. Os Query Builders (Knex, jOOQ) também parametrizam as consultas por defeito se não forem usados métodos raw.
O escaping de caracteres especiais (mysql_real_escape_string) é um método obsoleto que não protege contra todos os tipos de SQL Injection. O problema: o escaping depende da codificação e pode ser contornado ao usar codificações multibyte (por exemplo, GBK em sistemas asiáticos). Use o escaping apenas em código legado onde a parametrização é impossível, e sempre em combinação com uma validação rigorosa do tipo de dados de entrada.
| Método | Eficácia | Recomendação |
|---|---|---|
| Prepared Statements | 100% | Obrigatório para todas as consultas |
| ORM (uso correto) | 99% | Recomendado |
| Escaping de strings | 70% (depende da codificação) | Apenas legado |
| Validação de entrada (white-list) | 50% (apenas números) | Adicional |
| WAF (Web Application Firewall) | 60% | Adicional |
A conta da aplicação deve ter os privilégios mínimos necessários: SELECT, INSERT, UPDATE, DELETE — apenas nas tabelas que a aplicação realmente necessita. Proíba o uso de DROP, TRUNCATE, CREATE para a conta da aplicação. Isto limita os danos mesmo em caso de uma injeção SQL bem-sucedida: o atacante não conseguirá eliminar tabelas nem executar operações administrativas.
Os testes regulares de SQL Injection devem fazer parte do pipeline de desenvolvimento seguro. Uma combinação de análise estática, análise dinâmica e testes de penetração manuais oferece os melhores resultados. Segundo o Relatório de Cibersegurança da Synopsys (2025), os scanners automatizados encontram até 70% das vulnerabilidades SQLi, mas os ataques Blind complexos requerem testes manuais.
Para aplicações móveis, a análise de SQLite local também é importante: verifique todas as chamadas rawQuery, consultas ContentProvider e consultas Room com rawQuery. Ferramentas: Android Studio Lint (deteta SQLi em SQLite), MobSF (Mobile Security Framework) para análise estática e dinâmica automática de APK/IPA. Recomenda-se também testar os endpoints da API através de sqlmap com interceção de proxy do tráfego da aplicação móvel.
Perguntas frequentes
SQL Injection ataca bases de dados relacionais através de consultas SQL. NoSQL Injection afeta bases de dados não relacionais (MongoDB, Couchbase) através dos seus operadores de consulta ($gte, $ne, $where). No MongoDB, a injeção é possível se a aplicação construir um documento BSON a partir de uma string JSON. Os mecanismos de proteção são semelhantes: parametrização e validação de tipos.
Encontre todos os locais onde as consultas SQL são formadas por concatenação de strings com dados do utilizador. Procure padrões como "SELECT ... WHERE id = " + userId ou f"UPDATE ... SET name = '{name}'". Cada linha deste tipo é uma potencial injeção SQL. Substitua-as todas por consultas parametrizadas ou prepared statements.
Os frameworks ORM protegem automaticamente apenas se utilizar os seus métodos Query Builder e parâmetros nomeados. Se utilizar consultas raw (nativeQuery em JPA, rawQuery em Room), a proteção não funciona — tem de passar os parâmetros através de expressões preparadas, não por concatenação de strings.
Second-Order SQL Injection é um ataque onde dados maliciosos são armazenados na base de dados como seguros, mas depois são utilizados noutra consulta sem escaping. Por exemplo, um atacante regista-se com um nome de utilizador como ' OR '1'='1. Os dados são guardados como uma string — não há ataque no registo. Mas se outra consulta utilizar o nome de utilizador em SQL sem parametrização, a injeção é ativada.
NoSQL Injection pode ser potencialmente mais perigoso devido à menor consciencialização dos programadores. Os programadores conhecem SQL Injection e a maioria usa ORM, mas poucos conhecem NoSQL Injection. No MongoDB, uma consulta mal formada pode devolver todos os documentos de uma coleção. A proteção é a mesma — prepared statements (parametrização BSON) e validação rigorosa de entrada.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também