First commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.idea
|
||||||
161
README.md
Normal file
161
README.md
Normal 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; ...">&lt;script&gt;alert('Faille XSS !')&lt;/script&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 `<` (less-than)
|
||||||
|
* `>` devient `>` (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: "&;lt;script>;alert('...')&;lt;/script>;"
|
||||||
|
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>&lt;script&gt;alert('Faille XSS !')&lt;/script&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
36
app.js
Normal 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
90
bin/www
Normal 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
1759
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
Normal file
17
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
133
public/stylesheets/style.css
Normal file
133
public/stylesheets/style.css
Normal 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
21
routes/index.js
Normal 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
6
views/error.pug
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
extends layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
h1= message
|
||||||
|
h2= error.status
|
||||||
|
pre #{error.stack}
|
||||||
48
views/index.pug
Normal file
48
views/index.pug
Normal 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 <script>alert('XSS !')</script>
|
||||||
|
| )
|
||||||
|
|
||||||
|
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
7
views/layout.pug
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
doctype html
|
||||||
|
html
|
||||||
|
head
|
||||||
|
title= title
|
||||||
|
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||||
|
body
|
||||||
|
block content
|
||||||
Reference in New Issue
Block a user