First commit

This commit is contained in:
Johan
2025-12-18 15:18:51 +01:00
commit 8438829832
11 changed files with 2279 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/.idea

161
README.md Normal file
View File

@@ -0,0 +1,161 @@
# Démonstration OWASP A05:2025 : faille d'injection (XSS Reflected)
## Objectif
Comprendre la faille de sécurité **OWASP A05:2025 (Injection)**, spécifiquement une attaque XSS (Cross-Site Scripting) Reflétée. L'OWASP Top 10 2025 classe le Cross-site Scripting comme un type de faille d'injection.
Dans cet exercice, nous allons voir comment une donnée fournie par un utilisateur via une URL (un paramètre `search`) peut être utilisée pour injecter et exécuter du code JavaScript malveillant dans la page. Cela est possible lorsque les données fournies par l'utilisateur ne sont pas validées, filtrées ou aseptisées par l'application.
Nous verrons également comment le moteur de template **Pug** nous protège par défaut, et comment une mauvaise utilisation de sa syntaxe peut créer la vulnérabilité.
## Contexte
Nous avons un simple serveur Express.js qui utilise **Pug** comme moteur de template.
1. L'utilisateur accède à la page, par exemple `http://localhost:3000/`.
2. Il peut utiliser un formulaire de recherche, qui soumet sa requête via un paramètre d'URL (ex: `http://localhost:3000/?search=chats`).
3. Le serveur (dans `routes/index.js`) récupère cette valeur `search`.
4. Il la transmet au template `views/index.pug` pour qu'il l'affiche.
<!-- end list -->
```mermaid
flowchart TD
subgraph "Navigateur de l'utilisateur"
A["Formulaire de recherche (index.pug)"]
end
subgraph "Serveur (Express + Pug)"
B["routes/index.js<br>Recupere req.query.search"]
C["views/index.pug<br>Affiche le 'searchTerm'"]
end
A -- "1. Submit GET /?search=..." --> B
B -- "2. Passe 'searchTerm' au template" --> C
C -- "3. Rend le HTML et l'envoie au navigateur" --> A
```
## Instructions
### Mise en place
1. Ouvrez un terminal et placez-vous dans le dossier de ce projet.
2. Installez les dépendances:
```bash
npm install
```
3. Lancez le serveur API:
```bash
nodemon ./bin/www
```
*(Le serveur tourne maintenant sur `http://localhost:3000`)*
4. Ouvrez `http://localhost:3000` dans votre navigateur.
-----
### Partie 1 : Test normal (non malveillant)
1. Sur la page, dans le formulaire de recherche, entrez un texte simple comme **"chats"**.
2. Cliquez sur "Lancer la recherche".
3. **Résultat attendu :**
* L'URL est devenue `http://localhost:3000/?search=chats`.
* La page affiche "chats" dans la section "1. Rendu SÉCURISÉ".
* La page affiche "chats" dans la section "2. Rendu VULNÉRABLE".
Tout fonctionne normalement.
-----
### Partie 2 : Démonstration de la faille XSS
Maintenant, nous allons simuler une attaque. Au lieu d'un simple mot, nous allons injecter une balise `<script>`.
1. Dans le formulaire de recherche, entrez le texte suivant :
```html
<script>alert('Faille XSS !')</script>
```
2. Cliquez sur "Lancer la recherche".
**Résultat attendu :**
* Une boîte d'alerte (pop-up) apparaît dans votre navigateur avec le message "Faille XSS \!".
* Si vous inspectez le code source de la page (clic droit \> Afficher le code source) au niveau des résultats, vous verrez ceci :
**1. Rendu SÉCURISÉ :** Le code a été "échappé". Il est inoffensif.
```html
<span style="background-color: #e0fde0; ...">&amp;lt;script&amp;gt;alert('Faille XSS !')&amp;lt;/script&amp;gt;</span>
```
**2. Rendu VULNÉRABLE :** Le code a été injecté tel quel. Le navigateur l'a lu et exécuté.
```html
<span style="background-color: #fde0e0; ..."><script>alert('Faille XSS !')</script></span>
```
-----
## Analyse : La cause de la faille (`#{}` vs. `!{}`)
La faille ne vient pas d'Express, mais de la façon dont le template Pug a été instruit d'afficher la donnée `searchTerm` (voir `views/index.pug`).
Pug, comme la plupart des moteurs de template modernes, fournit une protection **par défaut** contre les attaques XSS en **"échappant"** les caractères spéciaux.
* `<` devient `&lt;` (less-than)
* `>` devient `&gt;` (greater-than)
<!-- end list -->
```mermaid
sequenceDiagram
participant Input as "Payload<br><script>alert('...')</script>"
participant Pug as "Moteur de template Pug"
participant HTML as "Sortie HTML (pour le navigateur)"
Input -->> Pug: "Syntaxe SÉCURISÉE<br>code: #35;{searchTerm}"
Pug-->>HTML: "Pug-->>HTML: "&amp&#59;lt&#59;script&gt&#59;alert('...')&amp&#59;lt&#59;/script&gt&#59;"
note right of HTML: Le navigateur affiche le texte, n'execute rien.
Input -->> Pug: "Syntaxe VULNÉRABLE<br>code: !{searchTerm}"
Pug-->>HTML: "<script>alert('...')</script>"
note right of HTML: "Le navigateur voit une balise script et l'execute !"
```
### 1. Le Rendu Sécurisé (Escaped)
En utilisant `#{searchTerm}`, nous demandons à Pug d'insérer la variable en l'échappant. C'est le comportement par défaut et souhaité pour toute donnée venant d'un utilisateur.
**Code Pug :**
```pug
span #{searchTerm}
```
**Résultat HTML (Sûr) :**
```html
<span>&amp;lt;script&amp;gt;alert('Faille XSS !')&amp;lt;/script&amp;gt;</span>
```
### 2. Le Rendu Vulnérable (Unescaped)
En utilisant `!{searchTerm}` (notez le `!`), nous disons explicitement à Pug : "Fais confiance à cette variable. Insère-la telle quelle, brute, sans aucun échappement."
C'est extrêmement dangereux si la variable (`searchTerm`) provient d'un utilisateur, car elle permet l'injection de n'importe quel HTML ou JavaScript.
**Code Pug :**
```pug
span !{searchTerm}
```
**Résultat HTML (Vulnérable) :**
```html
<span><script>alert('Faille XSS !')</script></span>
```
## Conclusion et Correction
* **La faille A05 (Injection)** se produit lorsque des données non fiables (venant de l'utilisateur) sont envoyées à un interpréteur (ici, le navigateur) sans être nettoyées ou échappées.
* **La correction** est de **toujours** utiliser la syntaxe d'échappement par défaut de votre moteur de template (`#{variable}` en Pug) lorsque vous affichez des données fournies par l'utilisateur.
* Cela correspond aux mesures préventives de l'OWASP, comme l'utilisation d'une "API sûre" (l'API de Pug par défaut) ou "l'échappement des caractères spéciaux".
* N'utilisez la syntaxe de non-échappement (`!{}`) que pour du contenu HTML que **vous** contrôlez entièrement et dont vous êtes certain qu'il est sûr.

