# Segurança da aplicação web

SQL injection, XSS, CSRF, autorização e palavras-passe, com exemplos de defesa por contexto.

Página: https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/

O email recebido no login é texto controlado pelo cliente. Se a aplicação o concatenar numa consulta, esse texto pode passar a ser sintaxe SQL. A defesa depende de onde a entrada é interpretada. Nos pedidos sobre bilhetes, é preciso verificar também quem pede e se pode operar sobre aquele objeto. A validação no navegador pode ser contornada.

## SQL injection: separar código de valores

Construir SQL por concatenação permite que um valor passe a ser sintaxe:

```
// Vulnerável: a entrada faz parte do texto da instrução.
$sql = "SELECT id FROM utilizadores WHERE email = '" . $email . "'";
```

Usa parâmetros preparados:

```
$stmt = $pdo->prepare('SELECT id FROM utilizadores WHERE email = :email');
$stmt->execute(['email' => $email]);
```

O valor é tratado como dado. Um apóstrofo deixa de fechar uma string do SQL. Parametrizar um valor não parametriza identificadores como nomes de colunas ou palavras-chave. Se a pessoa escolher a ordenação, mapeia a opção para uma lista de colunas autorizadas, sem concatenar o texto recebido.

O ORM ajuda quando usa parâmetros corretamente. Uma expressão raw construída com entrada continua vulnerável. Limita também os privilégios da conta da aplicação: uma leitura não precisa de permissões para alterar o esquema.

## XSS: o contexto decide a codificação

**Cross-site scripting** faz o navegador interpretar dados controlados pelo atacante como código da página. Pode ser refletido na resposta, guardado e mostrado mais tarde, ou criado no cliente ao inserir dados de forma insegura no DOM.

Para texto dentro de HTML, em PHP:

```
echo htmlspecialchars($comentario, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
```

No Blade, `{{ $comentario }}` escapa HTML e `{!! $comentario !!}` imprime sem essa proteção. No DOM, prefere `textContent` para texto, porque `innerHTML` interpreta HTML e só deve receber conteúdo tratado para esse propósito.

A codificação depende do destino. Escapar texto HTML não torna segura uma interpolação dentro de JavaScript, CSS ou um URL. Num URL também tens de verificar o esquema, para não aceitar `javascript:`. Quando a funcionalidade permite HTML formatado, usa uma biblioteca de sanitização mantida com uma lista restrita de elementos e atributos, em vez de remover apenas `<script>`.

Uma política CSP pode reduzir certos ataques, mas não substitui codificação por contexto e sanitização. `HttpOnly` protege a leitura direta do cookie; um script injetado pode ainda agir com a sessão da vítima.

## CSRF: a sessão é enviada automaticamente

Em **cross-site request forgery**, uma página de outra pessoa induz um pedido ao sistema usando credenciais que o navegador envia, como cookies. O atacante não precisa de conhecer o identificador da sessão nem de ler a resposta para causar uma alteração.

Uma compra baseada em sessão deve usar um token imprevisível associado ao contexto autorizado e validado pelo servidor. No Laravel, formulários protegidos pelo middleware adequado incluem `@csrf`. O token só é útil se for verificado; não deve ser colocado num URL que aparece em históricos e registos.

`SameSite` e verificação de origem são defesas complementares, não substitutos do token. O valor `Lax` continua a enviar o cookie em navegações de topo com método seguro: se alguma operação de escrita estiver acessível por `GET`, essa proteção não a impede, por isso nenhuma alteração de estado pode depender só do método do pedido. Além disso, a comparação é feita ao nível do domínio registável, não da origem completa: dois subdomínios diferentes do mesmo domínio contam como mesmo site. Um método POST sozinho não protege, e CORS não impede todos os envios de formulários. XSS pode contornar muitas defesas CSRF porque executa código na origem legítima.

## Autenticado não significa autorizado

O comprador 7 pode estar autenticado e pedir `GET /bilhetes/900`, pertencente ao comprador 8. A aplicação tem de testar a relação entre a identidade e o recurso. Esconder o link ou usar um identificador difícil de adivinhar não cria essa permissão.

