mercoledì 5 novembre 2025

Corso di PHP: 11 – Sicurezza e Best Practices

11 Sicurezza e Best Practices

Obiettivi didattici

  • Conoscere gli attacchi web più comuni (SQL Injection, XSS, CSRF, ecc.).

  • Imparare le contromisure pratiche: prepared statements, escaping, hashing delle password, protezione delle sessioni.

  • Applicare best practice di organizzazione del codice, gestione degli errori e politiche di privilegio.

  • Esercizio pratico: progettare un form di login sicuro con suggerimenti implementativi.

1) Panorama delle vulnerabilità web più comuni

SQL Injection

Descrizione: l’attaccante inserisce frammenti SQL in input non filtrati per alterare una query (es. bypassare login, estrarre tabelle).
Esempio concettuale: se si costruisce la query concatenando l’input utente:
"SELECT * FROM users WHERE username = '" + user + "' AND password = '" + pass + "'"
un input malizioso può chiudere la stringa e iniettare comandi.

Impatto: perdita o esposizione di dati, compromissione del DB, escalation di privilegi.

Cross-Site Scripting (XSS)

Descrizione: l’attaccante inietta script JavaScript eseguiti nel browser delle vittime (riflesso, persistente, DOM-based).
Impatto: furto di cookie/sessioni, defacement, redirect a siti malevoli.

Cross-Site Request Forgery (CSRF)

Descrizione: l’attaccante induce l’utente autenticato a inviare richieste involontarie al sito (es. trasferimento fondi, eliminazione).
Meccanismo: il browser invia cookie di autenticazione automaticamente alle richieste.

Altre minacce rilevanti

  • Brute-force / credential stuffing (tentativi massivi di password).

  • Session fixation / session hijacking (furto o fissazione dell’id di sessione).

  • File upload insecure (upload di script che poi vengono eseguiti).

  • Insecure direct object references (IDOR) — accesso a risorse usando identificatori prevedibili.

  • Clickjacking — UI overlay che induce l’utente a compiere azioni.

2) Contromisure fondamentali e regole pratiche

A. Prepared statements / parameterized queries (anti-SQL-Injection)

  • Non concatenare mai input utente nella query SQL.

  • Usare API che supportano parametri (PDO, mysqli prepared statements).

  • I parametri vengono inviati separati dal codice SQL e il DB li tratta come dati, non codice.

Concetto: la query è fissa con segnaposto, i valori sono bindati separatamente.

B. Validazione e sanificazione dell’input

  • Validazione: controllare che l’input rispetti il formato atteso (es. email valida, numero intero in range). Questo è un requisito funzionale.

  • Sanificazione: pulire o normalizzare l’input prima di usarlo (es. strip_tags per testo libero o escaping). Non sostituire le prepared statements per query DB: usare entrambi quando opportuno.

  • Principio: “trust no input”.

C. Escaping per output (anti-XSS)

  • Quando si mostra contenuto utente in HTML, usare escaping appropriato (in PHP: htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')).

  • Per attributi HTML, JS inline, URL o contesto CSS, usare regole di escaping specifiche per il contesto.

  • Per JSON o dati API, serializzare correttamente (json_encode).

D. Protezione CSRF

  • Generare un token CSRF (random, difficile da indovinare) memorizzarlo in sessione e inserire il token nei form come campo nascosto. Verificare il token al POST.

  • Alternativa/aggiunta: header custom + CORS ben configurato o SameSite cookie.

E. Gestione sicura delle password

  • Mai salvare password in chiaro.

  • Usare funzioni di hashing adatte per password con salt incorporato e cost factor: in PHP password_hash($password, PASSWORD_DEFAULT) e verificare con password_verify($password, $hash).

  • Evitare funzioni hash generiche (MD5, SHA1) per password.

  • Impostare politiche di password ragionevoli, ma attenzione: complessità vs usabilità. Preferire l’uso di password manager e autenticazione a due fattori (2FA) quando possibile.

F. Protezione delle sessioni

  • Avviare sessione in modo sicuro (session_start() prima di qualsiasi output).

  • Usare cookie di sessione con flag Secure, HttpOnly e SameSite.

  • Rigenerare l’id di sessione dopo login: session_regenerate_id(true).

  • Limitare durata di sessione e inattività.

  • Validare user agent / IP con cautela (può creare falsi positivi).

G. Gestione upload file

  • Controllare tipo MIME e estensione, ma basarsi sulla verifica del contenuto (es. finfo_file) più che sull’estensione.

  • Salvare file in cartelle non eseguibili (fuori dalla webroot) o con nomi generati casualmente.

  • Imporre limiti di dimensione.

  • Rimuovere permessi di esecuzione sui file caricati.

H. Principio del least privilege

  • L’applicazione deve usare un account DB con permessi minimi necessari (SELECT/INSERT/UPDATE, non DROP/CREATE se non necessario).

  • Separare ruoli e permessi sul server (es. utenti DB per operazioni specifiche).

I. Logging e gestione degli errori

  • Non mostrare messaggi di errore dettagliati all’utente (leak di informazioni).

  • Loggare gli errori (stacktrace, query fallite) in file sicuri accessibili solo agli amministratori.

  • Monitorare i log per attività sospette (tentativi di login massivi, pattern di SQL injection, upload anomali).

J. Crittografia di trasporto

  • Usare HTTPS (TLS) ovunque; redirigere sempre HTTP → HTTPS.

  • Impostare HSTS per forzare connessioni TLS.

  • Assicurarsi che certificati e suite TLS siano aggiornati.

K. Header di protezione HTTP

  • Content-Security-Policy (CSP): limita origine di script, immagini, stili.

  • X-Frame-Options: DENY o SAMEORIGIN per mitigare clickjacking.

  • X-Content-Type-Options: nosniff per prevenire MIME-sniffing.

  • Referrer-Policy, Permissions-Policy ecc. secondo necessità.

3) Organizzazione del codice e best practices architetturali

