Préambule La sécurité PHP pour les tout petits

 
CONTINUER À LIRE
La sécurité PHP pour les tout petits

1. Préambule
         Ce document est un livre de recettes pour protéger une architecture LAMP (Linux, Apache, MySQL,
PHP), voire WAMP (Windows, Apache, MySQL, PHP) mais elle peut aussi aider, pour peu qu'on l'adapte
correctement, à protéger toute architecture Web. La démarche ne garantit absolument pas l'inviolabilité, le 100%
ne faisant pas partie de ce monde, mais va rendre la tâche du pirate plus compliquée, plus coûteuse et plus
longue.

    1.1. Pré-requis
          Nous supposerons que PHP (5.6 minimum) est installé en tant que module dans Apache. Le cas de PHP
utilisé comme CGI ne devrait recouvrir que le cas des particuliers chez leur FAI favori. La version PHP-PFM est
plus performante, mais plus complexe, elle n’est pas traitée ici. Nous supposerons aussi que le lecteur est au
moins habitué à la programmation, même sans être un expert. Nous supposerons enfin que les développeurs web
ne sont pas hostiles, tout au plus inconscients.

    1.2. But des scripts
         Les scripts, PHP par exemple, ont pour but d’interagir avec l’utilisateur, ce qui signifie que celui-ci doit
envoyer, d’une manière ou d’une autre, des données qui seront utilisées par le programme. Ces données seront,
après transformation, stockées par le programme, soit dans des fichiers, soit dans des bases de données, MySQL
par exemple, pour être ensuite renvoyées d'une manière ou d'une autre à un utilisateur. La difficulté va donc
porter sur la protection de l'ensemble de cette chaîne en évitant ou limitant les attaques informatiques.

Version du jeudi 29 mars 2018                     Fabrice Prigent                                               1/17
2. Linux
         Plusieurs choses peuvent être faites au niveau du Linux pour renforcer la sécurité. Parmi les plus
simples, mettre en place le pare-feu pour interdire les connexions extérieures sur autre chose que les ports
spécifiquement ouverts:
 iptables   –A   INPUT   –m   state –-state ESTABLISHED –j ACCEPT
 iptables   –A   INPUT   –i   lo –j ACCEPT
 iptables   –A   INPUT   –p   tcp –-dport 80 –j ACCEPT
 iptables   –A   INPUT   –p   tcp –-dport 443 –j ACCEPT
 iptables   -A   INPUT   -p   icmp --icmp-type echo-request -j ACCEPT
 iptables   –A   INPUT   –j   LOG
 iptables   –A   INPUT   –j   DROP
           Reste ensuite à interdire aux processus apache et MySQL à sortir de la machine : qu'auraient-ils donc à
faire à l'extérieur (sauf cas du mod_proxy) ?
 iptables   –A   OUTPUT   –o   lo –j ACCEPT
 iptables   –A   OUTPUT   –m   state –-state ESTABLISHED –j ACCEPT
 iptables   –A   OUTPUT   –d   mysql.site.fr –p tcp –-dport 3306 –j ACCEPT # Serveur MySQL distant
 iptables   –A   OUTPUT   –d   smtp.site.fr –p tcp –-dport 25 –j ACCEPT # Serveur SMTP du site
 iptables   –A   OUTPUT   –d   dns.site.fr –p udp –-dport 53 –j ACCEPT # Serveur DNS du site
 iptables   -A   OUTPUT   -m   owner --uid-owner 48 –j LOG # Utilisateur Apache
 iptables   -A   OUTPUT   -m   owner --uid-owner 27 –j LOG # Utilisateur MySQL
 iptables   -A   OUTPUT   -m   owner --uid-owner 48 –j DROP # Utilisateur Apache
 iptables   -A   OUTPUT   -m   owner --uid-owner 27 –j DROP # Utilisateur MySQL
 iptables   –A   OUTPUT   –j   ACCEPT
        Dans les solutions plus ardues, on trouvera tous les processus de durcissement d'OS, au rang desquels
on trouvera SELinux, GrSecurity, Pax, Apparmor.
          Dans les solutions générales et assez longues, on se reportera sur les excellents guides de la N.S.A. qui
parlent, entre autres, de Linux, Cisco, Windows, etc.

    2.1. SeLinux
         De manière générale : ne pas le désactiver (hormis cas spécifiques) , par contre, il sera utile d’ouvrir
certaines capacités :
 getsebool -a # Pour la liste des autorisations "simples" activées ou pas
 setsebool httpd_can_network_connect_db on # Pour autoriser, par exemple, les connexions aux
 SGBDs

3. Apache

    3.1. Le logiciel lui-même
         Plusieurs méthodes existent pour sécuriser apache. La première idée est de le mettre en chroot. Cela
impose de nombreuses contraintes, difficiles à respecter en production. Heureusement, la NSA pense à nous
grâce à son SELinux qui permet de contraindre un processus, même root, dans un environnement sécurisé.

    3.2. Le paramétrage
         Il est important de vérifier quelques paramètres, et de limiter leur action suivant vos utilisateurs (les
options allowOverride sont là pour cela). Attention : cela empêche l'utilisation des .htaccess. A vous de décider
ce que vous placer.
 
 AllowOverride None
 
        Désactivez la signature de l'apache:
 ServerSignature Off
 ServerTokens Prod
        Désactiver le mode trace
 TraceEnable off
        Pensez aussi à journaliser correctement :

Version du jeudi 29 mars 2018                    Fabrice Prigent                                              2/17
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
 CustomLog logs/access_log combined
         Évitez les log DNS qui sont une mauvaise idée, car ils prennent du temps pour une information inutile,
