Browse Source

bootstrap

master
Francois Lesueur 1 year ago
parent
commit
2d38bac294
  1. 9
      README.md
  2. 70
      cours-authcrypto.md
  3. 117
      td-crypto.md
  4. 131
      td-passwords-files/skeleton.py
  5. 250
      td-passwords-files/toolbox.py
  6. 102
      td-passwords.md
  7. 37
      tp-bd-files/docker-compose.yml
  8. 11
      tp-bd-files/sqlinit/sqlinit.sql
  9. 3
      tp-bd-files/webapp/Dockerfile
  10. 13
      tp-bd-files/webapp/README.txt
  11. 19
      tp-bd-files/webapp/admin/do_login.php
  12. 32
      tp-bd-files/webapp/admin/do_support.php
  13. 17
      tp-bd-files/webapp/admin/index.php
  14. 45
      tp-bd-files/webapp/admin/print_clients.php
  15. 19
      tp-bd-files/webapp/clients/do_login.php
  16. 19
      tp-bd-files/webapp/clients/do_message.php
  17. 17
      tp-bd-files/webapp/clients/index.php
  18. 37
      tp-bd-files/webapp/clients/print_clients.php
  19. 9
      tp-bd-files/webapp/db.inc.php
  20. 24
      tp-bd-files/webapp/helpdesk/do_helpdesk.php
  21. 18
      tp-bd-files/webapp/helpdesk/do_login.php
  22. 17
      tp-bd-files/webapp/helpdesk/index.php
  23. 33
      tp-bd-files/webapp/helpdesk/print_clients.php
  24. 147
      tp-bd.md
  25. 171
      tp-https.md
  26. 92
      tp-ldap.md

9
README.md

@ -1,3 +1,10 @@
# LPDLIS
Authentification et sécurité des données pour les LP DLIS - IUT Vannes
Authentification et sécurité des données pour les LP DLIS - IUT Vannes
* [Cours Authentification forte / Cryptographie (3h)](cours-authcrypto.md)
* [TD crypto asymétrique (1,5h)](td-crypto.md)
* [Passwords (1,5h)](td-passwords.md)
* [TP HTTPS / Authentification mutuelle (4,5h)](tp-https.md)
* [TP Sécurité des BD (12h)](tp-bd.md)
* [LDAP (10,5h)](tp-ldap.md)

70
cours-authcrypto.md

