Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gestion de la sécurité du projet #15

Merged
merged 5 commits into from
Jun 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[![Build result](https://github.com/BaptisteBuvron/SeeISS/actions/workflows/build.yml/badge.svg)](https://github.com/BaptisteBuvron/web-app-recrutement/actions/workflows/node.js.yml)<img src="https://img.shields.io/badge/JavaScript-323330?style=flat&logo=javascript&logoColor=F7DF1E" alt="JavaScript" width="80"/> <img src="https://img.shields.io/badge/TypeScript-007ACC?style=flat&logo=typescript&logoColor=white" alt="TypeScript" width="110"/> <img src="https://img.shields.io/badge/Express.js-000000?style=flat&logo=express&logoColor=white" alt="Express.js" width="100"/> <img src="https://img.shields.io/badge/SQL-4479A1?style=flat&logo=sql&logoColor=white" alt="SQL" width="70"/>

# web-app-recrutement

Application Express.js avec TypeScript pour le système de recrutement dans le cadre de l'UV AI16.


## Etudiants
* [Baptiste Buvron](https://github.com/BaptisteBuvron)
* [Soudarsane Tillai](https://github.com/darsane21)


## Clonage du projet
```
git clone https://github.com/BaptisteBuvron/web-app-recrutement.git
cd web-app-recrutement
```

## Configuration de la base de données
1. Créer un fichier `.env` à la racine du projet.
2. Ajouter les paramètres suivants dans le fichier `.env` :
```
DB_HOST=
DB_USER=
DB_PASSWORD=
DB_DATABASE=
```

## Exécution du fichier table.sql
Exécuter le fichier `table.sql` pour initialiser la base de données.

## Installation et exécution

```
cd app
npm install
npm run dev
```

## License
Ce projet est sous license GNU General Public License (GPL). Veuillez consulter le fichier [LICENSE](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) pour plus d'informations.
17 changes: 0 additions & 17 deletions app/README.md

This file was deleted.

4 changes: 2 additions & 2 deletions app/controllers/FicheController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export class FicheController {
let teletravail: boolean = req.body.teletravail === "on";
//TODO get the siren from the recruiter
//random number 9 digits
let random = Math.floor(Math.random() * 1000000000);
let siren: string = String(random);

let siren: string = req.user.siren as string;
let nbHeures: number = parseInt(req.body.nbHeures);


Expand Down
3 changes: 2 additions & 1 deletion app/controllers/HomeController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {OffreDePoste} from "../entity/OffreDePoste";
import {UserRepository} from "../repository/UserRepository";
import {Alert} from "../utils/Alert";
import {FicheDePosteRepository} from "../repository/FicheDePosteRepository";

const {loggedInNoRedirection} = require("../passport/passportFunctions");


Expand Down Expand Up @@ -35,7 +36,7 @@ export class HomeController {
const alerts: Alert[] = [];
if (req.method === "POST") {
let siren = req.body.siren;
let mail = "[email protected]"; //TO DO get mail from session variable
let mail = req.user.email//TO DO get mail from session variable
if (req.body.siege) {
let organisation: Organisation = new Organisation(
req.body.siren,
Expand Down
13 changes: 10 additions & 3 deletions app/controllers/OfferController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {OfferRepository} from "../repository/OfferRepository";
import {FicheDePoste} from "../entity/FicheDePoste";
import {Alert} from "../utils/Alert";
import {loggedInNoRedirection} from "../passport/passportFunctions";
import {csrfValidation} from "../utils/Security";

export class OfferController {

Expand All @@ -15,6 +16,13 @@ export class OfferController {
console.log(req.method);

if (req.method === "POST") {

let csrfToken = req.body._csrf;
if (!csrfValidation(req, csrfToken)) {
alerts.push(new Alert("danger", "Erreur CSRF"));
//TODO message d'erreur
return res.redirect("/logout");
}
//TODO validation data
let listePiece: string = "";
let nbPiece: number = 0;
Expand Down Expand Up @@ -70,10 +78,9 @@ export class OfferController {
title: "Créer une offre",
ficheDePostes: ficheDePostes,
alerts: alerts,
user: loggedInNoRedirection(req, res)
user: loggedInNoRedirection(req, res),
csrfToken: req.session.csrfSecret
});
});


}
}
12 changes: 12 additions & 0 deletions app/middlewares/CSRFMiddlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {randomBytes} from 'crypto';
import {loggedInNoRedirection} from "../passport/passportFunctions";

export function createCSRFToken(req: any, res: any, next: any) {
if (loggedInNoRedirection(req, res)) {
if (req.session.csrfSecret === undefined) {
req.session.csrfSecret = randomBytes(64).toString("hex");
console.log("2. in createCSRFToken req.sessionID: ", req.sessionID);
}
}
next();
}
9 changes: 6 additions & 3 deletions app/repository/OfferRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ export class OfferRepository {
FROM ${OfferRepository.tableName}
LEFT JOIN FicheDePoste ON OffreDePoste.fiche = FicheDePoste.numero
INNER JOIN Organisation ON Organisation.siren = FicheDePoste.siren`;
let params: (string | number | undefined)[] = [];
if (filterOffer) {
query += ` WHERE FicheDePoste.salaire >= ${filterOffer.minSalary}`;
query += ` WHERE FicheDePoste.salaire >= ?`;
params.push(filterOffer.minSalary);
if (filterOffer.region) {
query += ` AND FicheDePoste.lieu = '${filterOffer.region}'`;
query += ` AND FicheDePoste.lieu = ?'`;
params.push(filterOffer.region);
}
}
return new Promise<[OffreDePoste]>(
(resolve, reject) =>
pool.query(query, (err, result) => {
pool.query(query, params, (err, result) => {
if (err) {
return reject(err);
}
Expand Down
2 changes: 2 additions & 0 deletions app/routes/MainRouter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Router} from "express";
import {HomeController} from "../controllers/HomeController";
import {createCSRFToken} from "../middlewares/CSRFMiddlewares";

const { v4: uuidv4 } = require("uuid");
const session = require("express-session");
Expand All @@ -25,6 +26,7 @@ defaultRouter.use(
);
defaultRouter.use(passport.initialize());
defaultRouter.use(passport.session());
defaultRouter.use(createCSRFToken)

defaultRouter.get("/", HomeController.index);
defaultRouter.get("/login", HomeController.login);
Expand Down
4 changes: 4 additions & 0 deletions app/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {User} from "../entity/User";

export {};

declare global {
namespace Express {
interface Request {
logout: any;
login: any;
session: any;
user: User;
}
}
}
5 changes: 5 additions & 0 deletions app/utils/Security.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {Request} from "express";

export function csrfValidation(req: Request, csrfToken: string) {
return req.session.csrfSecret === csrfToken;
}
14 changes: 7 additions & 7 deletions app/views/demandeRecruteur.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<div class="tab-content mb-2">
<div class="tab-pane fade show active" id="select-tab">
<h5 class="mb-4">Choisissez l'entreprise pour laquelle vous demandez à être recruteur</h5>
<form class="needs-validation" action="/recruiter" method="POST">
<form class="needs-validation" action="/devenir-recruteur" method="POST">
<div class="form-group row">
<div class="input-group col-sm-8">
<select class="form-control" id="siren" name="siren">
Expand All @@ -27,17 +27,17 @@
</form>
</div>
<div class="tab-pane fade" id="input-tab">
<form class="needs-validation" action="/recruiter" method="POST">
<form class="needs-validation" action="/devenir-recruteur" method="POST">
<div class="mb-3">
<label for="nom" class="form-label">Nom de l'entreprise</label>
<input type="text" class="form-control" id="nom" name="nom" placeholder="Nom">
<label for="nom" class="form-label">Nom de l'entreprise</label>
<input type="text" class="form-control" id="nom" name="nom" placeholder="Nom">
</div>
<div class="mb-3">
<label for="siren" class="form-label">Siren</label>
<input type="text" class="form-control" id="siren" name="siren" placeholder="983434688" maxlength="9">
<label for="siren" class="form-label">Siren</label>
<input type="text" class="form-control" id="siren" name="siren" placeholder="983434688" maxlength="9">
</div>
<div class="mb-3">
<label for="type" class="form-label">Type</label>
<label for="type" class="form-label">Type</label>
<input type="text" class="form-control" id="type" name="type" placeholder="SARL">
</div>
<div class="mb-3">
Expand Down
1 change: 1 addition & 0 deletions app/views/offre/creation.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<div class="card-body">
<h4 class="mb-3">OffreDePoste</h4>
<form class="needs-validation" novalidate="" action="/offre/creation" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<div class="row g-3">
<!-- Select fiche de poste from the ficheDePostes variables-->
<div>
Expand Down
108 changes: 108 additions & 0 deletions securite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Rapport de sécurité - Protection contre les injections SQL

## Introduction

Ce rapport vise à expliquer comment notre application Express.js se protège contre les attaques d'injections SQL lors des requêtes à la base de données. L'injection SQL est une technique couramment utilisée par les attaquants pour manipuler ou compromettre une application en injectant du code SQL non autorisé. Pour prévenir de telles attaques, nous avons mis en place des mesures de sécurité appropriées dans notre code.

## Utilisation de placeholders pour les paramètres

Dans notre code, nous utilisons des placeholders sous la forme de points d'interrogation (`?`) pour les paramètres de requête. Ces placeholders permettent de remplacer les valeurs des paramètres par des valeurs réelles lors de l'exécution de la requête, évitant ainsi les injections SQL.

Voici un extrait de code qui illustre l'utilisation des placeholders :

```javascript
let params: (string | number | undefined)[] = [];
if (filterOffer) {
query += ` WHERE FicheDePoste.salaire >= ?`;
params.push(filterOffer.minSalary);
if (filterOffer.region) {
query += ` AND FicheDePoste.lieu = ?'`;
params.push(filterOffer.region);
}
}
return new Promise<[OffreDePoste]>(
(resolve, reject) =>
pool.query(query, params, (err, result) => {
// Gestion des résultats de la requête
})
);
```

Dans cet exemple, nous construisons dynamiquement notre requête SQL en ajoutant des conditions basées sur les paramètres fournis. Les valeurs des paramètres sont stockées dans un tableau `params`, et lors de l'exécution de la requête, ces valeurs sont remplacées par les placeholders correspondants. Ainsi, les paramètres ne sont pas interprétés comme du code SQL potentiellement malveillant.

## Avantages de l'utilisation de placeholders

L'utilisation de placeholders présente plusieurs avantages en termes de sécurité :

1. **Prévention des injections SQL**: En utilisant des placeholders, nous séparons clairement les instructions SQL des données utilisateur. Cela empêche les attaquants d'injecter du code SQL malveillant en manipulant les données fournies.
2. **Protection contre les caractères spéciaux**: Les placeholders assurent l'échappement automatique des caractères spéciaux présents dans les valeurs des paramètres. Cela garantit que ces caractères sont traités comme des données normales et non comme du code SQL potentiellement dangereux.
3. **Facilité d'utilisation**: L'utilisation de placeholders simplifie la construction des requêtes SQL, en évitant la concaténation manuelle des valeurs des paramètres dans la chaîne de requête. Cela réduit les erreurs potentielles et facilite la maintenance du code.

# Protection contre les attaques CSRF (Cross-Site Request Forgery)

En plus de la protection contre les injections SQL, notre application Express.js met également en place des mesures de sécurité pour se prémunir contre les attaques CSRF (Cross-Site Request Forgery). Les attaques CSRF exploitent la confiance d'un utilisateur authentifié pour effectuer des actions non autorisées à son insu.

## Génération d'un token CSRF unique

Lorsqu'un utilisateur authentifié interagit avec notre application, nous générons un token CSRF unique et l'associons à sa session. Ce token est créé à l'aide d'un middleware qui s'exécute avant chaque requête. Voici un extrait de code illustrant cette fonctionnalité :

```javascript
export function createCSRFToken(req: any, res: any, next: any) {
if (loggedInNoRedirection(req, res)) {
if (req.session.csrfSecret === undefined) {
req.session.csrfSecret = randomBytes(64).toString("hex");
console.log("2. in createCSRFToken req.sessionID: ", req.sessionID);
}
}
next();
}
```

Ce middleware vérifie si l'utilisateur est authentifié et, le cas échéant, génère un token CSRF unique en utilisant la fonction `randomBytes` pour créer une chaîne aléatoire de 64 octets. Ce token est stocké dans la session de l'utilisateur.

## Utilisation du token CSRF dans les formulaires

Lors du rendu d'un formulaire, nous ajoutons le token CSRF généré dans un champ caché du formulaire. Cela permet de "signer" le formulaire avec le token CSRF associé à la session de l'utilisateur. Voici un exemple de code HTML montrant l'utilisation du token CSRF dans un formulaire :

```html
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
```

Le token CSRF est récupéré depuis la session et inséré dans le champ caché du formulaire à l'aide d'un moteur de template (dans cet exemple, `<%= csrfToken %>` représente l'insertion du token CSRF dans le HTML).

## Vérification du token CSRF lors de la soumission d'un formulaire

Lorsqu'un utilisateur soumet un formulaire, le contrôleur correspondant à cette action vérifie que le token CSRF du formulaire correspond à celui stocké dans la session de l'utilisateur. Cela garantit que le formulaire a été généré par notre serveur et n'a pas été forgé par un attaquant. Voici un extrait de code illustrant cette vérification :

```javascript
let csrfToken = req.body._csrf;
if (!csrfValidation(req, csrfToken)) {
alerts.push(new Alert("danger", "Erreur CSRF"));
//TODO message d'erreur
return res.redirect("/logout");
}
```

Le token CSRF soumis avec le formulaire est extrait de la requête (`req.body._csrf`) et comparé au token stocké dans la session. Si les tokens ne correspondent pas, une erreur CSRF est générée et l'utilisateur est déconnecté par mesure de sécurité.

# Rapport de sécurité - Protection contre les attaques XSS

## Introduction

Les mesures de sécurité mises en place dans notre application afin de prévenir les attaques XSS (Cross-Site Scripting). L'attaque XSS est une technique couramment utilisée par les attaquants pour injecter du code JavaScript malveillant dans les pages web, qui est ensuite exécuté par les navigateurs des utilisateurs.

## Protection de la base de données

Une des mesures de sécurité que nous avons mises en place consiste à échapper automatiquement tous les caractères lorsqu'ils sont stockés dans la base de données. L'échappement des caractères implique la conversion de certains caractères spéciaux en leur équivalent sûr, empêchant ainsi leur interprétation en tant que code exécutable. Lorsqu'un utilisateur soumet des données, telles que des commentaires, elles sont traitées et échappées avant d'être stockées dans la base de données.

## Avantages de l'échappement automatique en base de données

L'échappement automatique des caractères en base de données présente plusieurs avantages en termes de sécurité :

1. **Prévention des attaques XSS**: En échappant automatiquement tous les caractères lorsqu'ils sont stockés en base de données, nous nous assurons que le code JavaScript malveillant ne sera pas exécuté lors de l'affichage des données sur notre site. Les caractères spéciaux sont convertis en leur forme sécurisée, évitant ainsi l'injection de scripts malveillants.
2. **Protection contre les erreurs humaines**: L'échappement automatique en base de données réduit les risques d'erreurs humaines lors de l'affichage des données. Même si une étape de filtrage ou d'encodage est omise lors de l'affichage, les caractères échappés en base de données garantissent que le code potentiellement dangereux ne sera pas exécuté.
3. **Uniformité de la sécurité**: En échappant les caractères au niveau de la base de données, nous appliquons une mesure de sécurité uniforme à toutes les données stockées. Cela garantit que toutes les données, même celles qui ont été stockées antérieurement, sont protégées contre les attaques XSS lors de leur affichage.

## Conclusion

Grâce à l'échappement automatique des caractères en base de données, notre application prévient efficacement les attaques XSS en empêchant l'exécution de code JavaScript malveillant lors de l'affichage des données. Cette mesure de sécurité assure l'intégrité et la sécurité de notre application en empêchant les attaquants d'exploiter cette faille de sécurité.