voire dangereuse : l'information DNS peut changer entre le problème et sa détection.
         Idem pour les IdentityCheck, hormis cas particulier.
 HostnameLookups Off
 IdentityCheck off
         Mettez en place le mécanisme des .htaccess et empêchez leur rapatriement.
 AccessFileName .htaccess
 
     Order allow,deny
     Deny from all
 
         On peut aussi limiter les requêtes en taille et réduire le timeout.
 LimitRequestBody 1048576
 LimitXMLRequestBody 10485760
 Timeout 45
         Bloquer par défaut les outils qui donneraient trop d’informations (même s’ils ne sont pas présents)
 #########################################################
 #
 #        Restrictions sur des outils d'administration
 #
 ExtendedStatus On
 
          SetHandler server-status
          Require ip 127.0.0.1 192.168.1 10.26
 
          allow from univ-tlse1.fr
          allow from ut-capitole.fr
          deny from all
 
          allow from univ-tlse1.fr
          allow from ut-capitole.fr
          deny from all
 
         Tuez dans l’œuf toute tentative de XSS distant (https://content-security-policy.com/)
 Header set Content-Security-Policy "default-src 'self' ;"
 Header set Content-Security-Policy "script-src 'self' www.google-analytics.com
 ajax.googleapis.com;"
         Refusez les indexations automatiques de répertoires, tous vos fichiers seraient visibles (hormis cas
précis). Cela peut-être fait en créant systématiquement un index.html, mais aussi au niveau du serveur ou des
répertoires grâce à la directive.
 Options -Indexes

    3.3. Les VirtualHosts
          Pensez à faire des journaux individuels pour chaque virtualhost, ainsi qu'un fichier de configuration par
virtualhost (le répertoire conf.d est fait pour cela). Choisissez bien leur nom, surtout si vous souhaitez utiliser les
certificats qui vont se baser sur ce nom.
         Créer un serveur par défaut, sans la moindre information.

    3.4. L'arborescence

         3.4.1.Fichiers à rendre inaccessibles
         Prévoyez une routine d'effacement de vos fichiers temporaires ou de sauvegarde (*.bak, *.old, *.$$$,
*.tmp, ficher~ ou *.2), éventuellement, vous pouvez aussi les rendre illisibles par le daemon apache.
 chown root:root *.bak

Version du jeudi 29 mars 2018                      Fabrice Prigent                                                3/17
chmod    400 *.bak

         3.4.2.Répertoires et fichiers
        Il faut aussi faire sortir de l'arborescence, tant que faire se peut, les fichiers qui n'ont pas vocation à être
directement visibles. En particulier tout ce qui concerne les include et les fichiers de données.

         3.4.3.Le fichier robots.txt
         Le fichier /robots.txt doit contenir l'ensemble des répertoires à ne pas laisser explorer par des robots tels
GOOGLEBOT ou autre. Cela pourrait effectivement donner beaucoup trop d'informations aux pirates, en
particulier doivent être exclus : les répertoires de statistiques, les répertoires privés, plus tout répertoire "piégé"
que vous pourriez créer. Exemple d'un tel fichier :
 User-agent: *
 #       Répertoires standard
 Disallow: /cgi-bin
 #       Quelques fichiers
 Disallow:        admin.php
 Disallow:        test.php
 #       Statistiques
 Disallow: /usage/
 Disallow: /mrtg/
 Disallow: /webalizer/
 Disallow: /private/mrtg/
 #       Répertoires prives
 Disallow: /install_redhat/
 Disallow: /noaccess/
        Rappel : une règle est faite pour être contournée, et les pirates ne s'en priveront pas, le robots.txt n'ayant
qu'une vocation d'indication.
         Ce fichier peut être validé grâce à cet outil http://www.searchengineworld.com/cgi-bin/robotcheck.cgi.

         3.4.4.Les journaux sont vos amis
         Il est important de suivre la vie de vos journaux, et de collecter le plus d'information possible sur votre
serveur. Journaliser, c'est bien, mais il est aussi souhaitable de repérer les anomalies.
         On regardera donc avec attention (ou plutôt, on fera regarder par un programme) les tentatives étranges,
répétitives. Un bon analyseur de journaux peut souvent (s'il n'est pas lui-même piratable) donner un bon aperçu
de choses "étonnantes". On regardera du côté de webstats, awstats ou autres.

         3.4.5.Le 6G Firewall
          Un développeur web (Jeff Starr)a conçu un .htaccess, à placer à la racine web du site, qui permet de