36
app.js Normal file
View File

@@ -0,0 +1,36 @@
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

90
bin/www Normal file
View File

@@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('injection-xss:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

1759
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "injection-xss",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"nodemon": "^3.1.10",
"pug": "2.0.0-beta11"
}
}

View File

@@ -0,0 +1,133 @@
/* --- Variables & Réglages de base --- */
:root {
--color-bg: #f4f7f6;
--color-white: #ffffff;
--color-text: #333;
--color-primary: #007bff;
--color-primary-dark: #0056b3;
--color-border: #ddd;
--color-code-bg: #eef;
--color-code-text: #55a;
--color-secure-bg: #e0fde0;
--color-secure-border: #b0dab0;
--color-secure-text: #2a612a;
--color-vulnerable-bg: #fde0e0;
--color-vulnerable-border: #dab0b0;
--color-vulnerable-text: #612a2a;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* --- Typographie et Contenu Principal --- */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
background-color: var(--color-bg);
color: var(--color-text);
/* On crée un conteneur centré */
max-width: 900px;
margin: 40px auto;
padding: 40px;
background-color: var(--color-white);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
h1 {
color: #222;
margin-bottom: 20px;
}
h2 {
color: #444;
margin-top: 30px;
margin-bottom: 15px;
border-bottom: 2px solid #eee;
padding-bottom: 5px;
}
p {
margin-bottom: 15px;
}
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
background-color: var(--color-code-bg);
padding: 2px 6px;
border-radius: 4px;
color: var(--color-code-text);
}
hr {
border: none;
border-top: 1px solid var(--color-border);
margin: 30px 0;
}
/* --- Formulaire --- */
form {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
}
label {
font-weight: bold;
white-space: nowrap; /* Empêche le label de passer à la ligne */
}
input[type="text"] {
flex-grow: 1; /* Prend l'espace disponible */
padding: 10px 12px;
font-size: 16px;
border: 1px solid var(--color-border);
border-radius: 4px;
}
button[type="submit"] {
padding: 10px 20px;
font-size: 16px;
color: var(--color-white);
background-color: var(--color-primary);
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
}
button[type="submit"]:hover {
background-color: var(--color-primary-dark);
}
/* --- Styles pour la Démo XSS --- */
/* Classes spéciales pour nos résultats.
On va remplacer le style="..." dans le Pug par class="..."
*/
.result-secure,
.result-vulnerable {
padding: 8px 12px;
border-radius: 4px;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-weight: bold;
display: inline-block;
}
.result-secure {
background-color: var(--color-secure-bg);
border: 1px solid var(--color-secure-border);
color: var(--color-secure-text);
}
.result-vulnerable {
background-color: var(--color-vulnerable-bg);
border: 1px solid var(--color-vulnerable-border);
color: var(--color-vulnerable-text);
}

