Context engineering
11 Sicurezza e Best Practices
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.
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.
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.
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.
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.
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.
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”.
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).
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.
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.
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).
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.
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).
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).
Usare HTTPS (TLS) ovunque; redirigere sempre HTTP → HTTPS.
Impostare HSTS per forzare connessioni TLS.
Assicurarsi che certificati e suite TLS siano aggiornati.
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à.
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.
Usare PDO o ORM (Doctrine, Eloquent) per isolamento e sicurezza.
Implementare un livello per la validazione dei dati (form request validators).
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 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).
Messaggi generici e amichevoli (es. “Credenziali errate”, “Errore interno, riprovare più tardi”).
Mai mostrare stack trace, query SQL o percorsi file in produzione.
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.
Implementare rotazione log (logrotate) e conservazione in storage sicuro.
Proteggere i log da accessi non autorizzati.
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.
Concetto: per ogni stringa proveniente dall’utente che viene mostrata in HTML, applicare escaping:
safe = htmlspecialchars(user_input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
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']).
Progettare un login sicuro che limiti attacchi di brute force, protegga password, prevenga session fixation e fornisca logging degli eventi.
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).
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
);
Mostra form
Form HTML con campi username e password + 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_until nel futuro → rifiutare (messaggio generico).
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”).
Ulteriori protezioni opzionali
CAPTCHA dopo N tentativi falliti.
Autenticazione a due fattori (2FA) come step successivo.
Notifica via email di tentativi sospetti.
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"
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.
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.
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.
Commenti
Posta un commento