```
// Excerto Laravel com Illuminate\Support\Facades\Gate importado.
$bilhete = Bilhete::findOrFail($id);
Gate::authorize('view', $bilhete);
```

Aplica o controlo em todos os caminhos, incluindo API, downloads e operações em lote. Recusa por defeito e concede apenas as permissões necessárias. O papel «comprador» pode dar acesso à funcionalidade, mas a pertença do bilhete decide que objeto pode consultar.

Receber `utilizador_id=8` no formulário não autoriza criar uma compra para 8. Usa a identidade autenticada quando a operação é «comprar para mim». `$fillable` controla preenchimento em massa de atributos, não verifica quem pode operar sobre cada objeto.

## Palavras-passe e fixação de sessão

Guarda palavras-passe com um algoritmo de hash próprio para palavras-passe, como Argon2id ou bcrypt, através de uma API mantida. SHA-256 simples é demasiado rápido para este objetivo. **Hash** não tem uma chave de desencriptação; **cifragem** permite recuperar o original com uma chave. Uma palavra-passe não precisa de ser recuperada para verificar o login.

Em PHP, `password_hash` gera o hash com os parâmetros e salt necessários; `password_verify` verifica-o. O hash guardado já contém a informação que a API precisa. Não compares um novo hash gerado ao texto do anterior, porque os salts podem diferir.

Após autenticar, regenera o identificador da sessão para impedir **session fixation**. Uma mensagem de login genérica evita revelar qual parte das credenciais existe. Limitação de tentativas, recuperação com tokens de uso único e expiração completam a proteção.

**main.php**

```php
<?php
$texto = '<img src=x onerror="alert(1)">';
echo htmlspecialchars($texto, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . "\n";
$hash = password_hash('exemplo_que_nao_e_segredo', PASSWORD_DEFAULT);
echo password_verify('exemplo_que_nao_e_segredo', $hash) ? "certa\n" : "recusada\n";
echo password_verify('outra', $hash) ? "certa\n" : "recusada\n";
```

O bloco isola duas APIs: codificação de texto para HTML e verificação de palavra-passe. Não implementa login, CSRF ou políticas de acesso, por isso não introduzas palavras-passe pessoais nestes exemplos.

## Identificar a defesa numa pergunta

| Situação | Defesa principal |
| --- | --- |
| Valor torna-se parte da sintaxe SQL. | Consulta parametrizada. |
| Comentário torna-se HTML ativo. | Codificação ou sanitização apropriada ao contexto. |
| Cookie da vítima acompanha uma operação induzida. | Proteção CSRF validada no servidor. |
| Pessoa consulta o bilhete de outra. | Autorização sobre o recurso. |
| Identificador de sessão anterior ao login é reutilizado. | Regeneração da sessão. |
| Base de dados de palavras-passe é exposta. | Hash resistente com parâmetros adequados, além de proteção do armazenamento. |

São mecanismos diferentes: uma consulta parametrizada não impede alguém de consultar um objeto para o qual não tem autorização, e um token CSRF não impede XSS.

## Um pedido ao bilhete alheio, camada a camada

O comprador 7 pede `GET /bilhetes/900`, mas o bilhete pertence ao comprador 8. Encontrar a linha com SQL seguro não concede permissão para a devolver.

![O comprador 7 pede o bilhete 900, pertencente ao comprador 8. A consulta parametrizada encontra a linha, mas a policy recusa a leitura antes de gerar a representação.](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/figura-1.svg)

Codificar o título impediria HTML ativo, mas não tornaria lícito mostrar os dados ao comprador 7.

A autorização testa a identidade 7 contra o proprietário 8 e recusa a leitura antes de gerar a representação. Para este pedido de leitura, um token CSRF não substitui a decisão sobre o objeto. Numa escrita, um token válido também não concede permissões; pode ser obtido e enviado por uma pessoa autenticada que tenta operar sobre um bilhete alheio.

## Exercícios

