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 conpassword_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,HttpOnlyeSameSite. -
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: DENYoSAMEORIGINper mitigare clickjacking. -
X-Content-Type-Options: nosniffper prevenire MIME-sniffing. -
Referrer-Policy,Permissions-Policyecc. 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
.envfuori 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
:usernameal 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'] = $tokene 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)
-
Mostra form
-
Form HTML con campi
usernameepassword+ token CSRF (opzionale).
-
-
Ricezione POST
-
Validare la presenza dei campi e il token CSRF.
-
Pulire i valori (trim) ma non modificare password raw.
-
-
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_untilnel futuro → rifiutare (messaggio generico).
-
-
Verifica password
-
Usare
password_verify($password, $password_hash)(PHP) o equivalente. -
Se verifica OK:
-
Resettare
failed_attempts = 0,locked_until = NULLnel 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_attemptssupera soglia (es. 5) impostarelocked_until = NOW() + INTERVAL 15 MINUTE. -
Loggare l’evento (username, IP, time) ma non la password.
-
Mostrare messaggio generico (es. “Credenziali errate”).
-
-
-
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