limiter grandement le potentiel des attaquants, en utilisant le mod_rewrite. On le trouve sur
https://perishablepress.com/6g/. Les règles ne sont pas toutes adaptées à tous les contextes. Il vous faudra vérifier
leur adéquation.
 # 6G FIREWALL/BLACKLIST
 # @ https://perishablepress.com/6g/

 # 6G:[QUERY STRINGS]
 
         RewriteEngine On
         RewriteCond %{QUERY_STRING}           (eval\() [NC,OR]
         RewriteCond %{QUERY_STRING}           (127\.0\.0\.1) [NC,OR]
         RewriteCond %{QUERY_STRING}           ([a-z0-9]{2000}) [NC,OR]
         RewriteCond %{QUERY_STRING}           (javascript:)(.*)(;) [NC,OR]
         RewriteCond %{QUERY_STRING}           (base64_encode)(.*)(\() [NC,OR]
         RewriteCond %{QUERY_STRING}           (GLOBALS|REQUEST)(=|\[|%) [NC,OR]
         RewriteCond %{QUERY_STRING}           (|%3) [NC,OR]
         RewriteCond %{QUERY_STRING}           (\\|\.\.\.|\.\./|~|`||\|) [NC,OR]
         RewriteCond %{QUERY_STRING}           (boot\.ini|etc/passwd|self/environ) [NC,OR]
         RewriteCond %{QUERY_STRING}           (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
         RewriteCond %{QUERY_STRING}           (\'|\")(.*)(drop|insert|md5|select|union) [NC]
         RewriteRule .* - [F]
 
Version du jeudi 29 mars 2018                      Fabrice Prigent                                                 4/17
# 6G:[REQUEST METHOD]
 
         RewriteCond %{REQUEST_METHOD} ^(connect|debug|delete|move|put|trace|track) [NC]
         RewriteRule .* - [F]
 
 # 6G:[REFERRERS]
 
         RewriteCond %{HTTP_REFERER} ([a-z0-9]{2000}) [NC,OR]
         RewriteCond %{HTTP_REFERER} (semalt.com|todaperfeita) [NC]
         RewriteRule .* - [F]
 
 # 6G:[REQUEST STRINGS]
 
         RedirectMatch 403 (?i)([a-z0-9]{2000})
         RedirectMatch 403 (?i)(https?|ftp|php):/
         RedirectMatch 403 (?i)(base64_encode)(.*)(\()
         RedirectMatch 403 (?i)(=\\\'|=\\%27|/\\\'/?)\.
         RedirectMatch 403 (?i)/(\$(\&)?|\*|\"|\.|,|&|&?)/?$
         RedirectMatch 403 (?i)(\{0\}|\(/\(|\.\.\.|\+\+\+|\\\"\\\")
         RedirectMatch 403 (?i)(~|`||:|;|,|%|\\|\s|\{|\}|\[|\]|\|)
         RedirectMatch 403 (?i)/(=|\$&|_mm|cgi-|etc/passwd|muieblack)
         RedirectMatch 403 (?i)(&pws=0|_vti_|\(null\)|\{\$itemURL\}|echo(.*)kae|etc/passwd|
 eval\(|self/environ)
         RedirectMatch 403 (?i)\.(aspx?|bash|bak?|cfg|cgi|dll|exe|git|hg|ini|jsp|log|mdb|out|
 sql|svn|swp|tar|rar|rdf)$
         RedirectMatch 403 (?i)/(^$|(wp-)?config|mobiquo|phpinfo|shell|sqlpatch|thumb|
 thumb_editor|thumbopen|timthumb|webshell)\.php
 
 # 6G:[USER AGENTS]
 
         SetEnvIfNoCase User-Agent ([a-z0-9]{2000}) bad_bot
         SetEnvIfNoCase User-Agent (archive.org|binlar|casper|checkpriv|choppy|clshttp|
 cmsworld|diavol|dotbot|extract|feedfinder|flicky|g00g1e|harvest|heritrix|httrack|kmccrew|
 loader|miner|nikto|nutch|planetwork|postrank|purebot|pycurl|python|seekerspider|siclab|
 skygrid|sqlmap|sucker|turnit|vikspider|winhttp|xxxyy|youda|zmeu|zune) bad_bot
         
                  Order Allow,Deny
                  Allow from All
                  Deny from env=bad_bot
         
 # 6G:[BAD IPS]
 
          Order Allow,Deny
          Allow from All
          # uncomment/edit/repeat next line to block IPs
          # Deny from 123.456.789
 
    3.5. Les modules de sécurité
         Afin de renforcer la sécurité d'apache, plusieurs modules ont été développés. On trouvera aussi des
routines intéressantes en PHP.

         3.5.1.Mod_chroot
          Mod_chroot qui se trouve sur http://core.segfault.pl/~hobbit/mod_chroot/permet de mettre Apache dans
un chroot, sans avoir à copier les innombrables fichiers nécessaires à son fonctionnement. Rapide et efficace, il
n'atteint pas, malgré tout, la précision d'une installation manuelle. De plus, il sera nécessaire de garder à l'esprit
que tous les programmes lancés par apache seront cantonnés au répertoire chrooté.
 SecChrootDir /chroot/apache

Version du jeudi 29 mars 2018                      Fabrice Prigent                                               5/17
3.5.2.Mod_security
         Mod_security se trouve sur http://www.modsecurity.org. Il permet de normaliser les urls avant l'envoi
aux divers modules, et surtout de repérer les chaînes de caractères, ou plutôt les expressions régulières
dangereuses dans les variables envoyées, ainsi que repérer, dans les pages de retour, les informations qui sont
signes d'une attaque réussie. Il est de plus capable de chrooter apache de la même manière que mod_chroot.
        Son emploi est plus adapté à un Apache 2.x. Mais sa réputation est telle que désormais il est intégré
dans quasiment toutes les distributions.
        Les signatures sont, quant à elles, gérées par l'OWASP, qui les met à jour régulièrement. Comodo en
propose aussi gratuitement (https://modsecurity.comodo.com/ )
        Pour une « redhat »
 yum install mod_security mod_security_crs
        ou sur une ubuntu/debian
 apt-get install libapache2-modsecurity
 cd /etc/modsecurity/
 cp modsecurity.conf-recommended modsecurity.conf
 service apache2 restart
         Puis configuration pour une instance donnée, ou au global. Avec les règles fournies, le plus gros travail
consiste à empêcher certaines règles de fonctionner.
 
 # Pour retirer une règle spécifiquement pour un répertoire
 
 SecRuleRemoveById 950009
 
 # Pour retirer globalement une règle
 SecRuleRemoveById      960010

 # Pour retirer une règle qui déclenche en phase 1
 SecRule REQUEST_URI "^/hr-rich-client/hrservlet/GetHRPage"
 "phase:1,id:10001,nolog,ctl:ruleRemoveById=960010"

 # Pour retirer une règle pour une variable précise.
 SecRule ARGS "logoutRequest" "phase:2,id:100020,nolog,ctl:ruleRemoveById=973332
 SecRule ARGS "javax.faces.ViewState" "phase:2,id:10021,nolog,ctl:ruleRemoveById=950006
 
          On pourra trouver sur le site http://www.modsecurity.org/projects/rules/index.html des règles de
filtrage, qui seront, bien évidemment, à adapter : on ne sécurise pas une application simple comme le logiciel
phpmyadmin.        Pour      plus  de      précision     sur    les    « phase     1»     et   « phase     2»
http://blog.modsecurity.org/2007/12/using-transacti.html

         3.5.3.Mod_evasive
         Ce module permet de mettre en liste noire des machines qui tentent de saturer un serveur web, par des
requêtes trop rapides. Voici un exemple de configuration pour apache 2.x. A mettre dans le httpd.conf. Une
option supplémentaire permet aussi de lancer une commande système.
 
     DOSHashTableSize     3097
     DOSPageCount         2
     DOSSiteCount         50
     DOSPageInterval      1
     DOSSiteInterval      1
     DOSBlockingPeriod    10
     DOSEmailNotify    webmaster@site.fr
     DOSLogDir         "/var/lock/mod_evasive"
     DOSWhitelist      127.0.0.1
     DOSWhitelist      193.49.*.*
 
Version du jeudi 29 mars 2018                    Fabrice Prigent                                             6/17
3.6. La sécurisation par chiffrement
         Saint Graal de la sécurité il y a quelques années, on s'aperçoit que le chiffrement n'est pas plus sûr que
les entrées du "tunnel" qu'il crée. Il s'agit toutefois d'un pré requis pour permettre de faire transiter des données
sans risquer l'observation et l'interception des communications.

         3.6.1.L'installation du chiffrement
          En apache, le chiffrement se fait de manière relativement simple : l'écoute se fera sur le port dédié 443.
Le reste dépendra des certificats obtenus, plus quelques options si l'on souhaite récupérer des variables dans les
certificats (le nom de l'utilisateur en particulier). Nous ne nous attarderons pas ici sur la génération des
certificats, qui relèverait d'un chapitre complet.
 
         ServerAdmin reseau@site.fr
         DocumentRoot /var/www/html_webmail/
         ServerName webmail.site.fr
         SSLEngine on
         SSLCertificateFile /etc/pki/tls/certs/webmail.site.fr.crt
         SSLCertificateKeyFile /etc/pki/tls/private/webmail.site.fr.key
         SSLCertificateChainFile /etc/pki/tls/certs/cachain.txt
         
                  SSLOptions +StdEnvVars
         
         ErrorDocument 402 https://webmail.site.fr/
         
                  SSLOptions +StdEnvVars
         
         ErrorLog logs/error_webmail_log
         CustomLog logs/access_webmail_log combined
 
        3.6.2.Le choix des algorithmes
        Les algorithmes de chiffrement se négocient au début de la session entre serveur et
client. Mais de nombreux critères vont présider au choix de la « CIPHERSUITE »
    •    les protocoles cassés,
    •    les incompatibles avec des navigateurs parce que trop vieux ou trop récents,
    •    les insuffisants,
    •    ceux spécialisés en chiffrement symétriques ou asymétriques,
    •    ceux spécialisés en signature (hashage),
    •    les performants mais pas efficaces, etc.
        Il est conseillé par certains experts (cf https://wiki.mozilla.org/Security/Server_Side_TLS) de
sélectionner la ciphersuite suivante (en mode « intermédiaire » pour des clients pas trop
vieux).
 SSLProtocol all -SSLv2 -SSLv3
 SSLCipherSuite          ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-
 ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-
 AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-
 SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-
 AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-
 AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-
 CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-
 SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS
 SSLHonorCipherOrder on
 # OCSP Stapling, only in httpd 2.3.3 and later
 SSLUseStapling          on
 SSLStaplingResponderTimeout 5
 SSLStaplingReturnResponderErrors off
 SSLStaplingCache        shmcb:/var/run/ocsp(128000)

Version du jeudi 29 mars 2018                     Fabrice Prigent                                               7/17
La version pour les navigateurs modernes est celle-ci :
 SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
 SSLCipherSuite          ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-
 ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-
 AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-
 SHA256:ECDHE-RSA-AES128-SHA256
 SSLHonorCipherOrder     on
 SSLCompression          off
 SSLSessionTickets       off

 # OCSP Stapling, only in httpd 2.3.3 and later
 SSLUseStapling          on
 SSLStaplingResponderTimeout 5
 SSLStaplingReturnResponderErrors off
 SSLStaplingCache        shmcb:/var/run/ocsp(128000)
        On pourra avec profit, s'intéresser aux outils de tests de validité de https://www.ssllabs.com (compter 10
minutes complètes de test).

         3.6.3.Le HSTS et le certificate pinning
         Certains pirates peuvent tenter 2 actions techniques pour contourner le chiffrement : faire
« redescendre » la communication en HTTP, et éventuellement (plus complexe) obtenir un vrai-faux certificat
(auprès d’une autorité complaisante ou piratable, ou bien en intégrant une fausse AC sur un poste). On se
prémunit de ceci en imposant le HTTPS grâce au HSTS et en ajoutant une durée de validité au certificat
(attention, il faut que le navigateur accepte ces 2 ordres).
 LoadModule headers_module modules/mod_headers.so

 #la valeur de max-age correspond ici a 180 jours
 Header always set Strict-Transport-Security "max-age=15552001; includeSubdomains;"
 
         3.6.4.Les contournements du chiffrement
         Ils sont nombreux, malgré les apparences, et sont généralement liés à l'utilisateur qui fait trop confiance,
ou trop peu attention à ce qu'il voit.

              3.6.4.1.Problème du domaine "valide" à l'orthographe similaire
        Un pirate peut acheter un certificat auprès d'une société (Verisign par exemple) pour le domaine
bnppariba.com. L'utilisateur recevra un certificat, valide, qu'il acceptera.
        On pourra aussi regarder du côté des codes utf8 qui permettent l’utilisation d’une graphie identique,
mais d’un sens différent.

              3.6.4.2.L'attaque Man in the Middle
        Un pirate peut, en se plaçant de manière réelle ou virtuelle entre la victime et le site sécurisé, intercepter
les communications. Il va présenter un pseudo certificat à l'utilisateur, qui va l'accepter. Le pirate va
retransmettre les données de la session au vrai site en les chiffrant réellement, mais tout sera visible pour lui. De
nombreux outils permettent de faire cela de manière très simple.

    3.7. Les processus d'authentification
         Il faut regarder avec attention les processus d'authentification, qui ne doivent en aucun cas être une
source supplémentaire d'intrusion. Il est par exemple très dangereux de faire circuler les mots de passe en clair
sur le réseau, ce qui implique le chiffrement des échanges d'authentification. Mais cela ne suffit pas : si la
personne croit parler au serveur d'authentification, alors qu'il s'agit d'un pirate (notion de « rogue » tel que les
rogue AP en WiFi), il faut absolument que la personne le sache, et qu'elle n'ait pas pris l'habitude d'accepter
n'importe quel certificat ! Ce qui signifie utiliser une VRAIE notion de certificat et donc d'IGC (Infrastructure de
Gestion de Clés), aussi appelée PKI (Public Key Infrastructure).

Version du jeudi 29 mars 2018                      Fabrice Prigent                                               8/17
3.7.1.Le cookie d'authentification
         Après une authentification, plus ou moins sérieuse, l'idée est souvent de stocker l'authentification dans
un cookie et de l'utiliser dans des applications non chiffrées. Le but pour le pirate consiste alors à récupérer le
cookie d'authentification pour le rejouer immédiatement. C'est une action tout à faire faisable grâce aux attaques
dites XSS (Cross Site Scripting).
         A minima, il faudra, pour limiter les conséquences de ce vol, stocker dans le cookie, ou dans les
variables de sessions dont il dépend, l'IP d'origine de la session, puis de refuser les connexions qui viennent
d'une autre IP. Cela se passera certainement moins bien si la victime et l'attaquant ont la même adresse IP
extérieure (NAT, proxy-cache, etc.), ou si l'IP est "changeante" (voir le protocole DHCP).

         3.7.2.Le SSO
         Acronyme de Single Sign On, il décrit un mécanisme d'authentification unique permettant à l'utilisateur
de ne rentrer qu'une seule fois son mot de passe pour plusieurs applications (souvent web). Cela recouvre
plusieurs techniques basées quasiment toutes sur un serveur d'authentification.
          Dans un cas le serveur connaît tous les mots de passe et les fournit à l'application qui le demande. Ceci
est particulièrement dangereux : l'application va être au courant du login et du mot de passe. Si elle est pirate ou
piratée, ceux-ci vont être diffusés.
       Dans le second, le serveur fournit l'identité du client et confirme son authentification à l'application
demandeuse (voir par exemple le projet CAS). L'application ne connaît donc pas le mot de passe.

         3.7.3.Les fédérations d'identité
         Les fédérations d'identité ont pour but de permettre à un individu, faisant partie d'un organisme A d'être
reconnu par un autre organisme B pour accéder à certains de ses services. B (appelé fournisseur de services),
lorsque l'individu le contacte, va demander à A de confirmer son identité, par exemple grâce à un SSO. B ne
connaîtra ni le login, ni le mot de passe de l'individu, mais, parce qu'il fait confiance à A, lui accordera des
droits. Deux projets sont en lice :
         http://shibboleth.internet2.edu (shibboleth)
         http://www.projectliberty.org (liberty alliance)

         3.7.4.Les authentifications multi-facteurs
         Il est parfois utile d’utiliser l’authentification multi-facteurs, celle-ci se base en plus de ce que l’on sait
(un mot de passe) un élément que l’on est (biométrique), ou que l’on a (un token). Cela peut être fait par SMS,
mais les banques ne sont plus si sures que cela marche (roaming étranger pour intercepter les SMS).
         Ce qui est relativement utilisable : googleauthenticator, freeotp (https://freeotp.github.io/) pour utiliser
les smartphones, la partie serveur est disponible ici : https://github.com/archiecobbs/mod-authn-otp.

    3.8. Les reverse-proxy

         3.8.1.Le mod_proxy
       Le mod_proxy va permettre de placer un apache en frontal d'un autre serveur HTTP, de type apache ou
non. Cela va décharger le serveur protégé d'un certain nombre d'actions de sécurité (voir le module
mod_security), et éventuellement du chiffrement des communications.
         Le serveur frontal n'ayant qu'un rôle de relais, on va pouvoir lui faire subir une cure d'amaigrissement,
toujours profitable, et lui restreindre ses possibilités de communications. Il deviendra alors moins vulnérable, et
moins capable d'actions néfastes.
         Afin de pouvoir faire du reverse-proxying en toute circonstance, on regardera du coté de mod_ext_filter
pour faire une réécriture des urls, même dans les javascript.
         La configuration d'un reverse-proxy pouvant être assez longue, nous laisserons le lecteur faire ses
propres recherches.

Version du jeudi 29 mars 2018                      Fabrice Prigent                                                 9/17
3.8.2.Un reverse-proxy de sécurité : Vulture
          Il existe un reverse-proxy de sécurité complet qui existe en GPL : il s'agit de Vulture, disponible sur
http://vulture.open-source.fr. Il combine les fonctionnalités de l'ensemble des outils précédents, puisqu'il les
intègre. Il est aussi capable de faire du SSO.

            3.8.3.Un reverse-proxy SSO : LemonLDAP
        Lemonldap est un reverse-proxy qui permet de créer un serveur apte à gérer une authentification unique
pour un ensemble d'applications web. Il est disponible sur http://LemonLDAP.sourceforge.net.

            3.8.4.NGINX
          Serveur web d'origine russe il est réputé pour sa faible empreinte mémoire et CPU, ainsi que pour sa
rapidité. On l'utilise soit en remplacement, soit en reverse-proxy de Apache. http://www.nginx.org. Sa faible
modularité et sa rigidité en font un outil dur, mais terriblement efficace.
         Certains le coupleront, à profit, avec son module NAXSI qui fait du filtrage positif (on n'autorise que ce
que l'on connaît) contrairement à mod_security.

4. Paramétrer PHP

    4.1. Les autres paramétrages
            D’autres variables permettent de limiter encore les droits des scripts PHP.

            4.1.1.open_basedir
            Permet de limiter l’ouverture des fichiers à un répertoire donné. Attention à bien le terminer par /.
 open_basedir="/var/www/html/:/var/www/html_toto:/usr/local/php"

            4.1.2.allow_url_fopen et allow_url_include
            Permet d’empêcher de chercher des urls à distance
 allow_url_fopen=Off
 allow_url_include=Off

            4.1.3.disable_functions
            On peut désactiver des fonctions. Est-il indispensable de laisser les commandes system et exec
actives ?
 enable_dl         = On
 disable_functions = system, exec, shell_exec, passthru, phpinfo, show_source, popen,
 proc_open
 disable_functions = fopen_with_path, dbmopen, dbase_open, putenv, move_uploaded_file
 disable_functions = chdir, mkdir, rmdir, chmod, rename
 disable_functions = filepro, filepro_rowcount, filepro_retrieve,
 posix_mkfifodisable_functions

            4.1.4.disable_classes
            Idem, mais avec les classes.

            4.1.5.Limiter les informations pour le pirate
          On empêche l'affichage d'informations trop précises sur la version de php, les erreurs qui ne doivent pas
s'afficher dans le navigateur mais dans les logs.
 expose_php                   = off
 display_errors               = off
 #     Si vous voulez         mettre les messages dans le syslog.
 # error_log                  syslog

Version du jeudi 29 mars 2018                        Fabrice Prigent                                                10/17
error_log                 /var/log/httpd/error_php.log
 report_memleaks           = On
 track_errors              = Off
 html_errors               = Off

         4.1.6.Protéger les sessions
 session.auto_start      = Off
 session.save_path       = /path/PHP-session/
 session.name            = myPHPSESSID
 session.hash_function   = 1
 session.hash_bits_per_character = 6
 session.use_trans_sid   = 0
 session.cookie_domain   = full.qualified.domain.name
 session.cookie_lifetime = 0
 session.cookie_secure   = On
 session.cookie_httponly = 1
 session.use_only_cookies= 1
 session.cache_expire    = 30
 default_socket_timeout = 60

         4.1.7.Paramètres de limitation divers
         Afin de limiter les dégâts d'un déni de service (D.O.S.) on peut mettre des contraintes sur les ressources.
En voici quelques unes :
 max_execution_time = 30 ; Temps maximum d'éxecution en secondes
 memory_limit = 8M ; taille mémoire maximale par script

         4.1.8.Les modules de protection
         Quelques modules d’aide au filtrage des variables ont été développés pour PHP, on pourra se reporter
en particulier à http://www.phpclasses.org/browse/class/78.html qui les recense. En voici un.

              4.1.8.1.HTMLPURIFIER
         Il s'agit d'une classe php qui va vérifier la conformité de la page et la nettoyer de tout XSS qui pourrait
être présent avant son affichage. Elle est disponible sur http://htmlpurifier.org/ et son utilisation est relativement
simple.

              4.1.8.2.Suhosin
         Ceci est un module pour PHP, qui va en combler les trous les plus flagrants. Il se compose de deux
parties indépendantes : un patch et une extension. On peut le trouver sur http://www.hardened-
php.net/suhosin.127.html.
         Le patch va nécessiter de recompiler php (et tous ses modules). L'extension, quant à elle, peut s'utiliser
seule sans modification. Mais elle ne donne sa pleine puissance qu'avec le patch.
         Il est livré avec les debian, mais n’est plus activement suivi depuis PHP5..

              4.1.8.3.Snuffleupagus
         C’est un module développé par la société NBS System, déjà auteur de naxsi. C’est le fils spirituel de
suhosin : il permet de protéger PHP 7 en bloquant des fonctions dangereuses, et en « patchant » virtuellement les
applications web de manière globale. Il est disponible sur https://github.com/nbs-system/snuffleupagus.

         4.1.9.Les frameworks de développements
         Ils permettent, pour les plus sérieux, de gérer une part importante des processus de sécurité :
            ●    gestion des tokens POST contre les CSRF
            ●    protection contre certains XSS
            ●    vérification des injections SQL (ou LDAP)

Version du jeudi 29 mars 2018                      Fabrice Prigent                                              11/17
●    déplacement des répertoires hors du répertoire WWW
         On y trouvera
             ●    cakePHP
             ●    symfony-project

5. MySQL

    5.1. Installer MySQL
         MySQL peut très facilement se placer en chroot, afin de limiter ses actions en cas d'intrusion :
 mkdir -p /chrooted/chrooted-mysqld
 cd /chrooted/chrooted-mysqld/
 mkdir -p var/lib var/run lib etc tmp
 grep "mysql:" /etc/passwd >etc/passwd
 chmod 1777 tmp
 cp -rp /var/lib/mysql var/lib/
 cp -rp /var/run/mysqld var/run
 ldd /usr/libexec/mysqld
 #Créer les répertoires repérés
 #copier les fichiers repérés dans les répertoires
 mkdir -p lib/i686 usr/lib
 cp -rp /lib/…..
 cp -rp /lib/libnss* lib/
 # ajouter dans /etc/my.cnf
 chroot=/chrooted/chrooted-mysqld

    5.2. Configurer MySQL
         MySQL est le support de beaucoup d'applications web. Les questions qui se posent :
        Le MySQL doit-il être accessible hors de la machine qui l'héberge ? Non à 90%, donc on n'attache le
processus qu'à l'interface localhost, ou, au pire, on utilise les iptables, ou tout autre procédé de pare-feu local
pour limiter l'accès aux seules machines autorisées.
         Est-il normal que l'accès administrateur se fasse sans mot de passe ou avec un mot de passe par défaut ?
Mettez en un nouveau. On peut utiliser pour cela la commande mysql_secure_installation, qui vous permettra
d'enlever les bases et les comptes inutiles.
         Désactiver les fonctions trop dangereuses en ajoutant dans /etc/my.cnf
 set-variable=local-infile=0

    5.3. Utiliser MySQL
        Lors de la création de compte MySQL, séparez les comptes suivants les applications. Profitez-en pour
ne donnez que les droits nécessaires, et en les divisant éventuellement :
   ●    un compte d'administration (création des tables),
   ●    un compte en écriture
   ●    un compte en lecture.

    5.4. Piéger MySQL
        Créer des données "honeytokens" afin de faciliter le repérage de vol de données (pour un I.D.S. ou pour
des preuves devant un tribunal). Cela peut-être un compte, un enregistrement, etc.

    5.5. Travailler avec de vrais jeux de tests et pas de vraies données
         pydbgen permet de générer des jeux de test pour des infrastructures de préproduction et de test afin
d’être compatible avec la RGPD/GDPR.

Version du jeudi 29 mars 2018                    Fabrice Prigent                                             12/17
6. Programmer en PHP
          Une fois que l’on a pris en compte l’environnement de travail, mis en place les protections nécessaires
sur les scripts PHP et autres fichiers de configuration, un grand nombre de choses restent à regarder.

    6.1. Bien programmer
          Cela semble évident, mais certains réflexes s’oublient en programmation web. Les principes doivent
être les suivants :
   ●    Une variable doit toujours être affectée.
   ●    Vérifiez toujours le résultat des actions que vous effectuez
        La connexion à la base s'est-elle bien passée ?
        Y a-t-il un résultat dans votre requête ?
        La valeur est-elle définie ?

    6.2. Contrôler les variables
          La grande majorité des failles actuelles proviennent des variables qui nous sont fournies par
l’utilisateur. Le contenu des variables peut être dangereux. Voici quelques méthodes pour vérifier les variables.
   ●    N’utilisez pas les variables directement.
   ●    Faites attention
   ●    Si une variable est sensée avoir un format particulier, vérifiez le grâce aux expressions régulières :
 if (preg_match('/^[A-Z0-9\._\-]{1,200}@[A-Z0-9\._-]{1,100}\.[A-Za-z]{2,6}$/', $email))
       {suite_du_traitement();}
 else
       {die "erreur dans la variable email\n";}; // Remarquez que l'on n'utilise pas la
 valeur de $email.
   ●    Dans le cas de variables énumératives, qui ne peuvent prendre leurs valeurs que dans un intervalle, on
        utilise soit les "elseif", soit un tableau intermédiaire :
 $valeur=$_GET['valeur'];
 if ($tab_valeurs_autorisees[$valeur])
         {$valeur=$tab_valeurs_autorisees[$valeur]);}
 else
        {die "valeur est incorrecte"}
   ●  Tente-t-on d'utiliser la variable pour envoyer du javascript ? Il faut enlever les caractères ""; Le
      mieux toutefois étant d’utiliser htmlentities (voir plus bas).
   ● Vous utilisez une variable qui contient nom de fichier, ou dont on dérive un nom de fichier, répertoire,
      ou commande ? Effacez les .. / ; * et autre ? Mieux n’utilisez QUE les caractères autorisés.
   ● Une base de données est impliquée ? Attention aux séquences dangereuses
      -- ou # (qui sont des commentaires)
      ; (qui est un séparateur de commande)
      l'espace (très utilisé si vous n'avez pas "quoté" la variable)
      les mots clés SQL (UNION étant le plus prisé).

    6.3. Les pièges à éviter

        6.3.1.Le stockage des mots de passe
         Doit-on stocker les mots de passe en clair ? A 99% c'est non, car on ne veut que comparer un mot de
passe, pas le lire. Il faut utiliser les commandes de hashage telles que SHA2 ou SHA3 et ne stocker que le haché
d'un mot de passe dans les bases de données.
        De plus, le hash doit inclure un partie « sel » pour éviter le phénomène des « rainbow tables ».
        Le sel doit être différent pour chaque utilisateur, sinon toute personne pouvant créer un compte va
pouvoir deviner le sel, puis reconstituer une rainbow table : Une Radeon 7970 est capable de faire 400 millions
de SHA256 à la seconde. La bonne solution en php est l’utilisation de la fonction password_hash et son pendant
password_verify avec les paramétrage par défaut. Ces procédures vont stocker un mot de passe irréversible et
quasiment impossible à comparer avec un dictionnaire dans un temps "raisonnable". Il sera alors impératif de

Version du jeudi 29 mars 2018                     Fabrice Prigent                                                13/17
penser que ce mot de passe "haché" devra être stocké dans un champ de 60 octets minimum, 256 étant le choix
recommandé
 hash=password_hash("motdepassedelamortquitue", PASSWORD_DEFAULT);
 if (password_verify('motdepassedelamortquitue', $hash)) ;
       {echo "Vous êtes autorisé" ;}

         6.3.2.L'emplacement des fichiers de données
        Les fichiers de données doivent-ils être dans un répertoire accessible par le web ? à 99% non, donc
déplacez les (dans la limite des processus de confinement de votre serveur).

         6.3.3.Le signalement des erreurs
         La tentation, si l’on fait des tests de variables est de signaler exactement le problème à l’utilisateur.
Erreur ! ! ! Le programme doit stopper le traitement, proprement, mais sans réutiliser la variable. Un pirate
pourrait utiliser cette protection pour entrer dans le système. Un comble !

    6.4. Contre les XSS : htmlentities() ou son synomyme htmlspecialchars()
        Pour se protéger des XSS, une idée simple est d'encoder les caractères particuliers en leur équivalent
web. Cela empêche leur interprétation par les navigateurs.

    6.5. Contre le SQL-Injection: mysql_real_escape_string()
         Afin d'éviter l'exécution de champs variables, il peut être utile "d'échapper", c'est à dire rendre
inoffensifs, les caractères dangereux pour MySQL. C'est le rôle de cette commande, qui doit être préférée à sa
petite sœur mysql_escape_string(). Pour être utilisée, une connexion à la base doit avoir été faite. Une version
postgresql existe.
 $link=mysql_connect('mysql_host','mysql_user','mysql_password') or die(mysql_error());
 $query=sprintf("SELECT * FROM users WHERE users='%s'",mysql_real_escape_string($user));
 mysql_query($query);

    6.6. Utilisation des prepare/execute
         Mieux encore, on peut aussi utiliser les commandes paramétrées plus résistantes encore, qui ont de plus
l'avantage d'accélérer les traitements :
 require_once("DB.php");
 $db = &DB::connect("mysql://user:pass@host/database1");
 $p = $db->prepare("SELECT * FROM users WHERE username = ?");
 $db->execute( $p, array($_GET['username']) );
         et
 $db->query( "SELECT * FROM users WHERE username = ?", array($_GET['username']) );
         ou encore, avec l'extension mysqli (php5).
 $db =   new mysqli("localhost", "user", "pass", "database");
 $stmt   = $db -> prepare("SELECT priv FROM testUsers WHERE username=? AND password=?");
 $stmt   -> bind_param("ss", $user, $pass);
 $stmt   -> execute();

7. Les expressions régulières
         Avant toute chose : http://www.commitstrip.com/fr/page/10/
         Les expressions régulières sont un formidable moyen de contrôle des données. Elles travaillent sur la
notion de schéma suite de caractères, et non de chiffres ou de mots.
          Il existe, pour simplifier, deux types d'expressions régulières : les posix et les expressions perl. Celles-
ci, plus complètes et puissantes sont de plus en plus utilisées. En php on vérifiera la présence d'une chaîne grâce
à la fonction preg_match. Nous ne voyons ici qu'une toute petite partie des expressions régulières.

Version du jeudi 29 mars 2018                      Fabrice Prigent                                              14/17
7.1. Les caractères
        Un caractère représente généralement son propre caractère a représente la lettre a.
        [a-z] représente tout caractère entre a et z
        [B-E] représente tout caractère entre B et E
        [0-9] représente tout caractère entre 0 et 9
        . (le point) représente n'importe quel caractère

    7.2. Les opérateurs
         Les opérateurs permettent de préciser le nombre de fois où un caractère apparaît ou bien de faire des
opérations de concaténation ou de choix
        AB représente la séquence des caractères A et B
        A* représente n'importe quelle chaine de caractères contenant des A (de 0 à autant que l'on veut)
        A+ représente n'importe quelle chaine ayant au moins un A
        A? représente le fait d'avoir zéro ou un A.
        A{3} signifie trois A de suite
        A{2,5} signifie que l'on peut avoir de deux à cinq A.
        A|B signifie que l'on peut avoir A ou B

    7.3. Les autres éléments
        ^ représente le début de la chaîne de caractères
        $ représente la fin de la chaîne de caractères
        ( ) les parenthèses permettent d'appliquer un opérateur à un ensemble de caractères.
         \ permet de faire perdre à un opérateur ou à un caractère son "caractère" spécial. \. représente le
caractère point.

    7.4. Les commutateurs
        Certaines options dans la sélection permettent de préciser les traitements lors de la recherche
        i pour ne pas faire de distinction entre majuscule et minuscule
        o pour ne "compiler" la règle de détection qu'une fois (ce qui est plus rapide)

    7.5. Quelques exemples
 If preg_match("/Fabrice/",$chaine)
        le prénom Fabrice est-il présent dans $chaine (fabrice ne sera par repéré car le f n'est pas majuscule)
 If preg_match("/h(ab)*/i",$chaine)
        Toute chaîne composée de h suivi de 0 à x fois de ab sera repérée. Les lettres pouvant être en minuscule
ou en majuscule.
 If preg_match("/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/",$chaine)
        Toute chaîne de caractère contenant une adresse IP (ou du moins qui y ressemble).
 If preg_match("/[0-9]{1,4}((bis)|(ter))?/",$chaine)
        Toute chaîne qui contient un numéro de rue (avec éventuellement le bis ou le ter)
 If preg_match("/^[0-9]{5}$/",$chaine)
        La chaîne est égale à un code postal (ni plus ni moins).

Version du jeudi 29 mars 2018                     Fabrice Prigent                                            15/17
8. Les outils de tests

    8.1. Les tests externes
         Ils se comportent comme les pirates en essayant d'obtenir des informations en étant sans droits
particuliers.

         8.1.1.Nmap
         Grand classique. Il permet de repérer les ports ouverts, la version du logiciel qui tourne (en se basant sur
les bannières). Il est disponible sur http://insecure.org

         8.1.2.Wapiti
         Scanneur de vulnérabilités        inconnues,    principalement    pour    XSS.    Il     est        disponible     sur
http://wapiti.sourceforge.net/.

         8.1.3.Autres outils
         NIKTO             http://www.cirt.net/code/nikto.shtml (vulnérabilités web connues)
         Nessus            http://www.nessus.org (vulnérabilités connues)
         Springenwerk      http://www.springenwerk.org (failles XSS)
          skipfish         http://www.googlecode.com/skipfish        (scanner     « exhaustif »         et     très   simple
d’utilisation).
         sqlmap            http://sqlmap.sourceforge.net/ (SQL injection)

         8.1.4.Les sites de surveillance et leurs abonnements
          Plusieurs services existent sur internet pour ausculter les sites. N'hésitez pas à vous abonner pour être
alerté si votre domaine a été piraté :
    •    https://www.pastbin.com
    •    https://www.haveibeenpwned.com
    •    https://www.zone-h.org

    8.2. Les tests internes
        Ce sont les validateurs de code. Ils essaient de repérer dans les codes sources les fonctions et méthodes
de programmation dangereuses.

         8.2.1.PHPsecAudit
         Il est disponible sur http://developer.spikesource.com/projects/phpsecaudit

         8.2.2.Pixy

         Auditeur de code PHP en java pour faille SQL et XSS http://pixybox.seclab.tuwien.ac.at/pixy/

         8.2.3.RATS
         http://www.securesoftware.com/resources/download_rats.html

9. URLographie
         Voici quelques URLs sur la bonne protection des scripts en PHP

Version du jeudi 29 mars 2018                     Fabrice Prigent                                                         16/17
•   http://www.owasp.org/
    •   https://www.owasp.org/index.php/PHP_Security_Cheat_Sheet
    •   http://fr2.php.net/manual/fr/security.php
    •   http://phpsec.org/
    •   http://www.phpsecure.info/
    •   http://shiflett.org/php-security.pdf
    •   http://regexlib.com/
    •   http://www.securityfocus.com/infocus/1694
    •   http://dev.mysql.com/doc/mysql/en/Security.html
    •   http://www.securityfocus.com/infocus/1726
    •   http://www.kitebird.com/articles/ins-sec.html
    •   http://www.certa.ssi.gouv.fr/site/CERTA-2007-INF-002.pdf
    •   http://www.certa.ssi.gouv.fr/site/CERTA-2004-INF-001/index.html
    •   http://www.apachesecurity.net/ (horse book de chez O'Reilly)
    •   http://en.wikibooks.org/wiki/Programming:PHP:SQL_Injection

10.Autres informations
        Ce document est largement perfectible et surtout, il a été fait pour débuter en sécurité web, et limiter
80 % des attaques. Contre un agresseur déterminé, regardez le site de l’OWASP. N
        'hésitez pas à me signaler les erreurs et les améliorations à l'adresse fabrice.prigent@laposte.net.
        Il est disponible sous licence creative commons http://creativecommons.org/licenses/by-nc-sa/2.0/fr/

Version du jeudi 29 mars 2018                    Fabrice Prigent                                               17/17
Vous pouvez aussi lire