Atacar sem ler a resposta

Um atacante precisa de ler a resposta da compra para realizar CSRF?

Pista

O objetivo pode ser alterar, não obter informação.

Mais uma pista

Um formulário pode enviar uma operação com cookies.

Solução

Não precisa de ler. Usa proteção CSRF validada e métodos apropriados, além das defesas complementares.

*   **Não. Basta induzir uma alteração com as credenciais automáticas da vítima..** A operação pode ter efeito mesmo sem expor o corpo ao atacante.
*   **Sim, tem de ler o cookie..** O navegador pode enviá-lo sem o atacante o conhecer.
*   **Sim, CORS impede a alteração..** CORS não bloqueia todos os pedidos que causam efeitos.

Erros frequentes

Não confundas bloquear leitura com bloquear efeito.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/#csrf-a-sess%C3%A3o-%C3%A9-enviada-automaticamente)

Verificar sem recuperar a palavra-passe

Por que não se compara um `password_hash` novo diretamente com o hash guardado?

Pista

Dois hashes da mesma palavra-passe podem ser diferentes.

Mais uma pista

O formato guarda parâmetros e salt para a verificação.

Solução

A API gera salts próprios; dois textos de hash podem diferir. `password_verify` usa o formato guardado para testar a palavra-passe.

Confere a tua resposta:

*   Reconheço salts diferentes.
*   Uso password\_verify.
*   Não preciso de recuperar o original.

Erros frequentes

Não uses SHA-256 rápido nem cifragem recuperável como substituto automático de um hash de palavras-passe.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/#palavras-passe-e-fixa%C3%A7%C3%A3o-de-sess%C3%A3o)

Uma defesa depende do destino

Escapar texto HTML torna segura qualquer string interpolada num bloco JavaScript?

Pista

Há dois interpretadores com regras diferentes.

Mais uma pista

É preferível evitar a interpolação em código ativo.

Solução

Não. Escolhe uma API e serialização próprias para o destino, ou mantém os dados fora de código ativo. CSP acrescenta defesa, não corrige a interpolação.

*   **Não. O contexto JavaScript exige tratamento próprio..** Codificação de texto HTML não é uma defesa universal.
*   **Sim, todas as linguagens interpretam os caracteres da mesma forma..** Os contextos e parsers diferem.
*   **Sim, se o site tiver HTTPS..** HTTPS protege o transporte, não essa interpretação.

Erros frequentes

Não chames «sanitização» a qualquer função sem indicar contexto.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/#xss-o-contexto-decide-a-codifica%C3%A7%C3%A3o)

Distinguir injeção e acesso alheio

O login constrói a consulta abaixo por concatenação. Separadamente, `GET /bilhetes/900` carrega e mostra o bilhete 900 a qualquer comprador autenticado, embora pertença ao comprador 8. O atacante é o comprador 7. Explica entrada, efeito e correção de cada falha. HTTPS e um token CSRF válido resolvem alguma delas?

```
$sql = "SELECT id FROM utilizadores WHERE email = '" . $email . "'";
```

Pista

Identifica qual interpretador ou regra é contornado em cada caminho.

Solução

Com email `' OR '1'='1`, a consulta passa a terminar em `WHERE email = '' OR '1'='1'`, uma condição verdadeira para todas as linhas. Usa uma consulta preparada com um parâmetro `:email`, para o valor nunca se tornar sintaxe.

O pedido de 7 ao bilhete de 8 pode ter SQL perfeitamente parametrizado e continuar a expor dados. Verifica no servidor uma policy sobre a operação `view` e o bilhete carregado, recusando o acesso de 7.

HTTPS protege o transporte. O token CSRF trata pedidos induzidos com credenciais automáticas. Nenhum substitui parâmetros SQL ou autorização sobre o objeto.

Confere a tua resposta:

*   Mostro como a entrada altera a sintaxe SQL.
*   Uso parâmetros para valores.
*   Recuso bilhete alheio por autorização.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/#um-pedido-ao-bilhete-alheio-camada-a-camada)
