Segurança web

Injeção SQL, XSS, CSRF, autorização, travessia de caminhos, HTTPS, palavras-passe e sessões.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

O título de um livro pode passar pelo pedido HTTP, pela base de dados e pelo HTML apresentado. Em cada passagem, o programa decide se o interpreta como dado ou como instrução. Preparar SQL impede que o título mude a consulta; escapar a saída impede que se torne marcação ativa. Validar formato e limites responde a outra pergunta, se o título é permitido pela aplicação.

Injeção SQL

Este excerto concatena dados numa instrução SQL:

$sql = "SELECT id FROM utilizador WHERE nome = '$nome'";

Se o atacante escolhe $nome = "' OR 1=1 -- ", a consulta resultante fica:

SELECT id FROM utilizador WHERE nome = '' OR 1=1 -- '

OR 1=1 é sempre verdadeiro e -- comenta o resto. A intenção de procurar um nome tornou-se uma condição que aceita linhas diferentes. O ataque não precisa de executar vários comandos para causar dano.

$stmt = $db->prepare('SELECT id FROM utilizador WHERE nome = ?');
$stmt->execute([$nome]);

O marcador recebe o valor como dado. A string de ataque passa a ser um nome literal e não altera a sintaxe. Mantém os valores fora do texto SQL. Para identificadores, como a coluna de ordenação, escolhe entre nomes fixos autorizados; marcadores não representam identificadores SQL.

No vídeo, identifica a concatenação que permite à entrada mudar a sintaxe SQL. Compara-a com a consulta preparada acima.

Abrir no YouTube

XSS e contexto de saída

XSS ocorre quando dados controlados pelo atacante passam a código ou marcação ativa na origem da aplicação. Pode ser persistente, como um comentário guardado; refletido, como uma pesquisa devolvida na resposta; ou baseado no DOM, quando JavaScript coloca entrada num destino perigoso.

// Excerto vulnerável num corpo HTML
 echo '<p>' . $comentario . '</p>';
