Skip to content
ShiSui97x edited this page Apr 4, 2025 · 12 revisions

layout: default title: "TP6 : Sécurisation du service web public" parent: Index des TPs nav_exclude: false has_children: false nav_order: 6 modifié le: 2024-03-14

TP6 : Sécurisation du service web public

2. Sécurisation serveur

Documentez et faites le point ici sur l'état de sécurisation de votre serveur.

2.1 Sécurisation du VPS

  • Mises à jour régulières :

    • Le serveur est maintenu à jour avec les dernières mises à jour de sécurité via sudo apt update.
  • Désactivation des services inutiles :

    • Un audit des services actifs a été effectué avec sudo systemctl list-units --type=service --state=running. Puis via sudo systemctl disable <nom_service>, pour empêcher le service de se lancer au prochain démarrage et un sudo systemctl stop <nom_service>, pour arrêter le service si en cours d'utilisation, les services ci-dessous ont été désactivés:

    Remarque : Le service qemu-guest-agent.service n'a pas été désactivé car on ne sait pas si le VPS est une VM ou non et donc ne pas causer de problème.

  • Contrôle des ports ouverts :

    • Les ports ouverts ont été vérifiés avec sudo netstat -tulnp, sudo ss -tulnp et sudo nmap -sS -p- <IP_VPS>.

    Uniquement les ports destinés aux TPs ont été ouverts, les autres sont fermés.

De plus fail2ban a également été configuré, voici un extrait du fichier jail.local :

[sshd]
enabled = true
port = x
filter = sshd
maxretry = 3
findtime = 5m
bantime = 30m
bantime.increment = true
bantime.factor = 2
bantime.rndelay = 5m
bantime.maxtime = 72h

En addition, dans le fichier sshd_config, les paramètres de sécurité ont aussi été configurés, notamment PermitRootLogin no et PasswordAuthentication no. On également ajouté les paramètre AllowUsers <nom_utilisateur> afin de permettre l'accès uniquement aux utilisateurs autorisés via leur clé SSH comme seul moyen de connexion au serveur.

2.2 Sécurisation de Docker Engine

  • Vérification de la version installée :
    • La version de Docker est vérifiée via docker --version et maintenue à jour.
  • Sécurisation des paramètres de Docker :
    • Les paramètres de sécurité sont contrôlés avec sudo docker info | grep Security.
    • Restriction des accès à l’API Docker via /etc/docker/daemon.json :
      {
        "icc": false,
        "userns-remap": "default"
      }
    • Redémarrage du service Docker : sudo systemctl restart docker

2.3 Sécurisation des containers

  • Contrôle des images utilisées :
    • Seules des images officielles et sans vulnérabilités sont utilisées (contrôlées via docker scan <nom_image>).
    • Le tag latest est évité afin d’assurer la stabilité.
  • Vérification des ports exposés :
    • Commande docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}" exécutée pour vérifier que seuls les ports nécessaires sont exposés.
    • Un audit complémentaire a été réalisé avec sudo netstat -tulnp | grep docker et sudo ss -tulnp | grep docker.
    • Seul le port 80 est publié pour Nginx, la base de données et PHP restent en réseau interne.

2.4 Sécurisation des services dans les containers

  • Nginx :
    • Désactivation des versions TLS faibles :
      ssl_protocols TLSv1.2 TLSv1.3;
    • Masquage des informations sensibles :
      server_tokens off;
  • Base de données :
    • Aucune exposition externe du service MySQL/MariaDB.
    • Utilisation d’un utilisateur limité pour les connexions.

3. Sécurisation des données

3.1. Isolation de la base de données

  • Documentez ici les modifications effectuées sur votre infrastructure pour isoler la base de données.

Le fichier compose.yml a été édité de sorte à :
(1) créer deux réseaux distincts "dmz_net et db_net",
(2) à connecter le serveur web au réseau dmz_net,
(3) à connecter le serveur php au réseau dmz_net et db_net et
(4) à connecter la base de données au réseau db_net.
image

  • Etablissez une procédure de validation de l'isolation de la base de données.

Voici un petit rappel des adresses IP des différents services :
image

Un ping depuis le serveur web vers le serveur php n'aboutit pas :
image

Un ping entre le serveur php et la base de données fonctionne :
image

Un ping entre le serveur web et la base de données échoue également :
image

3.2. Configuration d'un utilisateur non privilégié

  • Documentez les modifications effectuées

