MDstable
NoteSnippetChecklistPlaybook

Secure Coding — Bonnes Pratiques de Développement

Pratiques de développement sécurisé : validation des entrées, cryptographie, gestion des secrets, authentification, prévention OWASP Top 10

checklistintermediate 2026-05-30 5 min read
secure-codingowaspinjectioncryptographievalidationdevsecops

Validation des entrées

Principe : Whitelist > Blacklist

python
# ❌ Mauvais — blacklist contournable
def validate_username(name):
if "<script>" in name or "SELECT" in name:
raise ValueError("Invalid")
return name
# ✅ Bon — whitelist stricte
import re
def validate_username(name):
if not re.match(r'^[a-zA-Z0-9_-]{3,32}$', name):
raise ValueError("Username: 3-32 chars, alphanumeric only")
return name

Validation côté serveur obligatoire

javascript
// ❌ Validation côté client uniquement — contournable via proxy
document.getElementById('form').onsubmit = () => {
if (!email.includes('@')) return false;
};
// ✅ Validation côté serveur (Express.js)
const { body, validationResult } = require('express-validator');
app.post('/register', [
body('email').isEmail().normalizeEmail(),
body('age').isInt({ min: 18, max: 120 }),
body('username').matches(/^[a-zA-Z0-9_]{3,32}$/),
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
// Traitement sécurisé
});

Prévention des injections

SQL — Requêtes paramétrées

python
# ❌ Injection SQL possible
query = f"SELECT * FROM users WHERE email = '{email}'"
cursor.execute(query)
# ✅ Paramètre bind
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))
# ✅ ORM (SQLAlchemy)
user = db.session.query(User).filter_by(email=email).first()
java
// ❌ Vulnérable
String query = "SELECT * FROM users WHERE name = '" + name + "'";
Statement stmt = conn.createStatement();
// ✅ PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE name = ?"
);
stmt.setString(1, name);

Command Injection

python
# ❌ Command injection possible
import subprocess
subprocess.run(f"ping {ip}", shell=True)
# ✅ Pas de shell=True, arguments séparés
import ipaddress
def ping_host(ip):
try:
ipaddress.ip_address(ip) # Validation
except ValueError:
raise ValueError("Invalid IP")
subprocess.run(["ping", "-c", "4", ip], shell=False, check=True)

SSTI — Server-Side Template Injection

python
# ❌ Vulnérable (Jinja2)
from jinja2 import Template
Template(user_input).render()
# ✅ Séparer template et données
template = Template("Hello {{ name }}!")
template.render(name=user_input) # Autoescape activé

Cryptographie

Hachage des mots de passe

python
# ❌ MD5, SHA1, SHA256 — NON pour les passwords
import hashlib
hash = hashlib.md5(password.encode()).hexdigest()
# ✅ bcrypt / argon2
import bcrypt
# Hachage
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Vérification
bcrypt.checkpw(password.encode(), hashed)
# ✅ argon2 (recommandé OWASP)
from argon2 import PasswordHasher
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=1)
hash = ph.hash(password)
ph.verify(hash, password)

Chiffrement symétrique

python
# ❌ DES, 3DES, RC4 — dépréciés
# ❌ AES-ECB — révèle les patterns
# ✅ AES-256-GCM (authentifié)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
key = os.urandom(32) # 256 bits
nonce = os.urandom(12) # 96 bits
aad = b"associated data" # Données authentifiées
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, aad)
plaintext = aesgcm.decrypt(nonce, ciphertext, aad)

Génération de tokens sécurisés

python
# ❌ Math.random() / random() — prévisible
import random
token = str(random.randint(100000, 999999))
# ✅ CSPRNG (Cryptographically Secure PRNG)
import secrets
token = secrets.token_urlsafe(32) # 256 bits, URL-safe
otp = secrets.randbelow(1000000) # OTP 6 chiffres

Gestion des secrets

python
# ❌ Secrets hardcodés dans le code
API_KEY = "sk-prod-abc123xyz789"
DB_PASSWORD = "supersecret"
# ✅ Variables d'environnement
import os
API_KEY = os.environ.get("API_KEY")
if not API_KEY:
raise RuntimeError("API_KEY environment variable not set")
# ✅ Coffre-fort (HashiCorp Vault)
import hvac
client = hvac.Client(url='https://vault.company.com', token=os.environ['VAULT_TOKEN'])
secret = client.secrets.kv.read_secret_version(path='myapp/database')
DB_PASSWORD = secret['data']['data']['password']
bash
# .gitignore — ne JAMAIS commiter
env
pem
key
config/secrets.yml
credentialsjson
# Vérification pré-commit
pip install detect-secrets
detect-secrets scan > secretsbaseline

Authentification & Sessions

python
# Sessions sécurisées (Flask)
from flask import Flask, session
app = Flask(__name__)
app.config.update(
SECRET_KEY=secrets.token_hex(32),
SESSION_COOKIE_SECURE=True, # HTTPS uniquement
SESSION_COOKIE_HTTPONLY=True, # Pas de JS access
SESSION_COOKIE_SAMESITE='Lax', # Protection CSRF
PERMANENT_SESSION_LIFETIME=1800, # 30 min timeout
)
javascript
// JWT — configuration sécurisée (Node.js)
const jwt = require('jsonwebtoken');
// ❌ Algorithme none accepté
// jwt.verify(token, '', { algorithms: ['none'] })
// ✅ Algorithme fixé, secret fort
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ algorithm: 'HS256', expiresIn: '1h' }
);
// Vérification stricte
jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // Algorithme explicite
issuer: 'myapp.com',
audience: 'myapp-client',
});

Headers de sécurité HTTP

python
# Flask — headers sécurité
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app,
content_security_policy={
'default-src': "'self'",
'script-src': ["'self'", 'cdn.jsdelivr.net'],
'style-src': ["'self'", "'unsafe-inline'"],
},
strict_transport_security=True,
strict_transport_security_max_age=31536000,
frame_options='DENY',
content_type_options=True,
referrer_policy='strict-origin-when-cross-origin',
)

Gestion des erreurs

python
# ❌ Exposer les détails d'erreur
@app.errorhandler(500)
def error(e):
return str(e), 500 # Stack trace exposée
# ✅ Message générique + log interne
import logging
logger = logging.getLogger(__name__)
@app.errorhandler(Exception)
def handle_error(e):
logger.error(f"Internal error: {e}", exc_info=True)
return {"error": "An internal error occurred"}, 500
# ✅ Messages d'auth non discriminants
# ❌ "User not found" vs "Wrong password" → user enumeration
# ✅ "Invalid credentials" (même message)

Checklist OWASP Secure Coding

A01 Broken Access Control
Vrifier les droits ct serveur chaque requte
Refuser par dfaut deny-by-default)
Logs des accs refuss
A02 Cryptographic Failures
Chiffrer les donnes sensibles au repos
TLS partout pas de HTTP en clair
Algorithmes modernes pas MD5 DES RC4
A03 Injection
Requtes paramtres pour SQL
Pas de shellTrue dans subprocess
chappement HTML des outputs
A05 Security Misconfiguration
Dsactiver les pages derreur dtailles en prod
Pas de comptes par dfaut admin/admin
Supprimer les endpoints de debug
A07 Auth & Session Failures
bcrypt/argon2 pour les passwords
CSPRNG pour les tokens
Expiration et invalidation des sessions
A09 Logging & Monitoring
Loguer les checs dauthentification
Loguer les changements de droits
Ne pas loguer les donnes sensibles passwords tokens
OPS·BRAIN v1.092 notes · Securitylocal