$ curl -s -o /dev/null -w "%{http_code}\n" -F "photo=@chat.jpg" https://site.lan/upload.php
500
$ sudo tail -1 /var/log/nginx/error.log
... FastCGI sent in stderr: "PHP message: PHP Warning: move_uploaded_file(/var/www/site/uploads/chat.jpg):
Failed to open stream: Permission denied in /var/www/site/upload.php on line 12" ...
$ sudo chmod -R 777 /var/www/site
$ curl -s -o /dev/null -w "%{http_code}\n" -F "photo=@chat.jpg" https://site.lan/upload.php
200
Ça marche. Et c’est exactement ça, le problème.
L’erreur a disparu en une commande, et la sécurité avec : n’importe quel compte de la machine peut désormais lire, modifier et exécuter tout le site.
Ce premier article pose les bases et donne la vraie réponse au Permission denied. Les bits spéciaux et les ACL viendront dans une partie 2.
L’essentiel en 30 secondes
- Chaque fichier a un propriétaire, un groupe, et des droits pour trois classes : propriétaire, groupe, autres.
r= 4,w= 2,x= 1. On additionne :7=rwx,5=r-x,6=rw-.- Sur un répertoire,
xveut dire « traverser » etwveut dire « créer et supprimer des fichiers dedans ».- Le noyau applique une seule classe : la première qui correspond.
- Face à un
Permission denied, on corrige le propriétaire ou le groupe. On n’ouvre pas tout.
Lire un ls -l
$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 68248 mars 23 2025 /usr/bin/passwd
Un caractère de type (- fichier, d répertoire, l lien), puis trois blocs de trois lettres : propriétaire, groupe, autres. Ensuite viennent le propriétaire et le groupe du fichier. Le s de passwd est un bit spécial, on le garde pour la partie 2.
r, w, x : pas le même sens sur un répertoire
C’est la source de la plupart des incompréhensions. Un répertoire n’est qu’une liste de noms, et ses droits s’appliquent à cette liste.
| Droit | Sur un fichier | Sur un répertoire |
|---|---|---|
r |
lire | lister les noms |
w |
modifier | créer, renommer, supprimer des entrées |
x |
exécuter | traverser (cd, ouvrir un fichier dedans) |
Deux conséquences à retenir :
- Le
wdu dossier suffit pour supprimer un fichier, même en lecture seule et appartenant à quelqu’un d’autre. - Il faut
xsur chaque répertoire du chemin. Un seul parent en700et c’estPermission denied, même si le fichier est en644.
Ce second point provoque une bonne partie des 777 de panique. namei -l affiche toute la chaîne d’un coup :
$ namei -l /var/www/site/index.html
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwx------ jemadhar jemadhar site ← le coupable
-rw-r--r-- jemadhar jemadhar index.html
L’octal en une table
Trois chiffres, dans l’ordre propriétaire / groupe / autres. Les seuls à connaître par cœur :
| Mode | Droits | Pour quoi |
|---|---|---|
755 |
rwxr-xr-x |
dossiers publics, scripts |
644 |
rw-r--r-- |
fichiers de contenu |
750 / 640 |
rwxr-x--- / rw-r----- |
dossiers et fichiers d’un service |
700 / 600 |
rwx------ / rw------- |
~/.ssh, clés privées |
Pour corriger une arborescence entière, le X majuscule ajoute x aux répertoires seulement, pas aux fichiers :
chmod -R u=rwX,g=rX,o= /srv/appli # dossiers en 750, fichiers en 640
Le noyau prend le premier qui correspond
Sous Windows, les droits s’additionnent. Sous Linux, non : le noyau teste les classes dans l’ordre et s’arrête à la première qui correspond.
D’où ce cas étonnant :
$ ls -l rapport.txt
----rw-r-- 1 jemadhar equipe 512 oct. 6 10:00 rapport.txt
$ cat rapport.txt
cat: rapport.txt: Permission non accordée
Je suis le propriétaire, donc seul le premier bloc --- s’applique à moi. Tout le monde peut lire ce fichier, sauf moi.
Pourquoi 777 est vraiment la bête
« Sur mon infra perso, qui va exploiter ça ? » Trois scénarios très concrets :
- Le script cron. Un
sauvegarde.shen777, lancé par root. N’importe quel compte local y ajoute une ligne, qui tournera en root à la prochaine exécution. - Le dossier d’upload. Un site en
777, un formulaire mal filtré : un.phpdéposé est aussitôt exécutable. C’est le webshell le plus banal qui soit. - Le partage. Un dossier commun en
777: chacun peut effacer les fichiers des autres, puisque supprimer, c’est écrire dans le dossier.
777 ne résout pas le problème : il le fait disparaître en même temps que la sécurité. Le vrai problème, c’était presque toujours un mauvais propriétaire.
La vraie réponse : qui est le processus, à qui est le fichier ?
Trois commandes pour poser le diagnostic :
ps -o user,cmd -C nginx # sous quel compte tourne le service ?
id www-data # dans quels groupes est ce compte ?
stat -c '%U:%G %a' /var/www/site # à qui appartient le dossier ?
Puis on corrige l’appartenance, pas les bits. Exemple complet pour un site web : le code appartient au compte de déploiement, le serveur web le lit via son groupe, et il n’écrit que dans uploads/.
chown -R deploy:www-data /var/www/site
chmod -R u=rwX,g=rX,o= /var/www/site # lecture seule pour le serveur web
chmod 770 /var/www/site/uploads # écriture uniquement ici
sudo -u www-data touch /var/www/site/uploads/test && echo OK
Si le site est compromis, l’attaquant ne peut pas réécrire le code. Côté serveur web, on interdit en plus l’exécution de PHP dans uploads/.
Un piège fréquent : après un usermod -aG www-data deploy, le nouveau groupe n’est actif qu’à la prochaine connexion ou au redémarrage du service. Sans ça, on croit que rien ne marche et on finit en 777.
umask : les droits à la naissance
Un programme qui crée un fichier demande 666, ou 777 pour un dossier. La bête est là dès la naissance. Le noyau retire ensuite les bits de l’umask :
666 - 022 = 644 (fichier) 777 - 022 = 755 (dossier)
L’umask par défaut, 022, laisse tout le monde lire ce que vous créez. L’ANSSI recommande 0077 pour les shells utilisateurs et 0027 pour les services (directive UMask=0027 dans l’unité systemd).
Les erreurs classiques
| Erreur | À faire à la place |
|---|---|
chmod -R 777 pour débloquer une appli |
corriger propriétaire et groupe |
chmod -R 755 sur tout un site |
chmod -R u=rwX,g=rX,o=rX |
Permission denied avec un fichier en 644 |
namei -l : un parent bloque la traversée |
usermod -aG sans se reconnecter |
nouvelle session ou redémarrage du service |
Clé SSH privée en 644 |
chmod 600, et 700 sur ~/.ssh |
Ce que je retiens
- Un
Permission deniedest une question d’identité avant d’être une question de bits.- Sur un dossier,
wpermet de supprimer etxde traverser. Tout le chemin compte.- Pas d’addition : le noyau applique la première classe qui correspond.
chownpour donner,Xmajuscule pour corriger en masse, umask pour partir sur de bonnes bases.777fait disparaître l’erreur, et la sécurité avec.
Dans la partie 2 : le s de passwd, le t de /tmp, le dossier partagé propre avec setgid, et les ACL quand un propriétaire et un groupe ne suffisent plus.
Sources et références
- chmod(1) — Linux manual page : modes octal et symbolique,
Xmajuscule. - path_resolution(7) — Linux manual page : droit de traversée sur chaque répertoire du chemin, ordre de vérification propriétaire / groupe / autres.
- namei(1) — Linux manual page : affichage des droits le long d’un chemin.
- ANSSI, Recommandations de configuration d’un système GNU/Linux (ANSSI-BP-028, v2.0) : R36, umask 0077 pour les shells, 0027 pour les services.