Strutturazione del progetto

  • Separazione netta tra presentation (view), business logic (controller/service) e data access (DAO/Repository).

  • Non mettere query SQL sparse in template. Centralizzare l’accesso al DB in componenti dedicati.

Uso di layer di astrazione

  • Usare PDO o ORM (Doctrine, Eloquent) per isolamento e sicurezza.

  • Implementare un livello per la validazione dei dati (form request validators).

Gestione delle configurazioni

  • Non commettere file di configurazione contenenti credenziali al VCS. Usare variabili d’ambiente o file .env fuori dal repo.

  • Limitare la visibilità dei file di configurazione e i permessi sul server.

Test e review

  • Test automatici: unit tests, integration tests.

  • Code review e static analysis (es. PHPMD, PHPStan) per individuare anti-pattern di sicurezza.

  • Penetration testing regolare e dependency scanning (vulnerabilità note nelle librerie).

4) Gestione degli errori: cosa loggare e cosa mostrare

Cosa mostrare all’utente

  • Messaggi generici e amichevoli (es. “Credenziali errate”, “Errore interno, riprovare più tardi”).

  • Mai mostrare stack trace, query SQL o percorsi file in produzione.

Cosa loggare internamente

  • Stack trace, timestamp, IP remoto, user agent, request parameters (attenzione a non loggare password in chiaro).

  • Eventi di sicurezza: tentativi di login falliti, rilevamento di payload sospetti, upload di file pericolosi.

Rotazione e gestione log

  • Implementare rotazione log (logrotate) e conservazione in storage sicuro.

  • Proteggere i log da accessi non autorizzati.

5) Esempi pratici sintetici (concettuali, leggibili)

A) Query sicura con PDO (pattern)

Concetto (pseudocodice, non concatenazione diretta):

  • Query: SELECT id, password_hash FROM users WHERE username = :username

  • Bind: associare :username al valore sicuro (es. $stmt->bindParam(':username', $inputUsername)).

  • Eseguire e ottenere risultati.

B) Escape output HTML

Concetto: per ogni stringa proveniente dall’utente che viene mostrata in HTML, applicare escaping:

  • safe = htmlspecialchars(user_input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')

C) Token CSRF semplice (logica)

  • All’apertura del form: generare $token = random_bytes(32)base64_encode → salvare in sessione $_SESSION['csrf'] = $token e inserire <input type="hidden" name="csrf" value="$token">.

  • Alla ricezione della POST: verificare isset($_POST['csrf']) && hash_equals($_SESSION['csrf'], $_POST['csrf']).

6) Esercizio svolto: form di login sicuro (progettazione e passi)

Obiettivo

Progettare un login sicuro che limiti attacchi di brute force, protegga password, prevenga session fixation e fornisca logging degli eventi.

Requisiti funzionali e di sicurezza

  • Password memorizzate come hash robusti (password_hash).

  • Uso di prepared statements per la query di autenticazione.

  • Rigenerazione dell’id di sessione dopo login.

  • Protezione CSRF sul form di login (opzionale ma consigliata).

  • Limitazione tentativi (rate limiting o lockout temporaneo).

  • Logging sicuro dei tentativi (senza password).

  • Messaggi generici all’utente (non dire “username inesistente” vs “password errata” per non confermare utenti esistenti).