// Correção para apresentar texto nesse contexto
 echo '<p>' . htmlspecialchars($comentario, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</p>';

Com a correção, <script>...</script> aparece como texto. O escape para texto HTML também serve para valores de atributos entre aspas quando aplicado corretamente, mas não valida a semântica de um URL nem torna qualquer contexto seguro. Um href ainda precisa de limitar esquemas aceites; JavaScript embebido e CSS têm regras próprias. Evita construir código com dados do utilizador.

No DOM, prefere textContent para texto e criação de elementos com atributos validados. innerHTML interpreta marcação; não o uses para um comentário cru. Sanitizar HTML permitido exige uma biblioteca adequada, com regras explícitas, não uma regex para apagar <script>.

HttpOnly pode impedir que um script leia o cookie, mas XSS ainda pode fazer pedidos autenticados e ler dados na página. Uma política CSP pode limitar código executável e recursos como defesa adicional; não corrige um destino inseguro de saída.

CSRF

Um pedido de alteração pode chegar de outra página e trazer credenciais enviadas automaticamente pelo navegador. Se o servidor verifica apenas o cookie de sessão, pode executar a ação sem saber se o utilizador quis iniciá-la. POST por si não impede isso.

Um token CSRF é um valor imprevisível associado à sessão. O servidor envia-o no formulário e exige-o no pedido de alteração. A página de outra origem não deve conseguir lê-lo pela política de mesma origem. O token não substitui a autenticação nem a autorização.

Segue o ataque antes do código. A vítima tem sessão válida e visita uma página externa que contém um formulário a apontar para a ação de reserva. Se a política do cookie permitir o envio nesse contexto, o pedido leva a sessão da vítima. Sem verificar a origem ou um token associado ao formulário, o servidor não consegue confirmar que a ação veio do percurso legítimo.

Estes excertos pertencem, respetivamente, à página que mostra o formulário e à ação que o processa:

<?php
session_start();
if (!isset($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}
?>
<form action="/actions/reservar.php" method="post">
  <input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf'], ENT_QUOTES, 'UTF-8') ?>">
  <button>Confirmar reserva</button>
</form>
<?php
session_start();
$esperado = $_SESSION['csrf'] ?? null;
$recebido = $_POST['csrf'] ?? null;
if ($_SERVER['REQUEST_METHOD'] !== 'POST'
    || !is_string($esperado) || $esperado === ''
    || !is_string($recebido)
    || !hash_equals($esperado, $recebido)) {
    http_response_code(403);
    exit('Pedido recusado.');
}
// Seguem-se autenticação, autorização e validação dos dados da reserva.

O teste exige que exista um token esperado não vazio. Comparar dois valores ausentes convertidos em '' aceitaria indevidamente um pedido sem token. hash_equals compara os valores sem a comparação habitual que pode variar com prefixos.

SameSite e verificação de Origin podem reforçar a proteção conforme o desenho. CORS trata leitura entre origens; não prova intenção do utilizador. Um ataque XSS na aplicação pode obter tokens e contornar esta defesa. Não coloques ações de alteração em GET.

Autenticação e autorização

Um utilizador autenticado pode não ter permissão para editar a reserva de outra pessoa. O identificador recebido num campo hidden, URL ou JSON continua a ser entrada. Verifica a associação na operação real:

$stmt = $db->prepare('UPDATE reserva SET quantidade = ? WHERE id = ? AND utilizador_id = ?');
$stmt->execute([$quantidade, $reservaId, $_SESSION['utilizador_id']]);

Compara dois utilizadores. O utilizador A edita a reserva 7, que lhe pertence. O utilizador B tenta editar a reserva 7 alterando o identificador no pedido. O utilizador vem da sessão verificada, não de $_POST['utilizador_id']. A consulta restringe o registo ao dono. Confirma o efeito esperado e devolve um erro quando não existe uma reserva autorizada. Esconder um botão ou impedir uma ação em JavaScript melhora a interface, mas o atacante pode enviar o pedido diretamente.

Palavras-passe e sessão

Guarda um resumo próprio para palavras-passe com password_hash; verifica com password_verify. PASSWORD_DEFAULT escolhe o algoritmo da versão e pode mudar, por isso a coluna deve suportar resumos com tamanho suficiente. O resumo guarda os parâmetros necessários à verificação, incluindo salt.

Salt é público e fica guardado com o resumo; não precisa de ser lembrado pelo utilizador nem enviado como segredo separado. password_hash cria-o automaticamente. Depois de um login válido, password_needs_rehash permite decidir se o resumo deve ser atualizado para os parâmetros atuais.

No registo de Ana e Rui, que escolheram a mesma palavra-passe, cada chamada a password_hash cria um salt aleatório. Os resumos normalmente diferem e conservam os parâmetros de verificação. No login de Ana, password_verify usa os parâmetros do resumo de Ana.

O salt dificulta tabelas pré-calculadas e impede reconhecer palavras-passe iguais apenas comparando os resumos. Não torna uma palavra-passe fraca resistente a tentativas: continua a precisar de limites de tentativas e de mensagens que não revelem contas desnecessariamente. SHA-1 e SHA-256 simples são rápidos e inadequados para guardar palavras-passe, mesmo com salt manual.

Regenera o identificador de sessão ao autenticar ou mudar privilégios. Em produção, usa HTTPS e cookies Secure, HttpOnly e SameSite adequado. Nunca confies num identificador de sessão recebido para autorizar sem recuperar o estado e verificar a ação.

Caminhos e ficheiros

Se readfile($_GET['ficheiro']) recebe ../../config.php, o programa pode revelar um ficheiro fora do conjunto pretendido. Remover literalmente ../ não cobre todas as formas de manipular caminhos.

Quando só há poucos documentos, mapeia identificadores para caminhos fixos:

$ficheiros = ['guia' => __DIR__ . '/../privado/guia.pdf'];
$id = $_GET['id'] ?? null;
if (!is_string($id) || !isset($ficheiros[$id])) {
    http_response_code(404);
    exit;
}
readfile($ficheiros[$id]);

Esse é um excerto de um endpoint que ainda deve verificar a permissão de leitura. Para uploads, valida tamanho e formato real, escolhe nomes no servidor e guarda fora de pastas onde possam executar. A extensão e o tipo declarados pelo cliente não são prova suficiente.

HTTPS e revisão de uma ação

HTTPS usa TLS para proteger confidencialidade e integridade do tráfego e autenticar o servidor conforme os certificados. O navegador verifica que o certificado corresponde ao nome pedido e encadeia até uma autoridade em que confia, além da validade temporal. Durante a ligação, as partes acordam chaves; a cifra simétrica protege depois os dados. A chave privada do servidor não é enviada ao cliente. Não corrige SQL injection, XSS nem autorização. Um sítio malicioso também pode ter HTTPS.

Ao rever uma ação de reserva, segue o percurso: método correto, sessão válida, token CSRF, dono autorizado, parâmetros com tipo e limites, SQL preparado, alteração consistente e saída no contexto certo. Para cada teste, explica um pedido que falharia sem ele.

Exercícios

Escolher a defesa certa

Uma ação concatena nome numa consulta SQL e mais tarde coloca esse nome em innerHTML. Um colega aplica htmlspecialchars antes de guardar e afirma que resolveu tudo. Identifica os problemas e indica as duas defesas necessárias.

Pista

São duas leituras distintas: SQL e marcação HTML.

Mais uma pista

Escapar HTML não separa dados de SQL.

Solução

A consulta continua vulnerável a injeção SQL e precisa de valores em marcadores preparados. A saída deve tratar o nome como texto, usando textContent no cliente ou escape correto no contexto HTML do servidor. Guardar texto já escapado também pode causar dupla codificação e não serve todos os contextos. Validação de formato pode complementar, mas não substitui nenhuma destas defesas.

Confere a tua resposta:

  • Separei SQL de saída HTML.
  • Escolhi prepare e destino de texto/escape de saída.

Erros frequentes

Não escolher uma função universal de escape nem tratar dados da base como automaticamente seguros.

Voltar à explicação
Encontrar um CSRF sem token

O servidor compara hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? ''). Que acontece quando ambos faltam? Corrige a condição e explica por que POST e CORS não chegam.

Pista

Dois valores vazios são iguais.

Mais uma pista

Exige um token esperado existente, não vazio e string, e um recebido string.

Solução

A condição ingénua compara vazio com vazio e aceita. Quando ambos faltam, hash_equals("", "") é verdadeiro. A ação deve verificar método POST, existência e tipo do token da sessão, valor esperado não vazio, tipo do token recebido e só depois hash_equals. Um formulário de outra origem pode enviar POST com credenciais automaticamente aplicáveis. CORS restringe leitura da resposta, não prova intenção nem impede todos os envios.

Confere a tua resposta:

  • Mostrei a aceitação de vazio/vazio.
  • Exigi token válido antes da comparação.

Erros frequentes

Não gerar um token só na ação e assumir que isso comprova o formulário; o valor deve estar ligado ao fluxo legítimo.

Voltar à explicação
Autorizar o registo correto

Uma ação permite editar reserva_id vindo do cliente se existir utilizador_id na sessão. O utilizador altera reserva_id para o de outra pessoa. Qual a verificação em falta? Mostra uma condição SQL apropriada.

Pista

Ter sessão não concede acesso a todos os registos.

Solução

Falta autorizar a propriedade do registo. Uma condição apropriada é WHERE id = :reserva AND utilizador_id = :utilizador, com reserva recebida e validada e utilizador obtido da sessão. Ambos são valores preparados. Confirma que houve um registo autorizado a alterar, e verifica também os novos dados e CSRF. Esconder o botão não protege o endpoint.

Confere a tua resposta:

  • Usei o dono vindo da sessão.
  • Restringi o próprio UPDATE ao registo autorizado.

Erros frequentes

Não aceitar utilizador_id de um hidden como autoridade para a permissão.

Voltar à explicação
Explicar salt e HTTPS

Dois utilizadores escolhem a mesma palavra-passe. O sistema usa password_hash para cada um. Os resumos têm de ser iguais? Um salt salva a palavra-passe 123456? HTTPS resolveria uma consulta SQL concatenada?

Pista

O resumo inclui salt e parâmetros; o algoritmo é próprio para palavras-passe.

Mais uma pista

Distingue ataque offline à palavra-passe e proteção do transporte.

Solução

Os resumos não têm de ser iguais e normalmente diferem por salts distintos. Isso dificulta pré-cálculo e comparação direta, mas 123456 continua fácil de tentar. password_verify verifica a palavra-passe contra cada resumo. HTTPS protege o transporte, não a interpretação SQL no servidor, que continua a exigir preparação. Regenera a sessão depois de autenticar.

Confere a tua resposta:

  • Expliquei resumos diferentes e limite do salt.
  • Distingui HTTPS de defesa SQL.

Erros frequentes

Não usar SHA-1 simples com salt nem inferir segurança da aplicação a partir do cadeado HTTPS.

Voltar à explicação
Proteger o ficheiro e o seu acesso

Uma ação autenticada recebe id=guia e mapeia-o para um PDF fixo fora da pasta pública. O programador põe uma cópia em public/guia.pdf para facilitar downloads. Quem não tem sessão consegue descarregar essa cópia por URL direto? Explica os dois controlos necessários.

Pista

O servidor pode servir um ficheiro público sem executar a ação PHP.

Solução

Sim, se o servidor servir a pasta public sem controlo adicional, o URL direto contorna a ação. O mapa fixo impede escolher caminhos arbitrários, mas a ação continua a precisar de verificar sessão e permissão para aquele documento. Guarda o PDF fora da raiz pública e disponibiliza-o só pelo endpoint autorizado. Não confies num nome difícil de adivinhar como controlo de acesso.

Confere a tua resposta:

  • Separei a seleção de caminhos da autorização.
  • Retirei a cópia pública e mantive o controlo no endpoint.

Erros frequentes

Não confundir caminho autorizado com utilizador autorizado nem considerar um ficheiro privado só por não ter uma ligação visível.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.