First commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/.idea
|
||||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# Démonstration OWASP A02 : mauvaise configuration de sécurité (CORS)
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Comprendre la faille de sécurité OWASP A02:2025 (Security Misconfiguration) à travers une démonstration concrète sur CORS (Cross-Origin Resource Sharing).
|
||||||
|
|
||||||
|
Dans cet exercice, vous n'allez pas coder. Vous allez manipuler la configuration d'un serveur Express.js pour observer la différence entre une configuration vulnérable et une configuration sécurisée.
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Nous avons deux projets:
|
||||||
|
* `cors`: un serveur Express (back-end) qui tourne sur `http://localhost:3000`.
|
||||||
|
* sous-répertoire `client-test`: un simple fichier `index.html` (front-end) qui sera servi par votre IDE (sur un port dynamique, ex: `http://localhost:63342`).
|
||||||
|
|
||||||
|
Le client (d'une certaine origine) va tenter d'appeler l'API (sur une autre origine). Nous allons voir comment CORS peut bloquer ou autoriser cet appel.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph "Client (Navigateur)"
|
||||||
|
ClientApp["index.html<br>Origine: http://localhost:63342"]
|
||||||
|
end
|
||||||
|
subgraph "Serveur (API)"
|
||||||
|
ServerApp["app.js (Express)<br>Origine: http://localhost:3000"]
|
||||||
|
end
|
||||||
|
ClientApp -- "Tente d'appeler l'API" --> ServerApp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
### Mise en place
|
||||||
|
|
||||||
|
1. Ouvrez un terminal et placez-vous dans le dossier `cors`.
|
||||||
|
2. Installez les dépendances:
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
3. Ouvrez le fichier `client-test/index.html` avec votre IDE (clic droit > "Run 'index.html'" ou similaire).
|
||||||
|
4. Gardez cette page navigateur ouverte. Elle vous indique sa propre origine (ex: `http://localhost:63342`). Notez-la.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Partie 1 : la vulnérabilité (origin: '*')
|
||||||
|
|
||||||
|
Dans cette partie, nous allons voir pourquoi `origin: '*'` est une mauvaise configuration de sécurité.
|
||||||
|
|
||||||
|
1. Ouvrez le fichier `cors/app.js`.
|
||||||
|
2. Vérifiez que c'est bien `corsOptions_VULNERABLE` qui est activé:
|
||||||
|
```javascript
|
||||||
|
// DÉCOMANDEZ L'OPTION QUE VOUS VOULEZ TESTER :
|
||||||
|
|
||||||
|
app.use(cors(corsOptions_VULNERABLE)); // <--- CETTE LIGNE DOIT ÊTRE ACTIVE
|
||||||
|
//
|
||||||
|
// app.use(cors(corsOptions_SECURISEE));
|
||||||
|
```
|
||||||
|
3. Lancez le serveur API dans votre terminal (depuis le dossier `cors`):
|
||||||
|
```bash
|
||||||
|
nodemon ./bin/www
|
||||||
|
```
|
||||||
|
*(Le serveur tourne maintenant sur `http://localhost:3000`)*
|
||||||
|
4. Retournez sur votre page `index.html` dans le navigateur.
|
||||||
|
5. Cliquez sur le bouton "Appeler l'API".
|
||||||
|
|
||||||
|
**Résultat attendu:** succès. La page affiche les données JSON de l'API.
|
||||||
|
|
||||||
|
**Pourquoi c'est une faille:**
|
||||||
|
L'API a répondu. C'est normal, car `origin: '*'` signifie "J'autorise n'importe quel site web sur Internet à m'appeler". Si cette page était un site de phishing, elle aurait pu récupérer vos données. C'est une faille A02.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client as "Client<br>localhost:63342"
|
||||||
|
participant Serveur as "Serveur<br>localhost:3000"
|
||||||
|
Client->>Serveur: "1. Requete API (Origin: localhost:63342)"
|
||||||
|
Serveur-->>Serveur: 2. Verifie config CORS: origin: '*'
|
||||||
|
Serveur->>Client: "3. Reponse API (Access-Control-Allow-Origin: *)"
|
||||||
|
Client-->>Client: 4. Navigateur autorise<br>Succes!
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Partie 2 : la correction (Whitelist)
|
||||||
|
|
||||||
|
Maintenant, nous allons corriger la faille en n'autorisant que notre client légitime.
|
||||||
|
|
||||||
|
1. Arrêtez le serveur API (`Ctrl+C` dans le terminal).
|
||||||
|
2. Dans `cors/app.js`, modifiez les lignes `app.use` pour activer la version sécurisée:
|
||||||
|
```javascript
|
||||||
|
// DÉCOMANDEZ L'OPTION QUE VOUS VOULEZ TESTER :
|
||||||
|
|
||||||
|
// app.use(cors(corsOptions_VULNERABLE));
|
||||||
|
//
|
||||||
|
app.use(cors(corsOptions_SECURISEE)); // <--- CETTE LIGNE DOIT ÊTRE ACTIVE
|
||||||
|
```
|
||||||
|
3. Dans ce même fichier, juste au-dessus, modifiez la variable `corsOptions_SECURISEE` pour qu'elle corresponde **exactement** à l'origine affichée sur votre page `index.html`.
|
||||||
|
|
||||||
|
*Exemple: si votre page affiche `http://localhost:63342`, le code doit être:*
|
||||||
|
```javascript
|
||||||
|
const corsOptions_SECURISEE = {
|
||||||
|
origin: 'http://localhost:63342'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
4. Sauvegardez `app.js` et relancez le serveur API:
|
||||||
|
```bash
|
||||||
|
nodemon ./bin/www
|
||||||
|
```
|
||||||
|
5. Retournez sur votre page `index.html` et rafraîchissez-la (`Ctrl+R` ou `Cmd+R`).
|
||||||
|
6. Cliquez sur le bouton "Appeler l'API".
|
||||||
|
|
||||||
|
**Résultat attendu:** succès. Les données s'affichent, comme dans la partie 1.
|
||||||
|
|
||||||
|
**Alors, quelle est la différence ?**
|
||||||
|
La différence est que maintenant, *seule* cette origine est autorisée. Pour le prouver, faisons un dernier test.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Partie 3 : la preuve de la sécurité
|
||||||
|
|
||||||
|
Prouvons que notre API rejette bien les origines inconnues.
|
||||||
|
|
||||||
|
1. Arrêtez le serveur API (`Ctrl+C`).
|
||||||
|
2. Dans `cors/app.js`, modifiez `corsOptions_SECURISEE` pour y mettre une fausse origine:
|
||||||
|
```javascript
|
||||||
|
const corsOptions_SECURISEE = {
|
||||||
|
origin: '[http://site-malveillant.com](http://site-malveillant.com)'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
3. Sauvegardez `app.js` et relancez le serveur API:
|
||||||
|
```bash
|
||||||
|
nodemon ./bin/www
|
||||||
|
```
|
||||||
|
4. Retournez sur votre page `index.html` (qui tourne toujours sur `http://localhost:63342`) et rafraîchissez-la.
|
||||||
|
5. Ouvrez la console de développement (F12) et allez dans l'onglet "Console".
|
||||||
|
6. Cliquez sur le bouton "Appeler l'API".
|
||||||
|
|
||||||
|
**Résultat attendu:** échec. La page affiche `ERREUR: Failed to fetch`.
|
||||||
|
|
||||||
|
**Pourquoi c'est corrigé:**
|
||||||
|
Regardez la console du navigateur. Vous y verrez une erreur CORS en rouge. Le navigateur a bloqué la requête car:
|
||||||
|
* Le client (`http://localhost:63342`) a demandé des données.
|
||||||
|
* Le serveur a répondu: "Je n'autorise que `http://site-malveillant.com`".
|
||||||
|
* Le navigateur a comparé les deux, a vu qu'ils ne correspondaient pas, et a bloqué la réponse pour protéger l'utilisateur.
|
||||||
|
|
||||||
|
Nous avons corrigé la faille A02:2025, notre API ne parle désormais qu'aux clients qu'elle connaît et approuve.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Client as "Client<br>localhost:63342"
|
||||||
|
participant Serveur as "Serveur<br>localhost:3000"
|
||||||
|
Client->>Serveur: "1. Requete API (Origin: localhost:63342)"
|
||||||
|
Serveur-->>Serveur: 2. Verifie config CORS: origin: 'http://site-malveillant.com'
|
||||||
|
Serveur->>Client: "3. Reponse API (Access-Control-Allow-Origin: http://site-malveillant.com)"
|
||||||
|
Client-->>Client: 4. Navigateur compare:<br>Requete "localhost:63342" != Reponse "site-malveillant.com"
|
||||||
|
Client-->>Client: 5. ECHEC: Erreur CORS
|
||||||
|
```
|
||||||
55
app.js
Normal file
55
app.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
var createError = require('http-errors');
|
||||||
|
var express = require('express');
|
||||||
|
var logger = require('morgan');
|
||||||
|
var cors = require('cors'); // Import du middleware CORS
|
||||||
|
var indexRouter = require('./routes/index');
|
||||||
|
var app = express();
|
||||||
|
app.use(logger('dev'));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
// DÉMONSTRATION A02 : MAUVAISE CONFIGURATION CORS
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
// --- OPTION 1 : la Vulnérabilité (A02) ---
|
||||||
|
// '*' signifie que N'IMPORTE QUEL site web sur Internet
|
||||||
|
// peut appeler votre API depuis le navigateur d'un client.
|
||||||
|
const corsOptions_VULNERABLE = {
|
||||||
|
origin: '*'
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- OPTION 2 : la correction ---
|
||||||
|
// On n'autorise QUE notre front-end (penser à mettre le bon port lu après exécution de la page HTML frontend)
|
||||||
|
const corsOptions_SECURISEE = {
|
||||||
|
origin: 'http://localhost:5500' // ou 'http://127.0.0.1:5500'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// DÉCOMMANDEZ L'OPTION QUE VOUS VOULEZ TESTER :
|
||||||
|
//
|
||||||
|
app.use(cors(corsOptions_VULNERABLE)); // <--- Testez d'abord ceci
|
||||||
|
//
|
||||||
|
// app.use(cors(corsOptions_SECURISEE)); // <--- Testez ceci ensuite
|
||||||
|
|
||||||
|
// ==========================================================
|
||||||
|
|
||||||
|
// --- Déclaration des routes ---
|
||||||
|
app.use('/', indexRouter);
|
||||||
|
|
||||||
|
// catch 404 and forward to error handler
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
next(createError(404, 'Route non trouvée'));
|
||||||
|
});
|
||||||
|
|
||||||
|
// error handler (renvoie du JSON)
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
res.status(err.status || 500);
|
||||||
|
res.json({
|
||||||
|
error: {
|
||||||
|
message: err.message,
|
||||||
|
status: err.status
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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')('cors: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);
|
||||||
|
}
|
||||||
60
client-test/index.html
Normal file
60
client-test/index.html
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Testeur CORS</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; padding: 20px; }
|
||||||
|
h1 { color: #333; }
|
||||||
|
h2 { font-size: 1.1em; color: #666; }
|
||||||
|
button { font-size: 1.2em; padding: 10px; }
|
||||||
|
#result {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
white-space: pre-wrap; /* Pour afficher le JSON formaté */
|
||||||
|
}
|
||||||
|
.success { border-color: green; background-color: #e9f9e9; }
|
||||||
|
.error { border-color: red; background-color: #f9e9e9; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Testeur d'API (Client)</h1>
|
||||||
|
<h2>Cette page tourne sur <code id="origin-display">Détection...</code></h2>
|
||||||
|
|
||||||
|
<button id="testButton">Appeler l'API (sur <code>http://localhost:3000</code>)</button>
|
||||||
|
|
||||||
|
<h3>Résultat de l'appel :</h3>
|
||||||
|
<pre id="result">En attente de l'appel...</pre>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const API_URL = 'http://localhost:3000/test-cors'; // L'URL de notre API
|
||||||
|
const L_ORIGINE_DE_CETTE_PAGE = window.location.origin;
|
||||||
|
document.getElementById('origin-display').textContent = L_ORIGINE_DE_CETTE_PAGE;
|
||||||
|
document.getElementById('testButton').addEventListener('click', () => {
|
||||||
|
const resultEl = document.getElementById('result');
|
||||||
|
resultEl.textContent = 'Chargement...';
|
||||||
|
resultEl.className = '';
|
||||||
|
|
||||||
|
fetch(API_URL)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Réponse réseau non-OK (code ' + response.status + ')');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
resultEl.textContent = JSON.stringify(data, null, 2);
|
||||||
|
resultEl.className = 'success';
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// C'est ici qu'on verra l'erreur CORS !
|
||||||
|
resultEl.textContent = 'ERREUR: ' + error.message + '\n\n(Regardez la console du navigateur (F12) pour voir le détail de l\'erreur CORS)';
|
||||||
|
resultEl.className = 'error';
|
||||||
|
console.error('Erreur de fetch:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1773
package-lock.json
generated
Normal file
1773
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "cors",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./bin/www"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie-parser": "~1.4.4",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
routes/index.js
Normal file
14
routes/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var router = express.Router();
|
||||||
|
|
||||||
|
/* GET /test-cors */
|
||||||
|
// C'est le point de terminaison que notre front-end va appeler
|
||||||
|
router.get('/test-cors', function(req, res, next) {
|
||||||
|
console.log('Appel API reçu !');
|
||||||
|
res.json({
|
||||||
|
message: 'Félicitations, l\'API vous a répondu !',
|
||||||
|
data: 'Voici des données "secrètes" que seul le front-end devrait voir.'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
Reference in New Issue
Block a user