Tabella utenti (esempio SQL)

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(100) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  failed_attempts INT DEFAULT 0,
  locked_until DATETIME NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Flusso di autenticazione (passi)

  1. Mostra form

    • Form HTML con campi username e password + token CSRF (opzionale).

  2. Ricezione POST

    • Validare la presenza dei campi e il token CSRF.

    • Pulire i valori (trim) ma non modificare password raw.

  3. Controlli preliminari di sicurezza

    • Recuperare l’utente dal DB con query parametrizzata: SELECT id, password_hash, failed_attempts, locked_until FROM users WHERE username = :username.

    • Se l’account ha locked_until nel futuro → rifiutare (messaggio generico).

  4. Verifica password

    • Usare password_verify($password, $password_hash) (PHP) o equivalente.

    • Se verifica OK:

      • Resettare failed_attempts = 0, locked_until = NULL nel DB.

      • session_start(), session_regenerate_id(true).

      • Salvare dati essenziali in sessione (user_id, username, roles).

      • Redirect alla dashboard.

    • Se verifica NO:

      • Incrementare failed_attempts.

      • Se failed_attempts supera soglia (es. 5) impostare locked_until = NOW() + INTERVAL 15 MINUTE.

      • Loggare l’evento (username, IP, time) ma non la password.

      • Mostrare messaggio generico (es. “Credenziali errate”).

  5. Ulteriori protezioni opzionali

    • CAPTCHA dopo N tentativi falliti.

    • Autenticazione a due fattori (2FA) come step successivo.

    • Notifica via email di tentativi sospetti.

Esempio di pseudocodice (logico, non verbatim)

POST /login
validate_csrf($_POST['csrf'])
username = trim($_POST['username'])
password = $_POST['password']

row = db.query("SELECT id,password_hash,failed_attempts,locked_until FROM users WHERE username = :u", {':u': username})
if not row: sleep(short) // mitigazione enumeration, log attempt, return "Credenziali errate"

if row.locked_until > now():
    return "Account temporaneamente bloccato"

if password_verify(password, row.password_hash):
    db.execute("UPDATE users SET failed_attempts=0, locked_until=NULL WHERE id=:id", {':id': row.id})
    session_start()
    session_regenerate_id(true)
    $_SESSION['user_id'] = row.id
    redirect /dashboard
else:
    increment failed_attempts; if threshold exceeded set locked_until = now + 15min
    db.execute("UPDATE users SET failed_attempts=?, locked_until=? WHERE id=?", ...)
    log("Failed login", username, ip, time)
    return "Credenziali errate"

Note operative

  • Tempi di attesa: aggiungere piccole pause (sleep) per rendere i brute force più costosi.

  • Politica lockout: configurabile (soglia tentativi, durata lockout).

  • Rate limiting a livello infrastruttura: firewall, WAF, CDN (es. Cloudflare) possono limitare richieste abusive.

  • Monitoraggio: inviare alert per molteplici lockout o pattern sospetti.

7) Checklist rapida di sicurezza per un’app PHP

  • Tutte le query usano prepared statements.

  • Password con password_hash/password_verify.

  • Output HTML correttamente escaped (htmlspecialchars) per contenuti utente.

  • Form sensibili protetti con CSRF token.

  • Sessioni protette (Secure, HttpOnly, SameSite) e rigenerate dopo login.

  • Upload file validati e memorizzati fuori dalla webroot.

  • Configurazioni e segreti fuori dal VCS (variabili ambiente).

  • Errori non esposti all’utente, ma loggati internamente.

  • Limitazione tentativi / rate limiting.

  • HTTPS forzato e HSTS abilitato.

  • Headers di sicurezza (CSP, X-Frame-Options, X-Content-Type-Options).

  • Principle of least privilege per account DB e filesystem.

  • Dipendenze verificate per vulnerabilità note.

8) Risorse e strumenti utili (pratici)

  • Scanners: OWASP ZAP, sqlmap (per test), Nikto.

  • Linters e static analysis: PHPStan, Psalm.

  • Dependency scanners: Snyk, GitHub Dependabot.

  • Documentazione: OWASP Top 10, OWASP Cheat Sheets (Authentication, Session Management, SQL Injection Prevention).

  • Librerie: usare framework aggiornati (Laravel, Symfony) che incorporano molte protezioni out-of-the-box.


Nessun commento:

Posta un commento

Corso Fondamenti di Informatica e Reti: 6 Reti di computer e Internet

Reti di computer e Internet Introduzione Prova a pensare alla vita quotidiana senza reti informatiche: niente messaggi WhatsApp, niente m...