21
routes/index.js Normal file
View File

@@ -0,0 +1,21 @@
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
// On récupère le paramètre 'search' de l'URL
// ex: http://localhost:3000/?search=mon_texte
const searchTerm = req.query.search;
// On "rend" la vue 'index.pug' en lui passant des variables
res.render('index', {
title: 'Démonstration de Faille XSS',
// On passe le terme de recherche (qui peut être malveillant)
// à notre template Pug.
searchTerm: searchTerm
});
});
module.exports = router;

6
views/error.pug Normal file
View File

@@ -0,0 +1,6 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

48
views/index.pug Normal file
View File

@@ -0,0 +1,48 @@
extends layout
block content
h1= title
p Bienvenue sur la page de démonstration XSS.
hr
h2 Formulaire de test
p Entrez un texte normal (ex: "chats") ou un script (ex:
code &lt;script&gt;alert('XSS !')&lt;/script&gt;
| )
form(method="GET" action="/" style="margin-top: 10px;")
label(for="search-input") Votre recherche :
input(type="text" name="search" id="search-input" style="width: 300px;" placeholder="<script>alert('XSS !')</script>")
button(type="submit") Lancer la recherche
hr
// On n'affiche les résultats que si un terme de recherche est présent
if searchTerm
h2 1. Rendu SÉCURISÉ (par défaut avec Pug)
p
| Le template utilise
code #{searchTerm}
| . Pug "échappe" les caractères HTML.
p
strong Résultat :
//- C'est la syntaxe SÉCURISÉE
span(style="background-color: #e0fde0; padding: 5px; border-radius: 4px;") #{searchTerm}
hr
h2 2. Rendu VULNÉRABLE (Faille XSS)
p
| Le template utilise
code !{searchTerm}
| . Le
code !
| désactive l'échappement.
p
strong Résultat :
//- C'est la syntaxe VULNÉRABLE. Le script sera inséré tel quel, car l'échappement est désactivé (!).
span(style="background-color: #fde0e0; padding: 5px; border-radius: 4px;") !{searchTerm}
else
p (Aucune recherche effectuée)

7
views/layout.pug Normal file
View File

@@ -0,0 +1,7 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content