---
title: "Secure Coding — Bonnes Pratiques de Développement"
domain: security
subdomain: hardening
type: checklist
tags: [secure-coding, owasp, injection, cryptographie, validation, devsecops]
difficulty: intermediate
status: stable
updated: "Sat May 30 2026 00:00:00 GMT+0000 (Coordinated Universal Time)"
---
## 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
credentials.json

# Vérification pré-commit
pip install detect-secrets
detect-secrets scan > .secrets.baseline
```

---

## 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
  □ Vérifier les droits côté serveur à chaque requête
  □ Refuser par défaut (deny-by-default)
  □ Logs des accès refusés

A02 — Cryptographic Failures
  □ Chiffrer les données sensibles au repos
  □ TLS partout, pas de HTTP en clair
  □ Algorithmes modernes (pas MD5, DES, RC4)

A03 — Injection
  □ Requêtes paramétrées pour SQL
  □ Pas de shell=True dans subprocess
  □ Échappement HTML des outputs

A05 — Security Misconfiguration
  □ Désactiver les pages d'erreur détaillées en prod
  □ Pas de comptes par défaut (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 d'authentification
  □ Loguer les changements de droits
  □ Ne pas loguer les données sensibles (passwords, tokens)
```