Dans notre fichier woodytoys.sql, nous rajouter les lignes suivantes en tête du fichier :

CREATE USER IF NOT EXISTS '${MYSQL_USER}'@'%' IDENTIFIED BY '${MYSQL_PASSWORD}';

GRANT SELECT ON woodytoys.* TO '${MYSQL_USER}'@'%'; 

FLUSH PRIVILEGES; -- Pour appliquer les droits

PS: À noter ici que le fichier sql ne lit pas le fichier .env, il faut donc le modifier manuellement.

  • Proposez une procédure de validation du fait que le serveur web ne peut pas effectuer d'opération dangereuse sur la DB.

3.3. Backup de la DB

  • Documentez la procédure de backup de votre base de données.
  • Si vous avez réalisé le bonus proposé, documentez-le et prouvez son fonctionnement.

Pour le backup plus le plus réaliste que possible, un script backup_db.sh a été ajouté au container mariadb, dans le dossier backups monté sur /backups. Puis un cron a été ajouté au container mariadb pour faire le backup tous les jours à 00h00.

backup_db.sh :

BACKUP_DIR="/backups"
DB_USER="root"
DB_PASS="mypass"
DATE=$(date +\%Y\%m\%d)
BACKUP_FILE="$BACKUP_DIR/all_databases_$DATE.sql.gz"
RETENTION_DAYS=3 

mkdir -p "$BACKUP_DIR"

echo "Tentative de dump à $(date)" >> "$BACKUP_DIR"/backup.log 2>&1

/usr/bin/mariadb-dump --all-databases -u$DB_USER -p$DB_PASS | gzip > "$BACKUP_FILE"

echo "Dump terminé à $(date)" >> "$BACKUP_DIR"/backup.log 2>&1

find "$BACKUP_DIR" -type f -name "all_databases_*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup terminé : $BACKUP_FILE"

Cron job :

0 0 * * * /bin/bash /backups/backup_db.sh

Voici la confirmation de la création du backup :

Backup

3.4. Logs de la DB

  • Documentez brièvement comment accéder aux logs de la DB.

4. Sécurisation des communications avec HTTPS

4.1. HTTPS via un certificat auto-signé

4.1.1. Génération du certificat auto-signé avec OpenSSL

  • Montrez par un screenshot la mise en place de votre certificat auto-signé.

Que pense votre navigateur du certificat utilisé ? Pourquoi ? Expliquez la problématique qui est ici mise en évidence.

Le navigateur affiche un avertissement, car le certificat est auto-signé et pas encore validé par une autorité de certification de confiance.

Avertissement

4.1.2. Configuration de Nginx en HTTPS pour le virtualhost www

  • Documentez la configuration en HTTPS de nginx. Montrez via des screenshots bien choisi qu'elle est fonctionnelle.

Une fois la clé et le certificat genéré et auto-signé, le fichier nginx.conf a ete modifié de sorte que le site www soit accessible via HTTPS. Dans un premier temps on redirige le tarffic du port 80 sur le port 443 comme ci-dessous :

server { 
        listen 80; 
        server_name www.l1-7.ephec-ti.be.; 
        return 301 https://$host$request_uri; 
    }

L'hôte écoute désormais sur le port 443, puis nous utilisons la directive ssl_certificate pour indiquer le chemin vers le certificat et la directive ssl_certificate_key pour indiquer le chemin vers la clé.

    server {
        listen          443 ssl;
        server_name     www.l1-7.ephec-ti.be;
        ...
        ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
        ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
        ...
    }
Site accessible via HTTPS

4.2. Obtention d'un certificat Let's Encrypt pour le site www

Documentez et prouvez la mise en place du certificat let's Encrypt sur vos virtuals hosts.

Avant tout, directement dans le conteneur web, nous avons utilisé la commande certbot --nginx -d www.l1-7.ephec-ti.be pour genérer la clé et le certificat.

  1. Examinez les logs dans le fichier /var/log/letsencrypt/letsencrypt.log
  • Trouvez les trois challenges ACME proposés par let's encrypt, et le token utilisé

Challenges ACME et token utilisé :

