Exercice 02 : Mettre en place CodeQL pour l'analyse statique du code
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Activer CodeQL via l'interface GitHub ou un workflow manuel
- ✅ Comprendre la différence entre SAST (code statique) et SCA (dépendances)
- ✅ Lire et interpréter les alertes CodeQL dans l'onglet Security
- ✅ Créer un fichier de configuration CodeQL personnalisé
- ✅ Déclencher CodeQL sur chaque Pull Request
Durée estimée : 30 minutes
Difficulté : ⭐⭐☆☆☆ (Intermédiaire)
Prérequis : Compte GitHub, repository JavaScript/Python/Java/C++ ou Go
📖 Contexte
CodeQL est l'outil d'analyse statique (SAST) de GitHub. Contrairement à Dependabot qui analyse les dépendances tierces, CodeQL analyse votre propre code pour détecter des vulnérabilités introduites par vous ou votre équipe : injections SQL, cross-site scripting (XSS), traversal de chemin, désérialisation non sécurisée, etc.
CodeQL utilise une approche "requêtes de données" : il modélise le code comme une base de données et exécute des requêtes pour trouver des patterns dangereux.
📋 Énoncé
Activez CodeQL sur un repository et découvrez comment il détecte les vulnérabilités dans le code.
🧭 Déroulement de l'exercice
Tâche 1 : Activer CodeQL via GitHub
Méthode 1 - Interface graphique (recommandé pour débuter) :
- Allez dans Settings → Security → Code security and analysis
- Cliquez sur Set up à côté de "Code scanning"
- Choisissez CodeQL Analysis
- GitHub crée automatiquement
.github/workflows/codeql.yml
Méthode 2 - Manuellement :
mkdir -p .github/workflows
cat > .github/workflows/codeql.yml << 'EOF'
name: CodeQL Security Analysis
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
schedule:
# Scan complet chaque semaine (dimanche à 2h)
- cron: '0 2 * * 0'
jobs:
analyze:
name: Analyse CodeQL
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write # Obligatoire pour poster les alertes
strategy:
fail-fast: false
matrix:
language: ['javascript'] # Adapter : python, java, go, cpp, csharp, ruby
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Initialiser CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# Requêtes de sécurité étendues
queries: security-and-quality
- name: Auto-construire le code
uses: github/codeql-action/autobuild@v3
- name: Analyser
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{ matrix.language }}"
EOFTâche 2 : Créer du code intentionnellement vulnérable
Pour observer CodeQL en action, créez un fichier JavaScript avec des vulnérabilités connues :
cat > vulnerable-app.js << 'EOF'
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
app.use(express.json());
// VULNÉRABILITÉ 1 : Path Traversal
// Un attaquant peut accéder à /etc/passwd avec ?file=../../../etc/passwd
app.get('/file', (req, res) => {
const filename = req.query.file;
const filepath = path.join(__dirname, 'files', filename); // ⚠️ Non validé
const content = fs.readFileSync(filepath, 'utf8');
res.send(content);
});
// VULNÉRABILITÉ 2 : XSS Reflected
// Un attaquant peut injecter du HTML/JS dans la réponse
app.get('/search', (req, res) => {
const query = req.query.q;
res.send(`<h1>Résultats pour : ${query}</h1>`); // ⚠️ Pas d'échappement HTML
});
// VULNÉRABILITÉ 3 : Injection dans eval()
app.post('/calculate', (req, res) => {
const expression = req.body.expr;
const result = eval(expression); // ⚠️ JAMAIS utiliser eval() avec entrée utilisateur
res.json({ result });
});
app.listen(3000);
EOF
git add .github/workflows/codeql.yml vulnerable-app.js
git commit -m "ci: activer CodeQL + code de démonstration"
git pushNote : Ces vulnérabilités sont créées intentionnellement pour l'exercice. Ne déployez jamais ce code en production.
Tâche 3 : Interpréter les alertes
Après que le workflow a tourné (2-5 minutes) :
- Allez dans l'onglet Security → Code scanning alerts
- Pour chaque alerte, observez :
- Rule : le type de vulnérabilité (ex:
js/path-injection) - Severity : Critical / High / Medium / Low
- Location : le fichier et la ligne exacte
- Description : l'explication et comment corriger
Indice : CodeQL montre le "data flow" - le chemin que suit la donnée depuis son entrée (source) jusqu'à l'endroit dangereux (sink). Pour le path traversal, la source est
req.query.fileet le sink estfs.readFileSync().
Vérification : Au moins 2 alertes doivent apparaître (path injection et XSS reflected).
Tâche 4 : Corriger les vulnérabilités
Corrigez le code et observez les alertes disparaître :
cat > secure-app.js << 'EOF'
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
app.use(express.json());
// CORRIGÉ : Path Traversal → valider et restreindre le chemin
app.get('/file', (req, res) => {
const filename = req.query.file;
// Validation : seulement des noms de fichiers simples (pas de ../ ni /)
if (!/^[a-zA-Z0-9_-]+\.[a-zA-Z]{2,4}$/.test(filename)) {
return res.status(400).json({ error: 'Nom de fichier invalide' });
}
const filesDir = path.resolve(__dirname, 'files');
const filepath = path.resolve(filesDir, filename);
// Double vérification : le chemin résolu doit rester dans filesDir
if (!filepath.startsWith(filesDir)) {
return res.status(403).json({ error: 'Accès refusé' });
}
const content = fs.readFileSync(filepath, 'utf8');
res.send(content);
});
// CORRIGÉ : XSS → échapper le HTML
const escapeHtml = (str) =>
str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
.replace(/"/g,'"').replace(/'/g,''');
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`<h1>Résultats pour : ${query}</h1>`);
});
// CORRIGÉ : Remplacer eval() par une whitelist d'opérations
const ALLOWED_OPS = { '+': (a,b) => a+b, '-': (a,b) => a-b, '*': (a,b) => a*b };
app.post('/calculate', (req, res) => {
const { a, op, b } = req.body;
if (!ALLOWED_OPS[op]) return res.status(400).json({ error: 'Opération invalide' });
res.json({ result: ALLOWED_OPS[op](Number(a), Number(b)) });
});
app.listen(3000);
EOF
git add secure-app.js
git commit -m "fix: corriger path traversal, XSS et injection eval"
git pushVérification : Après le prochain scan CodeQL, les alertes pour secure-app.js ne doivent pas apparaître. Les alertes vulnerable-app.js restent (le fichier est toujours là).
Tâche 5 : Bloquer les PRs avec des alertes CodeQL
Configurez GitHub pour bloquer les merges si CodeQL trouve des vulnérabilités :
- Settings → Branches → Add branch protection rule
- Pour
main, cochez :
- Require status checks to pass before merging
- Ajoutez "CodeQL / Analyse CodeQL (javascript)" dans les checks requis
- Sauvegardez
Désormais, une PR introduisant une vulnérabilité détectée par CodeQL ne pourra pas être mergée.
✅ Vérification du résultat
.github/workflows/codeql.ymlest commité et poussé- Le workflow apparaît dans l'onglet Actions et s'exécute
- Des alertes apparaissent dans Security → Code scanning alerts
- Les alertes montrent le fichier, la ligne et le type de vulnérabilité
💡 À retenir
CodeQL analyse votre code, Dependabot analyse vos dépendances. Les deux sont complémentaires :
| Outil | Cible | Exemples |
|---|---|---|
| CodeQL | Votre code | XSS, injection SQL, path traversal |
| Dependabot | Bibliothèques tierces | CVE dans express, log4j |
| Gitleaks | Historique Git | Clés API, tokens |
La combinaison des trois couvre la majorité des risques de sécurité applicative.
✨ Solution Complète
# .github/workflows/codeql.yml (minimal)
name: CodeQL
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with: {languages: javascript, queries: security-and-quality}
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3