@ -0,0 +1,70 @@
# Cours Authentification forte - Cryptographie (3h)
Mode d'emploi
=============
Déroulement du cours en "autonomie encadrée" :
* Vous avez un travail en autonomie décrit dans la suite de ce document. Ce document vous oriente vers des références externes (à lire, évidemment) et tisse les liens entre ces lectures.
* Durant tout ce travail, je suis disponible autant pour des éclaircissements que des approfondissements.
* Je vous encourage, évidemment, à échanger aussi en petits groupes via le canal de votre choix
* Les durées sont données à titre indicatif et seront très variables en fonction de vos questions.
Ce que nous allons voir :
* Le principe de l'authentification, l'authentification multi-facteurs et l'authentification forte
* La suite se concentrera sur l'authentification forte par certificat numérique
* Le fonctionnement de la crypto, les classes de crypto, les clés, ... comme outil de base
* Les infrastructures à clés publiques pour lier la crypto à des identités
* Le protocole TLS comme empaquetage de cet ensemble
L'authentification forte (45 minutes)
========================
En informatique, on parle de :
* Identification : association d'un identifiant à un utilisateur
* Authentification : contrôle de cette association (c'est ici que l'on parle de vérification de cet identifiant). Il existe plusieurs méthodes classiques pour cela, décrites [ici](https://fr.wikipedia.org/wiki/Authentification)
L'authentification est couramment un point faible de la sécurité des systèmes. De nombreuses attaques tentent d'usurper des comptes, soit via du phishing pour obtenir des authentifiants valides soit via des attaques directes. Intuitivement, ce mécanisme étant le point d'entrée dans le système et un de ses gardiens essentiels, il est évidemment critique, d'autant plus pour l'authentification des comptes à privilèges (administrateurs).
Afin de rendre l'authentification plus robuste, les authentifications multi-facteurs ou fortes permettent de réduire les risques. Une petite lecture [ici](https://fr.wikipedia.org/wiki/Authentification_forte). L'ANSSI fait la distinction entre les 2 (et apporte en tous cas un élément de réflexion et de choix) dans la [Section 2.5 du guide de l'authentification](https://www.ssi.gouv.fr/uploads/2021/10/anssi-guide-authentification_multifacteur_et_mots_de_passe.pdf#section.2.5).
Pour la suite, nous explorerons le certificat numérique comme facteur cryptographique.
Bases de la crypto (1h)
==================
Vous devez lire le cours de [Ghislaine Labouret](https://web.archive.org/web/20170516210655/http://www.hsc.fr/ressources/cours/crypto/crypto.pdf) <!-- http://www.hsc.fr/ressources/cours/crypto/crypto.pdf https://doc.lagout.org/security/Cryptographie%20.%20Algorithmes%20.%20Steganographie/HSC%20-%20Introduction%20a%20la%20cryptographie.pdf --> (jusqu'à la page 27/32). Vous y verrez les notions de cryptographie symétrique (ex AES), asymétrique (ex RSA), hash, chiffrement, signature ainsi que le problème de la distribution des clés. Ce cours est intéressant car bien construit mais assez ancien (2001). Les notions, principes et difficultés n'ont pas changé depuis, les algorithmes et tailles de clés si : cela vous donne une idée de l'évolution à attendre pendant les 10 prochaines années (hors découverte majeure).
Côté découvertes, on commence par exemple à s'intéresser à la [cryptographie post-quantique](https://fr.wikipedia.org/wiki/Cryptographie_post-quantique) (contentez-vous de lire l'introduction de l'article) : cette cryptographie post-quantique s'exécute sur des ordinateurs traditionnels mais a l'avantage d'être résistante aux attaques des ordinateurs traditionnels et quantiques (à ne pas confondre avec la crypto quantique, donc, qui s'exécuterait sur un ordinateur quantique). Dans votre carrière, vous devriez probablement voir la systématisation de cette cryptographie post-quantique.
À vous de chercher quels sont les algorithmes souhaitables aujourd'hui. Pour les tailles de clés, consultez le site [Key Length](http://www.keylength.com/) qui est très pratique.
<!--
La suite du travail est de découvrir le fonctionnement de RSA (sans entrer, pour l'instant, dans les fondements mathématiques), par exemple sur [Wikipedia](https://fr.wikipedia.org/wiki/Chiffrement_RSA). Prêtez une attention particulière à la génération des clés, aux mécanismes de chiffrement et déchiffrement.
-->
Enfin, le programme [Bullrun](https://fr.wikipedia.org/wiki/Bullrun) donne un bon aperçu des forces et faiblesses de la cryptographie moderne : la partie mathématique est plutôt sûre, les attaques se concentrent sur l'usage (standardisation), le déploiement, l'implémentation, etc.
Infrastructures à clés publiques (PKI) (1h)
=======================================
Le rôle d'une PKI est de lier une clé publique à une identité (typiquement, à une chaîne de caractères intelligible comme une URL `www.acme.org` ou une adresse mail `brice@acme.org`). L'obtention de clés publiques est un service orthogonal au service de sécurité rendu par la cryptographie (ie, un même service, le mail chiffré et signé par exemple, peut-être rendu avec une approche type CA avec S/MIME ou une approche toile de confiance avec PGP).
Vous devez lire la [page anglaise de Wikipedia](https://en.wikipedia.org/wiki/Public_key_infrastructure) sur ce sujet, qui présente différentes formes de PKI (autorités de certifications, toile de confiance, SPKI, blockchain). Attention, la page française n'est pas assez détaillée.<!-- très différente et présente une vision réduites à l'approche CA, c'est uniquement la page anglaise qui fait référence pour ce cours. -->
Vous devez détailler chacune des différentes formes, avec une attention particulière pour les [CA](https://en.wikipedia.org/wiki/Certificate_authority) et le [Web of trust](https://en.wikipedia.org/wiki/Web_of_trust). La PKI DANE/TLSA est très bien décrite et positionnée dans cet [article](http://www.bortzmeyer.org/6698.html). Vous devez enfin lire les [Criticisms](https://en.wikipedia.org/wiki/Public_key_infrastructure#Criticism) de la page principale (et les détails de PKI security issues with X.509, Breach of Comodo CA, Breach of Diginotar).
> Pour comprendre DANE/TLSA qui repose sur DNSSEC, vous devrez peut-être vous rafraichir la mémoire sur le fonctionnement et les différents acteurs du système DNS (typiquement, notions de _registry_, _registrar_, gestion d'une zone et mécanisme de résolution récursif). Ces points ont normalement déjà été vus en TC mais vous pouvez par exemple lire [Sebsauvage](http://sebsauvage.net/comprendre/dns/) jusque "Dans ce cas, ils sont à la fois registry et registrar.", [Bortzmeyer](http://www.bortzmeyer.org/files/cours-dns-cnam-PRINT.pdf) sections "Le protocole DNS" et "Gouvernance" et/ou d'autres ressources équivalentes.
TLS (15 minutes)
===
Dans le TP, nous allons manipuler une CA pour faire du HTTPS (HTTP sur TLS). TLS permet l'authentification mutuelle, une bonne explication [ici](https://tls.ulfheim.net/)
![xkcd](https://imgs.xkcd.com/comics/security.png)

117
td-crypto.md

@ -0,0 +1,117 @@
TD2 : Usage de la cryptographie asymétrique
=============================================
_Pas de compte-rendu pour ce TD_
Ce TD présente et applique les notions de cryptographie asymétrique :
* Génération de clés RSA
* Distribution de clés
* Signature et chiffrement RSA
Le cryptosystème que nous allons utiliser ici est basé sur la fonction RSA. Le cryptosystème proposé est simple et présente donc certaines vulnérabilités mais illustre le fonctionnement.
Génération de clés RSA
======================
Nous allons commencer par générer une paire de clés RSA pour chacun. Voici l'algorithme simplifié de génération de clés RSA (en réalité, d'autres tests doivent être réalisés) :
* Choisir deux nombres premiers _p_ et _q_ ([exemples de premiers](https://fr.wikipedia.org/wiki/Liste_de_nombres_premiers))
* Calculer _n = p * q_ (__Attention, pour que la suite du TD fonctionne, n doit être supérieur à 1000 !__)
* Calculer _&phi;(n) = (p-1)(q-1)_
* Choisir _e_ tel que :
* _1 < e < &phi;(n)_
* _pgcd(e, &phi;(n)) = 1_
* Par exemple, un premier qui ne divise pas &phi;(n)
* Déterminer l'inverse modulaire _d &equiv; e<sup>-1</sup> mod &phi;(n)_. Vous pouvez utiliser [DCODE](https://www.dcode.fr/inverse-modulaire) pour cela (attention, pas le `pow` Python pour ça, sauf si vous êtes *certain* d'avoir une version de python supérieure ou égale à 3.8 !) <!-- Vous pouvez utiliser [Wolfram Alpha](http://www.wolframalpha.com), avec une requête de la forme `7 ^ -1 mod 1147` (attention, pas le `pow` Python pour ça !) -->
* La clé publique est _(e,n)_ et la clé privée est _(d,n)_
Gardez votre clé privée secrète et transmettez votre clé publique avec votre nom à l'enseignant, sur un papier. Elle sera inscrite au tableau (la "PKI").
Les exemples dans la suite du sujet sont réalisés avec p=31, q=37, n=1147, &phi;(n)=1080, e=7, d=463. La clé publique est _(e,n)_, ici _(7,1147)_, et la clé privée est _(d,n)_, ici _(463,1147)_.
<!-- Code Python pour calculer _a<sup>-1</sup> mod b_ : `modinv(a,b)` disponible [ici](modinv.py) -->
Rappel : la propriété utilisée est que pour tout message _m, m<sup>de</sup>[n] = m_.
Chiffrement et déchiffrement
============================
Description
-----------
Nous allons chiffrer des chaînes de caractères. Pour cela, chaque lettre est remplacée par son rang dans l'alphabet, sur 2 chiffres :
|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z|_|
|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|
Par exemple, "crypto" devient `03 18 25 16 20 15`
Ensuite, afin de ne pas retomber dans un chiffrement par substitution simple, les chiffres sont assemblés par blocs de 3 (complété éventuellement de 0 à la fin), ainsi `03 18 25 16 20 15` devient `031 825 162 015`.
Enfin, chaque bloc clair de 3 chiffres est chiffré indépendamment par la fonction RSA : bloc<sub>chiffré</sub> = bloc<sub>clair</sub><sup>e</sup>[n]. Attention, _(e,n)_ représente une clé publique, mais celle de qui ? L'utilisation de la clé _(7,1147)_ donne le chiffré `1116 751 245 1108`.
> Pour calculer les exponentiations modulaires, vous pouvez utiliser python (dans l'interpréteur, tapez `pow(a,b,c)` pour obtenir a<sup>b</sup>[c]) ou [DCODE](https://www.dcode.fr/exponentiation-modulaire)<!--[Wolfram Alpha](http://www.wolframalpha.com)-->. Attention, lors des calculs, n'écrivez pas de '0' en début d'entier. Par exemple, pour le bloc clair `031`, tapez `pow(31,7,1147)`. Commencer un entier par '0' le fait interpréter comme un nombre encodé en _octal_ (même principe qu'un nombre commençant par '0x' qui est interprété comme un hexadécimal).
Le déchiffrement est opéré de manière analogue, en utilisant la clé privée au lieu de la clé publique. Chaque bloc clair est réobtenu à partir du bloc chiffré par le calcul : bloc<sub>clair</sub> = bloc<sub>chiffré</sub><sup>d</sup>[n]
Mise en pratique
----------------
Vous allez maintenant transmettre un message chiffré à un étudiant éloigné (en vous déplaçant). Le chiffrement assure la _confidentialité_ du message transmis. Imaginez qu'il serait normalement transmis par des routeurs intermédiaires, qui auraient pu être vos collègues, mais nous privilégions ici une transmission directe en raison des règles sanitaires.
1. **Envoi de votre message** : Chiffrez un message de votre choix avec le cryptosystème proposé. Inscrivez sur un papier votre identité, le message chiffré et le destinataire. Envoyez-le !
2. **Réception d'un message** : À la réception d'un message, appliquez l'algorithme de déchiffrement. Quelqu'un d'autre sur la route du message pouvait-il obtenir le clair de ce message ?
<!-- 2. **Routage des autres messages** : Que fait un routeur ? Il lit un message, l'analyse, décide où l'envoyer puis le reproduit. De manière analogue, vous allez pour chaque saut retransmettre le message entrant mais vous pouvez le lire avant de le retransmettre. Pouvez-vous en déduire des informations ? -->
Signature et vérification
=========================
Description
-----------
Nous allons signer des chaînes de caractères. Pour cela, chaque lettre est remplacée par son rang dans l'alphabet. Pour un message _m = (m<sub>0</sub>, ..., m<sub>i</sub>)_ avec _(m<sub>0</sub>, ..., m<sub>i</sub>)_ les rangs de chaque lettre (attention, on ne fait plus des blocs de 3 chiffres ici), le haché _h(m)_ est calculé par l'algorithme suivant :
h = 2;
for (j=0; j<i; j++) {
h = h * 2;
h = h + m[j];
}
return h%1000;
La valeur de la signature vaut alors _h(m)<sup>d</sup>[n]_. Attention, _(d,n)_ représente une clé privée, mais celle de qui ? Le haché de "crypto" vaut par exemple 831 et la signature par _(463,1147)_ est 335.
Le message est alors envoyé accompagné de sa signature. La vérification d'un message reçu _m_ signé avec _sig_ est opérée de la manière suivante :
* Calculer _h(m)_ par rapport au _m_ reçu
* Calculer _sig<sup>e</sup>[n]_
* Vérifier que _h(m) == sig<sup>e</sup>[n]_ sur le message reçu
Mise en pratique
----------------
Vous allez maintenant transmettre un message clair (non chiffré) signé à un étudiant éloigné. La signature permet de vérifier l'_intégrité_ du message transmis. Imaginez qu'il serait normalement transmis par des routeurs intermédiaires, qui auraient pu être vos collègues, mais nous privilégions ici une transmission directe en raison des règles sanitaires.
1. **Envoi de votre message** : Signez un message de votre choix avec le cryptosystème proposé. Inscrivez sur un papier votre identité, le message clair, la signature et le destinataire. Envoyez-le !
2. **Réception d'un message** : À la réception d'un message, appliquez l'algorithme de vérification de la signature. Le message reçu est-il intègre ? Si non, quelle attaque avez-vous détectée ? Un attaquant pouvait-il l'altérer en chemin ?
<!-- 2. **Routage des autres messages** : Utilisez le même protocole multi-saut que précédemment. Pour chaque saut, recopiez le message entrant sur un autre papier puis retransmettez ce second papier. -->
Bonus : Attaques sur le cryptosystème proposé
=====================================
Étudiez et testez quelques attaques sur le système mis en place :
* Modification de message en conservant la validité de la signature
* Attaque de la clé privée (par factorisation de _n_ par exemple)
Toutes ces attaques sont possibles ici. Réfléchissez à leur cause et aux protections mises en place dans les cryptosystèmes réels. Implémentez une (ou plusieurs) attaque dans le langage de votre choix, proposez une contre-mesure et évaluez la complexité rajoutée par votre contre-mesure.
![Don't roll your own crypto](https://image.slidesharecdn.com/signal-publickeycrypto-181018000453/95/signal-practical-cryptography-40-638.jpg?cb=1539821143)

131
td-passwords-files/skeleton.py

@ -0,0 +1,131 @@
#!/usr/bin/env python3
import time
import sys
from toolbox import *
# You should tweak these values during the work
nblogins = 10 # would be larger in real-life
nbpasswords = 1000000 # would be larger in real-life
nbiterations = 10 # 10000 is currently recommended, should be adapted to the usecase and changed with time (improvement of computation power), like a key size
############################################
# Part of the script to edit #
############################################
# Hint : you can call decrypt(key,data) to decrypt data using key
def crackencrypted(database):
key = readfile("enckey")[0]
crackeddb = []
for i in database:
# i[0] is the login, i[1] is the encrypted password
#...
crackeddb.append((i[0],i[1])) # second argument should contain cleartext password
return crackeddb
# Hint : - genshahashes(passwords) returns all the hashes of passwords dictionary
# - getpassfromshahash(hashes, hash) returns the password which hashed as "hash" in hashes
def cracksha(database):
global nbpasswords
passwords = getPassDict(nbpasswords) # passwords contains a dictionary of passwords
#...
crackeddb = []
for i in database:
# i[0] is the login, i[1] is the hashed password
#...
crackeddb.append((i[0],i[1])) # second argument should contain cleartext password
return crackeddb
# Hint : salthash(password, salt) return the salted hash of password
def cracksaltedsha(database):
global nbpasswords
passwords = getPassDict(nbpasswords)
crackeddb = []
for i in database:
# i[0] is the login, i[1] is the hashed password, i[2] is the salt
#...
crackeddb.append((i[0],i[1])) # second argument should contain cleartext password
return crackeddb
# Hint : pbkdf2(password, salt, nbiterations) returns the pbkdf2 of password using salt and nbiterations
def crackpbkdf2(database):
global nbpasswords
passwords = getPassDict(nbpasswords)
crackeddb = []
for i in database:
# i[0] is the login, i[1] is the hashed password, i[2] is the salt, i[3] is the iteration count
#...
crackeddb.append((i[0],i[1])) # second argument should contain cleartext password
return crackeddb
############################################
# Nothing to change after this line ! #
############################################
if __name__ == '__main__':
# When called with init
if len(sys.argv) > 1 and sys.argv[1] == "init":
initworkspace(nblogins,nbpasswords,nbiterations)
print("Workspace initialized in files/ subdirectory")
exit(0)
# Test whether init has been called before
try :
readfile("plain")
except FileNotFoundError:
initworkspace(nblogins,nbpasswords,nbiterations)
print("Workspace initialized in files/ subdirectory")
# test plain DB
print("\n============\nPlain storage:")
plaindb = readfile("plain")
print("Plain DB is : " + str(plaindb))
print("Authenticating with plain DB : " + str(authplain(plaindb[0][0],plaindb[0][1],plaindb)))
#test encrypted db
print("\n============\nEncrypted storage:")
encdb = readfile("enc")
print("Encrypted DB is " + str(encdb))
print("Authenticating with encrypted DB : " + str(authencrypted(plaindb[1][0],plaindb[1][1],encdb)))
start = time.time()
crackedenc = crackencrypted(encdb)
end = time.time()
print("Time to crack encrypted DB : " + str(end-start) + " seconds")
print("Cracked encrypted DB is " + str(crackedenc))
#test SHA db
print("\n============\nSHA storage:")
shadb = readfile("sha")
print("SHA DB is " + str(shadb))
print("Authenticating with SHA DB : " + str(authsha(plaindb[0][0],plaindb[0][1],shadb)))
start = time.time()
crackedsha = cracksha(shadb)
end = time.time()
print("Time to crack SHA DB : " + str(end-start) + " seconds")
print("Cracked SHA DB is " + str(crackedsha))
#test Salted SHA db
print("\n============\nSalted SHA storage:")
saltedshadb = readfile("saltedsha")
print("Salted SHA DB is " + str(saltedshadb))
print("Authenticating with Salted SHA DB : " + str(authsaltedsha(plaindb[0][0],plaindb[0][1],saltedshadb)))
start = time.time()
crackedsaltedsha = cracksaltedsha(saltedshadb)
end = time.time()
print("Time to crack salted SHA DB : " + str(end-start) + " seconds")
print("Cracked salted SHA DB is " + str(crackedsaltedsha))
# test PBKDF2 DB
print("\n============\nPBKDF2 storage:")
pbkdf2db = readfile("pbkdf2")
print("PBKDF2 DB is " + str(pbkdf2db))
print("Authenticating with PBKDF2 DB : " + str(authpbkdf2(plaindb[0][0],plaindb[0][1],pbkdf2db)))
start = time.time()
crackedpbkdf2 = crackpbkdf2(pbkdf2db)
end = time.time()
print("Time to crack PBKDF2 DB : " + str(end-start) + " seconds")
print("Cracked PBKDF2 DB is " + str(crackedpbkdf2))

250
td-passwords-files/toolbox.py

@ -0,0 +1,250 @@
#!/usr/bin/env python3
# Avoir une lib avec des briques prêtes
# Avoir un script qui crée les fichiers de password SHA/PB/etc. pour pouvoir les manipuler en texte
# TD : associer les briques pour évaluer les attaques sur un pass / une base
# mettre un « except ImportError » et ressayer avec « Cryptodome » a la place de « Crypto »
import re
import time
import random
import hashlib
# tweak to (try to) handle different crypto lib naming across systems (Linux, Mac, Win)
try:
from Crypto.Cipher import AES
from Crypto import Random
except ImportError:
try:
from crypto.Cipher import AES
from crypto import Random
except ImportError:
from Cryptodome.Cipher import AES
from Cryptodome import Random
import base64
import os
import urllib.request
import string
# returns an array of a dictionary of passwords
def getPassDict(nbpasswords):
try:
f = open("files/passwords.txt")
except FileNotFoundError:
print("Downloading a passwords list...")
urllib.request.urlretrieve("https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt?raw=true", "files/passwords.txt")
print("Done !")
f = open("files/passwords.txt")
passwords = []
#nbpasswords = 10000
passtogen = nbpasswords
for password in f:
passwords.append(password.strip())
passtogen-=1
if passtogen == 0:
break
return passwords
def genRandomPassword():
length = 6
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for i in range(length))
# reads/writes shadow-style files
def readfile(filename):
f = open("files/"+filename)
res = []
for line in f:
output = line.strip().split(":")
res.append(output)
return res
def writeFile(filename, array):
f = open("files/"+filename,'w')
for line in array:
towrite = ""
for item in line:
towrite+=item + ":"
towrite = towrite[:-1]
f.write(towrite)
f.write('\n')
# Plain storage
def genplain(nblogins,nbpasswords):
passwords = getPassDict(nbpasswords)
logins = []
for i in range(0,nblogins):
login = "user" + str(i)
if (random.randint(0,10) < 4):
logins.append((login,passwords[random.randint(0,len(passwords)-1)]))
else:
logins.append((login,genRandomPassword()))
return logins
def authplain(login, passwd, database):
for i in database:
if i[0] == login:
current = i[1]
return (current == passwd)
# Encrypted storage
def genencrypted(logins):
encdb = []
key = Random.new().read(16)
iv = Random.new().read(AES.block_size)
f = open("files/enckey",'wb')
f.write((base64.b64encode(key)))
f.write(b":")
#f = open("files/enciv",'wb')
f.write((base64.b64encode(iv)))
for i in logins:
cipher = AES.new(key, AES.MODE_CFB, iv)
enc = (base64.b64encode(cipher.encrypt(i[1].encode('utf-8')))).decode("utf-8")
encdb.append((i[0],enc))
#print(enc)
return encdb
def authencrypted(login, passwd, database):
for i in database:
if i[0] == login:
current = i[1]
keyiv = readfile("enckey")[0]
key = base64.b64decode(keyiv[0])
iv = base64.b64decode(keyiv[1])
#key = base64.b64decode(readfile("enckey")[0][0])
#iv = base64.b64decode(readfile("enciv")[0][0])
cipher = AES.new(key, AES.MODE_CFB, iv)
return (passwd == cipher.decrypt(base64.b64decode(current)).decode('utf-8'))
def decrypt(keyiv,data):
key = base64.b64decode(keyiv[0])
iv = base64.b64decode(keyiv[1])
cipher = AES.new(key, AES.MODE_CFB, iv)
return cipher.decrypt(base64.b64decode(data)).decode('utf-8')
# SHA storage
def gensha(logins):
db = []
for i in logins:
csum = hashlib.sha256(i[1].encode('utf-8')).hexdigest()
db.append((i[0],csum))
return db
def authsha(login, passwd, database):
for i in database:
if i[0] == login:
current = i[1]
return (current == hashlib.sha256(passwd.encode('utf-8')).hexdigest())
def genshahashes(passwords):
hashes = []
for passwd in passwords:
hashes.append([hashlib.sha256(passwd.encode('utf-8')).hexdigest(),passwd])
return hashes
def getpassfromshahash(hashes, hash):
for j in hashes:
if j[0] == hash:
return j[1]
return None
# Salted SHA storage
def gensaltedsha(logins):
db = []
for i in logins:
salt = str(random.randint(0,65535))
csum = hashlib.sha256((i[1]+salt).encode('utf-8')).hexdigest()
db.append((i[0],csum,salt))
return db
def authsaltedsha(login, passwd, database):
for i in database:
if i[0] == login:
current = i[1]
salt = i[2]
return (current == hashlib.sha256((passwd+salt).encode('utf-8')).hexdigest())
def salthash(password,salt):
return hashlib.sha256((password+str(salt)).encode('utf-8')).hexdigest()
# PBKDF2 storage
def genpbkdf2(logins,nbiterations):
db = []
for i in logins:
salt = str(random.randint(0,65535))
csum = base64.b64encode(hashlib.pbkdf2_hmac('sha256',i[1].encode('utf-8'),str(salt).encode('utf-8'),nbiterations)).decode('utf-8')
db.append((i[0],csum,salt,str(nbiterations)))
return db
def authpbkdf2(login, passwd, database):
for i in database:
if i[0] == login:
current = i[1]
salt = i[2]
nbiterations = int(i[3])
return (base64.b64decode(current) == hashlib.pbkdf2_hmac('sha256',passwd.encode('utf-8'),str(salt).encode('utf-8'),nbiterations))
def pbkdf2(password,salt,nbiterations):
nbiterations = int(nbiterations)
return base64.b64encode(hashlib.pbkdf2_hmac('sha256',password.encode('utf-8'),str(salt).encode('utf-8'),nbiterations)).decode('utf-8')
# Generate shadow-style files
def initworkspace(nblogins,nbpasswords,nbiterations):
print("Generating " + str(nblogins) + " logins and " + str(nbpasswords) + " passwords")
try :
os.mkdir("files")
except FileExistsError:
pass
plaindb = genplain(nblogins,nbpasswords)
writeFile("plain", plaindb)
encdb = genencrypted(plaindb)
writeFile("enc", encdb)
shadb = gensha(plaindb)
writeFile("sha", shadb)
saltedshadb = gensaltedsha(plaindb)
writeFile("saltedsha", saltedshadb)
pbkdf2db = genpbkdf2(plaindb,nbiterations)
writeFile("pbkdf2", pbkdf2db)
# Unit tests
if __name__ == '__main__':
# create shadow files
initworkspace(10,100,1000)
print("======\nUnit tests of the toolbox, you must work in skeleton.py\n=========")
# test plain DB
print("\n============\nPlain storage:")
plaindb = readfile("plain")
print("Plain DB is : " + str(plaindb))
print("Authenticating with plain DB : " + str(authplain(plaindb[0][0],plaindb[0][1],plaindb)))
#test encrypted db
print("\n============\nEncrypted storage:")
encdb = readfile("enc")
print("Encrypted DB is " + str(encdb))
print("Authenticating with encrypted DB : " + str(authencrypted(plaindb[1][0],plaindb[1][1],encdb)))
#test SHA db
print("\n============\nSHA storage:")
shadb = readfile("sha")
print("SHA DB is " + str(shadb))
print("Authenticating with SHA DB : " + str(authsha(plaindb[0][0],plaindb[0][1],shadb)))
#test Salted SHA db
print("\n============\nSalted SHA storage:")
saltedshadb = readfile("saltedsha")
print("Salted SHA DB is " + str(saltedshadb))
print("Authenticating with Salted SHA DB : " + str(authsaltedsha(plaindb[0][0],plaindb[0][1],saltedshadb)))
# test PBKDF2 DB
print("\n============\nPBKDF2 storage:")
pbkdf2db = readfile("pbkdf2")
print("PBKDF2 DB is " + str(pbkdf2db))
print("Authenticating with PBKDF2 DB : " + str(authpbkdf2(plaindb[0][0],plaindb[0][1],pbkdf2db)))
print("\n======\nUnit tests of the toolbox, you must work in skeleton.py\n=========")

102
td-passwords.md

@ -0,0 +1,102 @@
# TD : Stockage et authentification par mot de passe
Ce TD présente le stockage et l'authentification par mot de passe côté serveur. Il s'agit typiquement du problème rencontré par une application web qui souhaite authentifier ses utilisateurs, ou de ce qui est mis en œuvre dans une base de mots de passes système (/etc/shadow ou LDAP). Pour cela, l'application va stocker en base les comptes existants ainsi que le moyen de les authentifier. Comme il n'est évidemment pas souhaitable de stocker les mots de passe des utilisateurs en clair, nous allons analyser comment résoudre ce problème.
Contexte général
================
Le scénario d'attaque est une exfiltration des fichiers de l'application (dont le fichier de la base de données ou le fichier `/etc/shadow` par exemple). Vous pouvez consulter une liste non exhaustive de bases ayant été dérobées sur le site [HaveIBeenPwned](https://haveibeenpwned.com/PwnedWebsites) pour vous rendre compte de l'étendue du problème. Dans ce cadre, nous posons les points suivants :
* Le serveur est déjà compromis, l'obtention d'un compte valide sur ce serveur n'a pas d'intérêt pour l'attaquant.
* Les victimes potentielles sont les utilisateurs du site qui y ont enregistré un compte. En effet, un attaquant pourrait alors essayer de se connecter en leur nom sur des services tiers grâce aux informations récupérées.
Pour limiter ce risque, deux approches complémentaires doivent être mises en place :
1. Le serveur doit compliquer autant que possible la tâche de l'attaquant qui a volé la base en maximisant le temps nécessaire pour obtenir des informations valides à partir de la base (objet de ce TD).
2. Les utilisateurs, n'ayant pas la possibilité de connaître les contre-mesures mises en place par le serveur, doivent limiter l'impact de cette compromission en utilisant des mots de passes différents, idéalement un pour chaque site (ce qui passe souvent par un gestionnaire de mots de passe, non abordé dans ce TD).
Ces deux mesures sont bien complémentaires car il est du devoir de chaque site de protéger les mots de passes des utilisateurs n'appliquant pas les meilleures pratiques et de chaque utilisateur de protéger au mieux de ses capacités ses mots de passes. Dans le cadre de ce TD, nous analysons la mesure (1), à appliquer côté serveur.
Squelette de code fourni
========================
Vous devez télécharger le squelette de code [ici](td-passwords-files). Vous pouvez récupérer l'intégralité du dépôt en tapant `git clone https://git.kaz.bzh/francois.lesueur/LPCyber.git`, puis aller dans le dossier `td-passwords-files`. Vous pourriez avoir besoin d'installer la bibliothèque python PyCryptodome (de préférence, et nécessaire avec Python 3.8) ou PyCrypto (dépréciée, mais a priori fonctionnelle jusque Python 3.7). Par exemple avec pip3 pour avoir PyCryptodome uniquement (les deux ne peuvent pas coexister sur le système) :
```
pip3 uninstall PyCrypto
pip3 install -U PyCryptodome
```
* `toolbox.py` est la bibliothèque contenant la boîte à outils, il peut être intéressant d'aller la consulter mais elle n'est pas à modifier ;
* `skeleton.py` contient le programme à écrire.
En début de TD, vous devez lancer `./skeleton.py init` pour créer votre espace de travail. Cette commande, que vous pourrez rappeler plus tard, génère les fichiers suivants (lisibles) dans le sous-dossier `files` :
* `plain` contient la base login/password en clair
* `enc` contient la base chiffrée en AES (la clé, volée également car nécessairement accessible à proximité sauf cas particulier est dans `enckey`)
* `sha` contient la base hashée avec SHA256
* `saltedsha` contient la base hashée/salée avec SHA256
* `pbkdf2` contient la base avec n itérations de hash salé (PBKDF2)
Vous pouvez visualiser tous ces fichiers dans un éditeur de texte classique et analyser les différents champs présents. Vous pouvez ensuite exécuter `./skeleton.py` (sans argument) qui affichera, pour chaque schéma de stockage :
* le contenu de la base stockée
* le résultat d'un test unitaire d'authentification (doit toujours être vrai)
* l'appel (chronométré) à une fonction pour casser la base (fonctions non implémentées dans le squelette fourni)
Analyse des différents schémas
==============================
Pour chaque schéma (clair `plain`, chiffré `enc`, hash `sha`, hash salé `saltedsha`, hash salé coûteux `pbkdf2`), vous devez :
* analyser le processus de l'ajout d'un compte et de la vérification d'un mot de passe
* proposer la procédure de récupération pour un utilisateur qui a perdu son mot de passe
* évaluer l'information révélée directement par la base de mots de passe
* évaluer le coût de cassage d'un mot de passe isolé
* évaluer le coût de cassage de la base entière
* implémenter la fonction pour casser la base
<!--
Notations
=========
* h(m) est le hash du message m
* Si K<sub>A</sub> est une clé symétrique, {m}<sub>K<sub>A</sub></sub> est le chiffré de m avec la clé K<sub>A</sub>, m = { {m}<sub>K<sub>A</sub></sub>}<sub>K<sub>A</sub></sub>
* Si Pub<sub>A</sub> et Priv<sub>A</sub> sont des clés asymétriques complémentaires publique/privée, {m}<sub>Pub<sub>A</sub></sub> est le chiffré de m avec la clé Pub<sub>A</sub> et m = { {m}<sub>Pub<sub>A</sub></sub>}<sub>Priv<sub>A</sub></sub>
* m signé avec la clé Priv<sub>A</sub> est noté m.{h(m)}<sub>Priv<sub>A</sub></sub>
-->
<!--
Côté client
===========
Un gestionnaire de mots de passe conserve une table liant un titre, un login et un mot de passe. Par exemple :
| Titre | Login | Password |
| - | - | - |
| CDiscount | Alice31 | hujk15tr |
| laposte.net | AliceLefur@laposte.net | jku78!io |
| CB | 4785 1547 4554 6657 | 7514 |
Considérons que l'utilisation de ce gestionnaire de mots de passe nécessite la saisie préalable d'un mot de passe maître, lors de l'ouverture.
Proposez la mise en œuvre d'un gestionnaire de mots de passe local puis d'un gestionnaire de mots de passe en ligne. Analysez les risques d'attaques par les différents acteurs (eux-mêmes ou suite à comprommission de leur infrastructure).
-->
Pour approfondir
================
**Les schémas vus dans ce TD, de manière similaire à ce que nous avons vu côté RSA, sont simplifiés pour comprendre le principe (textbook)**. Pour référence plus précise, vous pouvez ensuite consulter (et garder) :
* [SOPHOS : Serious Security: How to store your users’ passwords safely](https://nakedsecurity.sophos.com/2013/11/20/serious-security-how-to-store-your-users-passwords-safely/)
* [OWASP : Password Storage Cheat Sheet](https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet)
* [Micode : 30 000 MOTS DE PASSE CRACKÉS EN 5 MINUTES ! (vidéo)](https://youtu.be/_1ONcmFUOxE)
* [Des exemples de dictionnaires](https://weakpass.com/wordlist)

37
tp-bd-files/docker-compose.yml

@ -0,0 +1,37 @@
version: "3.3"
services:
db:
image: postgres
container_name: postgres
#networks:
# - postgres
environment:
- POSTGRES_DB=clientsdb
- POSTGRES_PASSWORD=foo
- POSTGRES_USER=www
ports:
- 5432:5432
volumes:
- ${PWD}/sqlinit:/docker-entrypoint-initdb.d
- mydb:/var/lib/postgresql/data
webapp:
build: webapp/
image: webapp-image
container_name: webapp
ports:
- 80:80
volumes:
- ${PWD}/webapp:/var/www/html
phppgadmin:
image: bitnami/phppgadmin
container_name: phppgadmin
ports:
- 81:8080
environment:
- DATABASE_HOST=postgres
volumes:
mydb:

11
tp-bd-files/sqlinit/sqlinit.sql

@ -0,0 +1,11 @@
-- CREATE USER www WITH PASSWORD 'foo';
-- CREATE DATABASE clientsdb OWNER www;
\c clientsdb
CREATE TABLE clients (id SERIAL, name varchar(15), password varchar(15), role varchar(15), email varchar(25), comment varchar(5000), message varchar(5000));
ALTER TABLE clients OWNER TO www;
INSERT INTO clients( name, password, role) VALUES ('admin', 'admin', 'admin');
INSERT INTO clients( name, password, role) VALUES ('helpdesk1', 'helpdesk1', 'helpdesk');
INSERT INTO clients( name, password, role) VALUES ('helpdesk2', 'helpdesk2', 'helpdesk');
INSERT INTO clients( name, password, role) VALUES ('client1', 'client1', 'client');
INSERT INTO clients( name, password, role) VALUES ('client2', 'client2', 'client');
INSERT INTO clients( name, password, role) VALUES ('client3', 'client3', 'client');

3
tp-bd-files/webapp/Dockerfile

@ -0,0 +1,3 @@
FROM php:apache
RUN apt-get update && apt-get install -y libpq-dev && docker-php-ext-install pgsql

13
tp-bd-files/webapp/README.txt

@ -0,0 +1,13 @@
Application web de gestion clientèle.
- admin/ contient le code pour ajouter/supprimer des clients
- helpdesk/ contient le code pour modifier des clients, lire leur message et le modifier
- clients/ contient le code permettant à chaque client de voir ses infos et d'envoyer un message au helpdesk
Dans chaque dossier :
- index.php fournit la page de login
- do_login.php traite le login, stocke login et pass dans la session PHP puis redirige vers la page d'affichage print_clients.php
- print_clients.php affiche, selon le cas, la liste + ajout/suppression, la liste + modification ou le compte du client connecté
- do_*.php traite les actions sur la BD (insertion, suppression, modification) demandées par print_clients.php puis redirige vers print_clients.php
La table utilisée est 'clients' de la base 'clientsdb' avec le compte www/foo.

19
tp-bd-files/webapp/admin/do_login.php

@ -0,0 +1,19 @@
<?php
ob_start();
session_start();
$_SESSION['login']=$_POST['login'];
$_SESSION['password']=$_POST['password'];
require_once("../db.inc.php");
$login = $_POST['login'];
$password = $_POST['password'];
$query = "SELECT * FROM clients WHERE name='$login' AND password='$password' AND role='admin'";
$result = pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
if (pg_num_rows($result) > 0)
header('Location: print_clients.php');
else
print "Authentication failed, $query";
?>

32
tp-bd-files/webapp/admin/do_support.php

@ -0,0 +1,32 @@
<?php
ob_start();
session_start();
echo "Login is ". $_SESSION['login'];
echo "&nbsp;<a href=login.php?logout=1>Logout</a><br/><br/>";
require_once("../db.inc.php");
// Gestion des ajouts/suppressions
if (isset($_POST['name'])) { // Ajout d'un nouveau client
$name=$_POST['name'];
$email=$_POST['email'];
$comment=$_POST['comment'];
$password=$_POST['password'];
$role=$_POST['role'];
$query = "INSERT INTO clients (name, email, comment, password, role) VALUES ('$name','$email','$comment', '$password', '$role')";
pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
}
if (isset($_GET['delete'])) { // Suppression d'un client
$id = $_GET['delete'];
$query = "DELETE FROM clients WHERE id='$id'";
pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
}
// Ferme la connexion
pg_close($dbconn);
header('Location: print_clients.php');
?>

17
tp-bd-files/webapp/admin/index.php

@ -0,0 +1,17 @@
<?
session_start();
unset($_SESSION['login']);
unset($_SESSION['password']);
?>
<html><head><title>Login Admin</title></head>
<body>
<form action="do_login.php" method="post">
Login: <input type="text" name="login"><br/>
Password: <input type="password" name="password"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>

45
tp-bd-files/webapp/admin/print_clients.php

@ -0,0 +1,45 @@
<?php
session_start();
echo "Login is ". $_SESSION['login'];
echo "&nbsp;<a href=index.php?logout=1>Logout</a><br/><br/>";
require_once("../db.inc.php");
// Affichage de la table des clients
// Exécution de la requête SQL
$query = 'SELECT * FROM clients';
$result = pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
// Affichage des résultats en HTML
echo "<table border=1><tr><td>Name</td><td>Email</td><td>Comment</td><td>Role</td><td>Supprimer</td>\n";
while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) {
$id=$line['id'];
$name=$line['name'];
$email=$line['email'];
$comment=$line['comment'];
$role=$line['role'];
echo "\t<tr><td>$name</td><td>$email</td><td>$comment</td><td>$role</td>\n";
echo "\t<td><a href=do_support.php?delete=".$id.">Supprimer</a></td></tr>\n";
}
echo "</table>\n";
// Libère le résultat
pg_free_result($result);
// Ferme la connexion
pg_close($dbconn);
?>
<br/><br/>
Ajouter un client :<br/>
<form action=do_support.php method=post>
Name: <input type="text" name="name"><br/>
Password: <input type="text" name="password"><br/>
Role: <input type="text" name="role"><br/>
Email: <input type="text" name="email"><br/>
Comment: <input type="text" name="comment"><br/>
<input type="submit" value="Submit">
</form>

19
tp-bd-files/webapp/clients/do_login.php

@ -0,0 +1,19 @@
<?php
ob_start();
session_start();
$_SESSION['login']=$_POST['login'];
$_SESSION['password']=$_POST['password'];
require_once("../db.inc.php");
$login = $_POST['login'];
$password = $_POST['password'];
$query = "SELECT * FROM clients WHERE name='$login' AND password='$password' AND role='client'";
$result = pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
if (pg_num_rows($result) > 0)
header('Location: print_clients.php');
else
print "Authentication failed, $query";
?>

19
tp-bd-files/webapp/clients/do_message.php

@ -0,0 +1,19 @@
<?php
// ob_start();
session_start();
require_once("../db.inc.php");
// Gestion des modifications
if (isset($_POST['message'])) { // Ajout du message dans la BD
$message=$_POST['message'];
$login=$_SESSION['login'];
$query = "UPDATE clients SET message='$message' WHERE name='$login'";
pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
}
// Ferme la connexion
pg_close($dbconn);
header('Location: print_clients.php');
?>

17
tp-bd-files/webapp/clients/index.php

@ -0,0 +1,17 @@
<?
session_start();
unset($_SESSION['login']);
unset($_SESSION['password']);
?>
<html><head><title>Login Client</title></head>
<body>
<form action="do_login.php" method="post">
Login: <input type="text" name="login"><br/>
Password: <input type="password" name="password"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>

37
tp-bd-files/webapp/clients/print_clients.php

@ -0,0 +1,37 @@
<?php
session_start();
echo "Login is ". $_SESSION['login'];
echo "&nbsp;<a href=index.php?logout=1>Logout</a><br/><br/>";
require_once("../db.inc.php");
// Affichage de la table des clients
// Exécution de la requête SQL
$login = $_SESSION['login'];
$query = "SELECT * FROM clients WHERE name='$login'";
$result = pg_query($dbconn,$query) or die('Échec de la requête : ' . pg_last_error());
// Affichage des résultats en HTML
echo "<table border=1><tr><td>Name</td><td>Email</td><td>Message</td></tr>\n";
while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) {
$id=$line['id'];
$name=$line['name'];
$email=$line['email'];
$message=$line['message'];
echo "\t<tr><td>$name</td><td>$email</td><td>$message</td></tr>\n";
}
echo "</table>\n";
// Libère le résultat
pg_free_result($result);
// Ferme la connexion
pg_close($dbconn);
?>
Envoyer un message :
<form action=do_message.php method=post>
<input type=text name=message>
<input type=submit>
</form>

9
tp-bd-files/webapp/db.inc.php

@ -0,0 +1,9 @@
<?php
$logindb = 'www';
$passworddb = 'foo';
$dbconn = pg_connect("host=postgres dbname=clientsdb user=$logindb password=$passworddb")
or die('Connexion impossible : ' . pg_last_error());
?>

24
tp-bd-files/webapp/helpdesk/do_helpdesk.php

@ -0,0 +1,24 @@
<?php
session_start();
ob_start();
echo "Login is ". $_SESSION['login'];
echo "&nbsp;<a href=login.php?logout=1>Logout</a><br/><br/>";
require_once("../db.inc.php");
// Gestion des modifications
if (isset($_POST['name'])) { // Modification d'un client existant
$name=$_POST['name'];
$email=$_POST['email'];
$comment=$_POST['comment'];
$id=$_POST['id'];
$message=$_POST['message'];
$query = "UPDATE clients SET name='$name',email='$email',comment='$comment',message='$message' WHERE id='$id'";
pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
}
// Ferme la connexion
pg_close($dbconn);
header('Location: print_clients.php');
?>

18
tp-bd-files/webapp/helpdesk/do_login.php

@ -0,0 +1,18 @@
<?php
session_start();
$_SESSION['login']=$_POST['login'];
$_SESSION['password']=$_POST['password'];
require_once("../db.inc.php");
$login = $_POST['login'];
$password = $_POST['password'];
$query = "SELECT * FROM clients WHERE name='$login' AND password='$password' AND role='helpdesk'";
$result = pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
if (pg_num_rows($result) > 0)
header('Location: print_clients.php');
else
print "Authentication failed, $query";
?>

17
tp-bd-files/webapp/helpdesk/index.php

@ -0,0 +1,17 @@
<?
session_start();
unset($_SESSION['login']);
unset($_SESSION['password']);
?>
<html><head><title>Login Helpdesk</title></head>
<body>
<form action="do_login.php" method="post">
Login: <input type="text" name="login"><br/>
Password: <input type="password" name="password"><br/>
<input type="submit" value="Submit">
</form>
</body>
</html>

33
tp-bd-files/webapp/helpdesk/print_clients.php

@ -0,0 +1,33 @@
<?php
session_start();
echo "Login is ". $_SESSION['login'];
echo "&nbsp;<a href=index.php?logout=1>Logout</a><br/><br/>";
require_once("../db.inc.php");
// Affichage de la table des clients
// Exécution de la requête SQL
$query = 'SELECT * FROM clients';
$result = pg_query($dbconn, $query) or die('Échec de la requête : ' . pg_last_error());
// Affichage des résultats en HTML
echo "<table border=1><tr><td>Name</td><td>Email</td><td>Comment</td><td>Message</td><td>Name</td><td>Email</td><td>Comment</td><td>Message</td><td>Update</td></tr>\n";
while ($line = pg_fetch_array($result, null, PGSQL_ASSOC)) {
$id=$line['id'];
$name=$line['name'];
$email=$line['email'];
$comment=$line['comment'];
$message=$line['message'];
echo "\t<tr><td>$name</td><td>$email</td><td>$comment</td><td>&nbsp;$message</td>\n";
echo "\t<form action=do_helpdesk.php method=post><td><input type=text name=name value=\"$name\"></td><td><input type=text name=email value=$email></td><td><input type=text name=comment value=\"$comment\"></td><td><input type=text name=message value=\"$message\"></td><td><input type=submit value=Update><input type=hidden name=id value=$id></td></form></tr>\n";
}
echo "</table>\n";
// Libère le résultat
pg_free_result($result);
// Ferme la connexion
pg_close($dbconn);
?>

147
tp-bd.md

@ -0,0 +1,147 @@
# TP : Sécurité des bases de données (12h)
Pour ce TP, nous allons utiliser Docker. Vous pouvez l'installer sur votre machine ou dans une VM quelconque.
Ce TP peut être réalisé dans la VM MI-LXC disponible [ici](https://flesueur.irisa.fr/mi-lxc/images/milxc-debian-amd64-1.4.1.ova) (identique à la première période). Avant de lancer la VM, il peut être nécessaire de diminuer la RAM allouée. Par défaut, la VM a 3GO : si vous avez 4GO sur votre machine physique, il vaut mieux diminuer à 2GO, voire 1.5GO pour la VM (la VM devrait fonctionner de manière correcte toujours).
Décrivez vos manipulations et expérimentations dans un compte-rendu *individuel* qui devra être déposé sur Moodle à la fin des 12 heures.
Quelques commandes utiles
=========================
Dans la console psql :
* \l lister les bases
* \dt lister les tables
* \du (\du+) lister les rôles (utilisateurs/groupes)
* \z lister les permissions
* TABLE clients; afficher la table clients
* \c database; ouvrir la base database
* La doc de référence : [doc](https://www.postgresql.org/docs/current/index.html)
Démarrage (30 minutes)
=========
Ici, on va démarrer une base PostgreSQL et faire de premières manipulations. Pour créer et démarrer un docker postgresql :
`docker run --name mypostgres -e POSTGRES_PASSWORD=foo -d postgres`
Ensuite, pour se connecter à la console :
`docker exec -it mypostgres psql -U postgres`
La base est persistente si vous stoppez/redémarrez le docker (à confirmer), mais pas en cas de fausse manip. Prenez des notes au fur et à mesure !
> Décrivez comment vous avez installé votre environnement pour ce TP.
Création d'une première base (2 heures)
============================
Il faut tout d'abord comprendre la notion de rôles (utilisateurs/groupes) et de bases.
Rôles
-----
La documentation est à lire [ici](https://www.postgresql.org/docs/current/user-manag.html) (jusque 22.2 seulement pour l'instant). Les groupes et les utilisateurs sont des rôles avec des attributs différents. La modification d'un rôle est expliquée [ici](https://www.postgresql.org/docs/current/sql-alterrole.html).
> Créez un utilisateur ayant le droit de se connecter, avec un mot de passe. Puis utilisez ALTER pour changer son mot de passe.
Base
----
Une base est un ensemble de tables (relations) qui appartient à un utilisateur. La création de base est documentée [ici](https://www.postgresql.org/docs/current/managing-databases.html) (ignorez les parties templates et tablespaces).
> Créez une nouvelle base qui doit appartenir à l'utilisateur que vous avez créé. Puis supprimez-là, récréez-là et utilisez ALTER pour modifier (puis remettre) son propriétaire.
Tables
------
Les tables sont les éléments des bases qui vont contenir les données qui seront requêtées. Pour créer une table, la [doc](https://www.postgresql.org/docs/current/ddl-basics.html). Il faut ensuite la remplir avec [INSERT/UPDATE/DELETE](https://www.postgresql.org/docs/current/dml.html) et la requêter avec [SELECT](https://www.postgresql.org/docs/current/queries.html).
> Créez une table, insérez et modifiez quelques lignes et requêtez-là avec un WHERE simple.
Nous avons maintenant le nécessaire pour explorer les fonctionnalités de sécurité.
Disponibilité (30 minutes)
=============
Expérimentez le dump et la restauration tel que décrit [ici](https://www.postgresql.org/docs/current/backup-dump.html)
Pour vous connecter en shell sur le docker, `docker exec -it mypostgres bash` (où bash peut évidemment être remplacé par toute commande que vous vouliez taper dans le docker).
> Présentez votre déroulé. Décrivez le contenu du dump.
Contrôle d'accès (3 heures)
================
Pour la confidentialité et l'intégrité, nous allons voir la gestion des comptes et des droits.
<< PAUSE COURS AU TABLEAU >> (à défaut, ces [slides](https://flesueur.irisa.fr/accesscontrol_fle.pdf) : lecture générale pour comprendre les différents modèles de contrôle d'accès, savoir s'y repérer et s'en inspirer, et évidemment avec un focus particulier sur la partie RBAC)
Gestion des comptes
-------------------
Il est maintenant temps de lire la gestion de l'appartenance aux rôles [ici](https://www.postgresql.org/docs/current/role-membership.html).
> Créez quelques utilisateurs, quelques groupes et affectez des utilisateurs aux groupes.
Gestion des droits
------------------
PostgreSQL permet de régler les droits :
* des tables : [ici](https://www.postgresql.org/docs/current/ddl-priv.html)
* des lignes : [ici](https://www.postgresql.org/docs/current/ddl-rowsecurity.html)
La gestion par tables permet la première approche à grain moyen, la gestion par lignes permet ensuite une granularité de contrôle beaucoup plus fine. Les droits sont affectés à des rôles afin de faire du RBAC.
> Sur une table, permettez à un rôle de lire uniquement (SELECT) et à un autre d'écrire.
> Sur des lignes, limitez la lecture à l'utilisateur nommé sur la ligne.
Mise en œuvre (6 heures)
=============
Bravo ! Vous voilà maintenant responsable de mettre une formidable application en production ! Cette application a été développée avec les dernières technologies à la mode par le stagiaire qui vient de partir (parce que c'était, avant vous, le seul technique de l'organisation).
Cette application est disponible dans le sous-dossier [tp-bd-files](tp-bd-files/). Vous pouvez la lancer avec `docker-compose up -d` (en étant placé dans le sous-dossier contenant le docker-compose.yml) et y accéder depuis votre hôte aux URLs `http://localhost/admin/`, `http://localhost/clients/` et `http://localhost/helpdesk/`.
Vous pouvez explorer son code (et son README.txt) dans le sous-dossier [webapp](tp-bd-files/webapp/). La base initiale est décrite dans [sqlinit.sql](tp-bd-files/sqlinit/sqlinit.sql), ce qui vous permet de connaître les authentifiants attendus. Pour vous connecter à la base (credentials www/foo) :
* `docker exec -it postgres psql -U www clientsdb`
* ou via un navigateur vers `http://localhost:81` (phppgadmin)
Et là, c'est le drame. En regardant `clients/do_login.php`, vous prenez peur pour la mise en production.
> Quel est le problème dans ce fichier ? Retrouvez-vous ce problème ailleurs ? Qu'aurait-il fallu faire à la place ? Exploitez-le !
Malheureusement, la recette a déjà eu lieu et vous n'avez plus la possibilité de faire des modifications au travers de toute l'application. Nous allons explorer 2 voies de défense en profondeur afin de limiter les impacts :
* Séparation des accès à la BD en 3 utilisateurs distincts admin/helpdesk/client puis restriction des droits sur les tables
* Séparation des accès à la BD en n utilisateurs distincts appartenant à l'un des 3 rôles puis application de Row-Level security
Segmentation des accès en 3 utilisateurs
----------------------------------------
Raffinez l'authentification entre l'application et la BD afin de limiter les dégâts potentiels :
* Créez 3 utilisateurs distincts admin/helpdesk/client au niveau de la BD, qui correspondront aux usages des 3 sous-dossiers de l'application
* Attribuez leur les droits minimaux nécessaires à chacune des sections de l'application (droits de SELECT, INSERT et UPDATE sur la table clients)
* Modifiez l'inclusion du `db.inc.php` pour que les fichiers PHP de chaque sous-dossier se connectent à la base avec les credentials adaptés
> Déployez ce modèle et mettez à jour le code PHP en fonction. Vérifiez que certaines exploitations initiales ne fonctionnent plus. Synthétisez vos changements dans le compte-rendu.
Segmentation des accès en n utilisateurs
----------------------------------------
Raffinez l'authentification entre l'application et la BD afin de limiter les dégâts potentiels :
* Un utilisateur de l'application web = un utilisateur de la BD
* L'authentification sera réalisée directement avec la BD (stockages des authentifiants dans la session PHP)
* Des rôles (hiérarchiques) pour factoriser la gestion des droits
* Une sécurité à grain fin au niveau des lignes de tables (Row security)
> Proposez (sur papier) et faîtes valider un modèle RBAC adapté. Déployez ce modèle et mettez à jour le code PHP en fonction. Vérifiez que votre exploitation initiale ne fonctionne plus. Synthétisez vos changements dans le compte-rendu.
> REMARQUE : En l'absence de row-level security (pas disponible avec MySQL/MariaDB par exemple), un résultat relativement similaire (mais plus complexe à maintenir) aurait pu être obtenu avec l'utilisation de vues.
Bonus
=====
Choisissez une base NoSQL, étudiez, expérimentez et présentez dans votre rapport sa gestion des droits.

171
tp-https.md

@ -0,0 +1,171 @@
# TP : Autorités de certification, authentification mutuelle et HTTPS
Ce TP présente le modèle des autorités de certification et l'applique au protocole HTTPS.
Ce TD sera réalisé dans la VM MI-LXC/SNSTER disponible [ici](https://flesueur.irisa.fr/mi-lxc/images/milxc-snster-vm-2.0.0.ova). Avant de lancer la VM, il peut être nécessaire de diminuer la RAM allouée. Par défaut, la VM a 3GO : si vous avez 4GO sur votre machine physique, il vaut mieux diminuer à 2GO, voire 1.5GO pour la VM (la VM devrait fonctionner de manière correcte toujours).
L'infrastructure déployée simule plusieurs postes dont le SI de l'entreprise _target_ (routeur, serveur web, poste admin), le SI de l'autorité de certification _mica_, un AS d'attaquant _ecorp_ et quelques autres servant à l'intégration de l'ensemble.
**L'objectif du TP est de permettre à la machine isp-a-home de naviguer de manière sécurisée sur le site `www.target.milxc` (hébergé sur la machine target-dmz) et à ce site de faire une authentification forte de ses clients.**
> Si vous êtes sous Windows et que la VM ne fonctionne pas avec VirtualBox, vous pouvez utiliser à la place VMWare Player. Dans ce cas, il faudra cliquer sur "Retry" lors de l'import.
> Le compte-rendu est à déposer en binôme sur Moodle.
Cheat sheet
===========
Voici un petit résumé des commandes dont vous aurez besoin :
| Commande | Description | Utilisation |
| -------- | ----------- | ----------- |
| print | Génère la cartographie du réseau | snster print |
| attach | Permet d'avoir un shell sur une machine | snster attach target-sales |
| display | Lance un affichage sur la machine cible | snster display target-sales |
| start | Lance la construction du bac à sable | snster start |
| stop | Éteint la plateforme pédagogique | snster stop |
Rappel: Vous devez être dans le répertoire `/root/mi-lxc/` pour exécuter ces commandes.
> Si la souris reste bloquée dans une fenêtre virtuelle, appuyez sur CTRL+SHIFT pour la libérer.
> Dans la VM et sur les machines MI-LXC, vous pouvez installer des logiciels supplémentaires. Par défaut, vous avez mousepad pour éditer des fichiers de manière graphique. La VM peut être affichée en plein écran. Si cela ne fonctionne pas, il faut parfois changer la taille de fenêtre manuellement, en tirant dans l'angle inférieur droit, pour que VirtualBox détecte que le redimensionnement automatique est disponible. Il y a une case adéquate (taille d'écran automatique) dans le menu écran qui doit être cochée. Si rien ne marche, c'est parfois en redémarrant la VM que cela peut se déclencher. Mais il *faut* la VM en plein écran.
Connexion en clair
==================
Depuis la machine isp-a-home, ouvrez un navigateur pour vous connecter à `http://www.target.milxc`. Vous accédez à une page Dokuwiki, qui est bien la page attendue.
Nous allons maintenant attaquer depuis l'AS ecorp cette communication en clair, non sécurisée, entre isp-a-home et target-dmz. L'objectif est que le navigateur, lorsqu'il souhaite se connecter à l'URL `http://www.target.milxc`, arrive en fait sur la machine ecorp-infra. Deux pistes peuvent être explorées :
* Attaque DNS qui, via le registrar, consisterait à altérer l'enregistrement DNS pour target.milxc dans la zone du TLD .milxc. Sur la machine milxc-ns :
* Altération de `/etc/nsd/milxc.zone` pour diriger les requêtes DNS pour `target.milxc` vers 100.81.0.2 (appartenant à ecorp)
* Puis `service nsd restart` (le DNS de ecorp est déjà configuré pour répondre aux requêtes pour target.milxc)
* Attaque BGP qui consisterait à dérouter les paquets à destination de l'AS target vers l'AS ecorp (un [exemple de BGP hijacking réel en 2020](https://radar.qrator.net/blog/as1221-hijacking-266asns), une [doc annexe](https://www.orange-business.com/fr/blogs/securite/actualites/detournement-de-trafic-internet-via-protocole-bgp-les-bases-partie-13), [ici](https://www.coinbase.com/blog/celer-bridge-incident-analysis) et [](https://medium.com/s2wblog/post-mortem-of-klayswap-incident-through-bgp-hijacking-en-3ed7e33de600)) :
* Sur la machine ecorp-router : prendre une IP de l'AS target qui déclenchera l'annonce du réseau en BGP (`ifconfig eth1:0 100.80.1.1 netmask 255.255.255.0`)
* Sur la machine ecorp-infra : prendre l'IP de `www.target.milxc` (`ifconfig eth0:0 100.80.1.2 netmask 255.255.255.0`)
Nous constatons ainsi le cas d'attaque que nous souhaitons détecter : un utilisateur sur isp-a-home qui, en tapant l'URL `www.target.milxc`, arrive en fait sur un autre service que celui attendu. Remettez le système en bon ordre de marche pour continuer (pour DNS, remettre la bonne IP 100.80.1.2 ; pour BGP, désactivez l'interface eth1:0 sur ecorp-router `ifconfig eth1:0 down`).
Création d'une CA
=================
Pour sécuriser les communications vers `www.target.milxc`, nous allons créer, déployer et utiliser une CA. Cette CA sera hébergée dans l'AS mica et manipulée sur la machine mica-infra (son nom DNS dans l'infra sera `www.mica.milxc`). Nous utiliserons le protocole ACME (celui de Let's Encrypt) pour l'opération de la CA (challenges, émission des certificats) via la suite d'outils de Smallstep.
Dans un premier temps, il faut initialiser une nouvelle CA en tant que root (création d'une paire de clés, d'un certificat racine, etc.) ([doc](https://smallstep.com/docs/step-ca/getting-started)) :
# step ca init <- le # dénote une commande shell à taper en root
Pour cette initialisation, les paramètres ont peu d'importance (l'essentiel est la création du matériel cryptographique) et les choix suggérés par défaut à chaque question seront suffisants. Il faut juste faire attention aux questions :
* Faire une installation "Standalone"
* "What DNS names or IP addresses would you like to add to your new CA?", à laquelle il faut répondre "www.mica.milxc"
* "What address will your new CA listen at?", à laquelle il faut bien répondre ":443" (comme suggéré par défaut) et non "127.0.0.1:8443" (comme suggéré dans la doc liée précédemment). La CA doit, dans notre cas, écouter sur l'interface réseau externe et non sur localhost pour permettre l'émission du certificat de target-dmz dans la partie suivante.
* "What do you want your password to be?", à laquelle il est préférable de choisir un mot de passe vous-mêmes
Dans un second temps, il faut activer le protocole ACME pour cette CA ([doc](https://smallstep.com/docs/tutorials/acme-challenge), le protocole ACME est responsable des défis/réponse pour la génération automatique des certificats) : <!-- (https://smallstep.com/blog/private-acme-server/)-->
# step ca provisioner add acme --type ACME
Il faut démarrer le serveur de la CA (il doit rester actif pour la suite du TP) :
# step-ca .step/config/ca.json
> La commande step-ca est bloquante, soit vous la mettez en arrière plan avec Ctrl+z puis `bg`, soit vous ouvrez ensuite un autre terminal. Ne la lancez pas avec un &, elle doit en effet demander au lancement le mot de passe de la clé privée, ce qu'elle ne pourrait pas faire lancée avec un &.
Vous aurez besoin du certificat public de la racine par la suite. Le plus simple est de le diffuser par le site web de la CA comme suit. Extrayez-le grâce à la commande suivante :
# step ca root root.crt <- ceci extrait le certificat racine vers le fichier root.crt
Puis copiez-le vers `/var/www/html` (avec les droits permettant sa lecture par le serveur web, donc `chmod 644 /var/www/html/root.crt`). Il sera ainsi accessible depuis toutes les autres machines par l'URL `http://www.mica.milxc/root.crt`.
> Si, après avoir affiché à l'écran un document chiffré (par exemple avec la commande `cat`), votre terminal affiche de mauvais caractères, utilisez la combinaison de touches `Ctrl+v, Ctrl+o` pour retrouver un affichage fonctionnel (ou tapez `reset`).
> Pour reprendre la configuration à 0, il faut supprimer le dossier `/root/.step` sur la machine mica-infra
Intégration de la CA à l'écosystème HTTPS
=========================================
Pour que la CA soit opérationnelle, il faut qu'elle soit reconnue par les clients HTTPS, ie, les navigateurs web (Firefox ici). Cette reconnaissance, dans le cas d'une CA globale, passe par l'intégration du certificat racine dans le magasin de certificats fourni avec le navigateur, donc par l'éditeur de ce navigateur.
> En entreprise, on rencontre souvent une CA locale qui est ajoutée localement au magasin de certificats. Dans ce TP, nous étudions le fonctionnement général de HTTPS global à travers le monde et non un déploiement à l'échelle locale.
Vous devez pour cela :
* Passer le filtre des éditeurs de navigateurs et les convaincre de reconnaître votre CA. Il s'agit bien évidemment d'une opération complexe, longue, coûteuse et rare. Ici, nous la simulerons chez l'éditeur de navigateur Gozilla. La machine `gozilla-infra` peut intégrer un certificat préalablement téléchargé au trousseau par défaut avec la commande `addcatofox.sh <certificate>` . Une fois cette commande exécutée, la distribution du navigateur (ou de ses mises à jour) intégrera ce nouveau certificat.
* Déclencher la mise à jour du navigateur par le client, en exécutant `updatefox.sh` en tant que root sur la machine `isp-a-home`
La nouvelle CA est ainsi devenue une CA par défaut, reconnue globalement. Vérifiez, après avoir redémarré Firefox, que vous la retrouvez bien dans le magasin de certiticats de Firefox.
Certification du serveur target-dmz
==================================
Sur l'AS target, vous disposez du serveur target-dmz sur lequel il faut déployer du matériel cryptographique pour faire du HTTPS. Vous devrez notamment :
* Générer une paire de clés et obtenir le certificat correspondant depuis la CA MICA, les clés arrivent dans `/etc/letsencrypt/live/www.target.milxc/` (les erreurs de certbot de type "InsecureRequestWarning" peuvent être ignorées, il faut par contre vérifier que son message final confirme bien la création des clés attendues). Durant cette étape, le serveur MICA va transmettre à certbot des défis (sous forme de chaîne aléatoire) puis va venir les requêter via le nom d'hôte demandé dans le certificat. Si cette requête fonctionne, cela montre que le client certbot est bien situé sur le serveur pour lequel il demande un certificat :
# certbot certonly -n --apache -d www.target.milxc --server https://www.mica.milxc/acme/acme/directory
* Configurer le matériel cryptographique de ce nouveau site dans le fichier `/etc/apache2/sites-enabled/default-ssl.conf` (vous devrez utiliser la chaîne complète de certificats depuis la racine, c'est-à-dire `fullchain.pem`, et la clé `privkey.pem`).
* Vous devez redémarrer le serveur apache2 après vos modifications : `service apache2 restart`
Connectez-vous maintenant en HTTPS depuis `isp-a-home` (si vous aviez ajouté une exception de sécurité à un moment du TP, retirez-la avant). Tout doit se dérouler sans alerte, visualisez le certificat reçu. (Vous arrivez sur une page par défaut, le dokuwiki est accessible à l'URL `https://www.target.milxc/dokuwiki/`)
<!-- ou apt install python3-certbot-apache puis un --apache au lieu du --standalone -->
Attaques sur un serveur HTTPS
=============================
Attaque sur la connexion au serveur
-----------------------------------
Refaites l'attaque du début (DNS ou BGP) et vérifiez que la connexion depuis isp-a-home, lorsqu'elle est routée vers le serveur attaquant, génère bien une alerte de sécurité.
<!-- il faudrait un certificat plus joli -->
Quelle est d'habitude votre réaction face à ce genre d'alerte ? Que pouvons nous en conclure sur la protection et le risque restant avec HTTPS ?
Attaque lors de la création du certificat
-------------------------------
En reprenant les attaques du début, obtenez depuis ecorp-infra un certificat bien signé par MICA lié à l'URL `www.target.milxc`. Ces attaques DNS/BGP vont vous permettre de vous faire passer pour Target auprès de mica, lors de la phase de création du certificat.
Validez la réussite en vous connectant depuis isp-a-home vers ce faux serveur, maintenant sans alerte de sécurité.
Authentification mutuelle
=========================
Mettez en place une authentification forte des clients par le serveur au moyen de certificats clients.
Déroulé général :
* Côté serveur (donc target-dmz), vous devez limiter l'accès aux seuls clients détenteurs d'un certificat valide. Dans `/etc/apache2/sites-enabled/default-ssl.conf` :
* Paramétrez la directive [SSLCACertificateFile](https://httpd.apache.org/docs/2.4/fr/mod/mod_ssl.html#sslcacertificatefile) en obtenant et spécifiant le crt de la CA (root.crt, pas le crt de ce serveur www.target.milxc !)
* Décommentez les directive [SSLVerifyClient require](https://httpd.apache.org/docs/2.4/fr/mod/mod_ssl.html#sslverifyclient) et [SSLVerifyDepth](https://httpd.apache.org/docs/2.4/fr/mod/mod_ssl.html#sslverifydepth) pour forcer l'authentification des clients
* Validez depuis isp-a-home que l'accès TLS à `https://www.target.milxc` vous est bien refusé
* Générez un certificat client sur la machine `mica-infra`. Il faut faire un `step ca certificate "VotreNomÀCertifier" client.crt client.key` et utiliser le provisioner par défaut JWK (pas le ACME). Si le programme `step-ca` a été quitté, il faut le relancer préalablement (`step-ca .step/config/ca.json`)
* Packagez ensemble ce certificat et cette clé client avec `openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12`
* Le client (la machine isp-a-home) doit récupérer ce client.p12 et l'importer dans Firefox (Préférences -> Sécurité -> Certificats -> Mes certificats -> Importer)
* Validez que l'accès est maintenant autorisé
<!--
Bonus : Révocation
==========
Expérimentez les mécanismes de révocation disponibles (CRL, OCSP en ligne ou agrafé) pour révoquer le certificat serveur ainsi que les certificats clients.
-->
Révocation
========
> Dans Smallstep, la révocation par CRL/OCSP n'est pas (encore) gérée. À la place, les certificats ont par défaut une durée courte (24h) et doivent être renouvelés automatiquement, ce qui limite largement le besoin de révocation explicite. Ce positionnement et toutes les limites de la révocation explicite sont très bien expliqués par les auteurs [ici](https://smallstep.com/blog/passive-revocation/)
<!-- pinning, hsts -->

92
tp-ldap.md

@ -0,0 +1,92 @@
# TP : Sécurité des annuaires (10,5h)
Ce TP-projet présente les annuaires LDAP.
Ce TD sera réalisé dans la VM MI-LXC/SNSTER disponible [ici](https://flesueur.irisa.fr/mi-lxc/images/milxc-snster-vm-2.0.0.ova). Avant de lancer la VM, il peut être nécessaire de diminuer la RAM allouée. Par défaut, la VM a 3GO : si vous avez 4GO sur votre machine physique, il vaut mieux diminuer à 2GO, voire 1.5GO pour la VM (la VM devrait fonctionner de manière correcte toujours).
Un [bon tuto LDAP](https://connect.ed-diamond.com/Linux-Pratique/lp-115/installation-et-configuration-d-un-annuaire-openldap) pour démarrer.
> Si vous êtes sous Windows et que la VM ne fonctionne pas avec VirtualBox, vous pouvez utiliser à la place VMWare Player. Dans ce cas, il faudra cliquer sur "Retry" lors de l'import.
> Le compte-rendu est à déposer en binôme sur Moodle.
Cheat sheet
===========
Voici un petit résumé des commandes dont vous aurez besoin :
| Commande | Description | Utilisation |
| -------- | ----------- | ----------- |
| print | Génère la cartographie du réseau | snster print |
| attach | Permet d'avoir un shell sur une machine | snster attach target-sales |
| display | Lance un affichage sur la machine cible | snster display target-sales |
| start | Lance la construction du bac à sable | snster start |
| stop | Éteint la plateforme pédagogique | snster stop |
Rappel: Vous devez être dans le répertoire `/root/mi-lxc/` pour exécuter ces commandes.
> Si la souris reste bloquée dans une fenêtre virtuelle, appuyez sur CTRL+SHIFT pour la libérer.
> Dans la VM et sur les machines MI-LXC, vous pouvez installer des logiciels supplémentaires. Par défaut, vous avez mousepad pour éditer des fichiers de manière graphique. La VM peut être affichée en plein écran. Si cela ne fonctionne pas, il faut parfois changer la taille de fenêtre manuellement, en tirant dans l'angle inférieur droit, pour que VirtualBox détecte que le redimensionnement automatique est disponible. Il y a une case adéquate (taille d'écran automatique) dans le menu écran qui doit être cochée. Si rien ne marche, c'est parfois en redémarrant la VM que cela peut se déclencher. Mais il *faut* la VM en plein écran.
Étude d'un LDAP existant
========================
L'organisation Target utilise une authentification centralisée basée sur un LDAP. Explorez son usage :
* Qui est le serveur ?
* Trouvez 2 clients, nous allons étudier leur configuration. Cherchez dans `/etc` et ses sous-dossiers (find est votre ami) les fichiers linss-ldap.conf, pam_ldap.conf et nsswitch.conf. Analysez ces fichiers de configuation, cherchez leur usage et notez les quelques lignes qui vous paraissent les paramètres essentiels.
* Analysez les échanges avec Wireshark :
* Lors d'une authentification (commercial et admin sont des comptes LDAP)
* Lors d'un `ls -l /home` (Vous devez forcer la résolution de l'uid de commercial ou admin qui sont des comptes LDAP. Attention au cache ! Pour gagner du temps, les clients gardent les dernières réponses en cache et ne refont pas les même requêtes. Pour couper le cache côté client : `service nscd stop`)
* En ligne de commande sur le serveur, à travers les commandes de manipulation ldap (et non les commandes shell `adduser`/`passwd`), trouvez comment :
* Ajouter un utilisateur (avec ldapadd)
* Modifier l'uidNumber d'un utilisateur (avec ldapmodify)
* Modifier le mot de passe d'un utilisateur (avec ldapmodify)
> La commande `ldapsearch -cxD cn=admin,dc=target,dc=milxc -w root -b dc=target,dc=milxc` permet par exemple d'afficher les données existantes. Quelques exemples d'utilisation peuvent être trouvés dans le fichier [mi-lxc/groups/target/ldap/provision.sh](https://github.com/flesueur/mi-lxc/blob/master/groups/target/ldap/provision.sh).
Déploiement d'un LDAP
=====================
Reprenez le TP3 (soit vous avez encore l'infra, soit vous la recréez à partir du sujet) pour avoir l'AS IUTVA. Dans cet AS :
* Ajoutez une machine qui vous servira de serveur LDAP. Une fois démarrée, installez et configurez le serveur LDAP
* Configurez une autre machine de l'AS IUTVA pour être client de ce LDAP (pour l'authentification, PAM, et pour les user/groupes affichés par `ls`, NSS)
* Obtenez un joli certificat TLS :
* Soit en utilisant la CA ACME déjà explorée
* Soit en recréant une CA ad hoc interne
* (le ldap étant un service interne, on peut concevoir de le manipuler avec un certificat à validité interne)
* Quel matériel cryptographique devez-vous déployer sur le client LDAP ?
* Mettez à jour la configuration pour faire du LDAPS
* Installez phpldapadmin sur votre machine LDAP pour aider à son administration
* Programmez une sauvegarde régulière vers une autre machine (cron). Modifiez ou supprimez votre annuaire puis restaurez votre sauvegarde, vérifiez.
ACL
===
Les ACL permettent de limiter les accès aux différents champs. Une bonne documentation [ici](https://www.vincentliefooghe.net/content/les-acl-dans-openldap).
Restreignez les accès aux mots de passe comme proposé : seul l'utilisateur et admin peuvent changer les mots de passe. Expérimentez ensuite la restriction d'un autre champ, puis l'annulation de cette restriction.
Durcissement LDAP
=================
Choisissez un guide de durcissement LDAP ([exemple1](https://www.openldap.org/doc/admin26/security.html), [exemple2](https://ldapwiki.com/wiki/Best%20Practices%20for%20LDAP%20Security)). Sans mettre toutes les recommandations en place :
* Analysez les recommandations déjà en place
* Celles qui seraient souhaitables
* Comment seraient-elles mises en place (de manière synthétique, pas des commandes exactes)
Une injection LDAP
==================
Installez, sur votre serveur LDAP ou une machine à côté, un serveur Apache avec PHP et son module LDAP (php-ldap).
Faîtes une page PHP d'annuaire qui demande un login et affiche ses coordonnées (pour l'exemple, vous pouvez juste afficher son uid). Vous pouvez vous inspirer des index.php/do_login.php du TP BD en les adaptant au cas du LDAP. Vous trouverez un peu de doc [ici](https://www.php.net/manual/fr/ldap.using.php) et [](https://www.php.net/manual/fr/ldap.examples-basic.php).
Enfin, faîtes une injection LDAP sur cette page pour lister l'ensemble des utilisateurs et proposez une remédiation. Une explication des injections LDAP peut être trouvée [ici](https://www.synopsys.com/glossary/what-is-ldap-injection.html).
Essayez de construire une injection LDAP plus complexe (avec intégration de structure de commande (des '()') côté client).
Loading…
Cancel
Save