{
  "identifier": {
    "type": "dns",
    "value": "www.l1-7.ephec-ti.be"
  },
  "status": "pending",
  "expires": "2025-04-01T10:36:23Z",
  "challenges": [
    {
      "type": "http-01",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall/2301878056/495179551606/rFD9UA",
      "status": "pending",
      "token": "kGbbds3-Adq88JZ_rI0x3KN_rkpGiC_h5Ghzr0APliQ"
    },
    {
      "type": "dns-01",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall/2301878056/495179551606/_NQ8zA",
      "status": "pending",
      "token": "kGbbds3-Adq88JZ_rI0x3KN_rkpGiC_h5Ghzr0APliQ"
    },
    {
      "type": "tls-alpn-01",
      "url": "https://acme-v02.api.letsencrypt.org/acme/chall/2301878056/495179551606/rOtykQ",
      "status": "pending",
      "token": "kGbbds3-Adq88JZ_rI0x3KN_rkpGiC_h5Ghzr0APliQ"
    }
  ]
}
  • Trouvez la configuration nginx temporaire utilisée par certbot pour répondre au challenge

...

  • Quelle est l'URL où se trouve le token sur votre serveur nginx?

  • Voyez-vous le certificat reçu? De combien de parties se compose-t-il?

Le certificat se compose de 2 parties.

-----BEGIN CERTIFICATE-----
MIID
.....
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIE
...
-----END CERTIFICATE-----
  • Où sont stockés les fichiers du certificat et de la clé privée générées par certbot?

Ils sont stockés respectivement aux emplacements /etc/letsencrypt/live/www.l1-7.ephec-ti.be/fullchain.pem et /etc/letsencrypt/live/www.l1-7.ephec-ti.be/privkey.pem

  1. Vérifiez votre configuration nginx: qu'est ce qui a changé?

Les valeurs des champs ssl_certificate et ssl_certificate_key ont été remplacé par /etc/letsencrypt/live/www.l1-7.ephec-ti.be/fullchain.pem et /etc/letsencrypt/live/www.l1-7.ephec-ti.be/privkey.pem.

  1. Vérifiez si votre site web possède à présent un certificat signé par Let's Encrypt et s'il est accepté par votre navigateur

Le certificat est signé par Let's Encrypt et est accepté par le navigateur.

Certificat signé par Let's Encrypt
  1. Examinez le certificat reçu avec l'outil OpenSSL, et identifiez les champs indiquant la signature du CA.

Dans le certificat, nous pouvons voir les champs suivants :

Issuer: C = US, O = Let's Encrypt, CN = E5
Validity
    Not Before: Mar 25 09:38:00 2025 GMT
    Not After : Jun 23 09:37:59 2025 GMT
Subject: CN = www.l1-7.ephec-ti.be
Subject Public Key Info:
....

Signature Value:
...
  1. Vérifiez également le statut HTTPS du second Virtual Host : blog.lx-y.ephec-ti.be. Que se passe-t-il? Comment pouvez-vous corriger ça ?

En essyant d'accéder à https://blog.l1-7.ephec-ti.be, le site redirige automatiquement vers https://www.l1-7.ephec-ti.be.

Il faut alors utiliser certbot pour obtenir un certificat pour les deux domaines et changer la configuration de nginx.conf pour que l'hôte écoute sur le port 443.

4.3. Obtention manuelle d'un certificat pour le domaine

Si vous avez réalisé l'obtention d'un certificat wildcard, documentez la procédure et prouvez qu'elle fonctionne sur votre domaine (par ex. via des screenshots).

Dans le conteneur web, on a rentré la commande certbot certonly --manual --preferred-challenges=dns --email <mail> --agree-tos -d \*.l1-7.ephec-ti.be

Suite à celà, on a reçu le _acme-challenge.l1-7.ephec-ti.be avec un token également puis nous avons ensuite placé dans le ficher zone l1-7.zone du dns la ligne suivante : _acme-challenge.l1-7.ephec-ti.be IN TXT token.

On a ensuite changé la configuration de nginx.conf en rajoutant ces deux lignes dnas le bloc http :

    http {
        ...

        ssl_certificate /etc/letsencrypt/live/l1-7.ephec-ti.be/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/l1-7.ephec-ti.be/privkey.pem; # managed by Certbot

        server{
          ...
        }

        server{
          ...
        }
    }

En vérifiant le certificat sur notre navigateur, on peut voir que maintenant le certificat a désormais un wilcards, qu'il est signé par Let's Encrypt et que c'est tout le domaine qui profite du certificat.

Certificat wildcard Certificat wildcard

Clone this wiki locally