Il Secure Coding (programmazione sicura) è la disciplina che studia come scrivere software privo delle vulnerabilità che gli attaccanti sfruttano per compromettere sistemi, rubare dati o eseguire codice arbitrario. Non si tratta di aggiungere sicurezza dopo aver scritto il codice, ma di incorporarla nelle scelte di progettazione, nelle API utilizzate e nelle convenzioni di sviluppo fin dall’inizio — secondo il principio Security by Design.
Il secure coding è il fondamento operativo di un ciclo di sviluppo sicuro (Secure SDLC) e copre tutte le fasi: scelta del linguaggio, gestione della memoria, validazione dell’input, gestione degli errori e delle credenziali.
Principi cardine
1. Minimo privilegio
Il codice deve operare con i privilegi minimi necessari alla sua funzione. Un server web non deve girare come root; un plugin non deve avere accesso al filesystem completo; una query al database deve usare un utente con i soli permessi di lettura se non deve scrivere.
2. Difesa in profondità
Nessun singolo controllo di sicurezza è infallibile. Stratificare più meccanismi di difesa (validazione dell’input, encoding dell’output, prepared statement, firewall applicativo) garantisce che il fallimento di uno strato non comprometta l’intero sistema.
3. Fallimento sicuro (Fail Secure)
In caso di errore, il sistema deve passare a uno stato sicuro, non a uno stato aperto. Se il controllo di autenticazione fallisce per un errore interno, l’accesso deve essere negato, non concesso.
4. Validazione di tutto l’input
Qualsiasi dato proveniente dall’esterno del confine di fiducia (utente, database, API esterna, filesystem, variabili d’ambiente) deve essere considerato non fidato fino a prova contraria. La validazione deve essere:
- Completa: verificare tipo, lunghezza, formato e range.
- Lato server: la validazione lato client è bypassabile.
- Whelist-based: preferire la lista di ciò che è consentito a quella di ciò che è vietato.
5. Separazione dei privilegi e dei ruoli
Componenti con funzioni diverse devono avere privilegi separati. Il modulo che autentica l’utente non deve avere accesso diretto al database degli ordini; il frontend non deve conoscere le chiavi di crittografia del backend.
6. Economia del meccanismo (Keep it Simple)
La complessità è il nemico della sicurezza. Sistemi semplici hanno meno superficie d’attacco, sono più facili da verificare e da testare. Evitare la re-implementazione di primitive crittografiche, protocolli o meccanismi di autenticazione già consolidati.
Pratiche concrete per categoria
Gestione della memoria (C/C++)
| Pratica | Motivazione |
|---|---|
Usare strncpy, snprintf al posto di strcpy, sprintf | Prevenire buffer overflow |
Verificare sempre i ritorni di malloc | Evitare dereferenziazione di NULL |
Non usare mai la stessa area di memoria dopo free | Prevenire use-after-free |
| Preferire linguaggi memory-safe (Rust, Go) quando possibile | Eliminare intere classi di vulnerabilità |
Input e output
| Pratica | Motivazione |
|---|---|
| Prepared statement / parametrizzazione delle query | Prevenire SQL Injection |
| Encoding contestuale dell’output (HTML, JavaScript, URL) | Prevenire XSS |
Usare sempre printf("%s", input) non printf(input) | Prevenire format string attack |
| Validare path e canonicalizzare prima dell’accesso al filesystem | Prevenire path traversal |
Gestione delle credenziali
| Pratica | Motivazione |
|---|---|
| Non inserire credenziali nel codice sorgente | Prevent secret leakage via repository |
| Usare variabili d’ambiente o vault (HashiCorp, AWS Secrets Manager) | Separare codice da configurazione sensibile |
| Hashare le password con bcrypt/Argon2, non MD5/SHA-1 | Resistenza a password cracking |
| Invalidare token/sessioni dopo logout e scadenza | Prevenire session hijacking |
Crittografia
| Pratica | Motivazione |
|---|---|
| Non inventare schemi crittografici custom | Le primitive esistenti sono state analizzate; le custom quasi sempre no |
| Usare TLS ≥ 1.2 (preferibilmente 1.3) per tutti i canali | Prevenire intercettazioni |
Generare nonce e IV da CSPRNG, mai da rand() | rand() è predicibile; invalida la cifratura |
| Preferire cifratura autenticata (AES-GCM, ChaCha20-Poly1305) | Rilevare alterazioni del ciphertext |
Standard e linee guida di riferimento
- CERT Secure Coding Standards (Carnegie Mellon): regole specifiche per C, C++, Java, Perl.
- OWASP Secure Coding Practices: checklist generica per applicazioni web.
- CWE (Common Weakness Enumeration): catalogo standardizzato delle debolezze del software, usato per classificare vulnerabilità.
- SEI CERT C Coding Standard: il riferimento più completo per C embedded e di sistema.
- MISRA C/C++: standard per codice safety-critical (automotive, medicale, aerospaziale).
Strumenti a supporto
| Categoria | Strumenti |
|---|---|
| Static Analysis (SAST) | Semgrep, CodeQL, Coverity, Clang Static Analyzer, Bandit (Python) |
| Dynamic Analysis (DAST) | AddressSanitizer, Valgrind, OWASP ZAP, Burp Suite |
| Dependency scanning | Dependabot, Snyk, OWASP Dependency-Check |
| Secret scanning | TruffleHog, GitLeaks, detect-secrets |
| Fuzzing | AFL++, libFuzzer, OSS-Fuzz |
Il secure coding non è una lista di regole da memorizzare, ma un cambio di prospettiva: ogni riga di codice che accetta input dall’esterno è un potenziale confine di fiducia, e ogni confine di fiducia deve essere presidiato esplicitamente.