13. Permissions avancées#

La gestion des permissions sous Linux dépasse largement le modèle rwx de base appris en introduction. Ce chapitre explore les mécanismes complémentaires : bits spéciaux SUID/SGID/sticky, listes de contrôle d’accès POSIX, capabilities du noyau, attributs étendus et umask. La maîtrise de ces outils est indispensable pour sécuriser un système en production.


Rappel du modèle DAC#

Le contrôle d’accès discrétionnaire (DAC, Discretionary Access Control) est le modèle de base de Linux. Chaque fichier appartient à un propriétaire (UID) et à un groupe (GID). Les permissions sont définies pour trois catégories : le propriétaire (u), le groupe (g), les autres (o).

Bits de permission#

Chaque catégorie dispose de trois bits : lecture (r = 4), écriture (w = 2), exécution (x = 1).

-rwxr-x--- 1 alice devs 4096 mars 10 14:32 script.sh

Décomposition :

  • - : type régulier (d = répertoire, l = lien symbolique, c = char device…)

  • rwx : propriétaire peut lire, écrire, exécuter

  • r-x : groupe peut lire et exécuter

  • --- : les autres n’ont aucun droit

En octal, cette permission s’écrit 750 : 7 = rwx, 5 = r-x, 0 = ---.

UID/GID effectifs#

Lors de l’exécution d’un processus, le noyau maintient plusieurs identités :

Identité

Description

UID réel

L’utilisateur qui a lancé le processus

UID effectif

L’identité utilisée pour les vérifications d’accès

UID sauvegardé

Copie de l’UID effectif avant un changement temporaire

UID de système de fichiers

Utilisé spécifiquement pour les accès aux fichiers

Normalement, UID réel = UID effectif. Les bits spéciaux (SUID/SGID) modifient ce comportement.

Vérification des permissions#

Le noyau effectue la vérification dans l’ordre suivant :

  1. Si l’UID effectif du processus est 0 (root) → accès accordé (presque toujours)

  2. Si l’UID effectif correspond au propriétaire → bits du propriétaire appliqués

  3. Si le GID effectif correspond au groupe → bits du groupe appliqués

  4. Sinon → bits « autres » appliqués

Important

La vérification s’arrête à la première correspondance. Si un fichier appartient à alice avec les permissions ---rwxrwx, alice elle-même n’a aucun droit, même si elle fait partie du groupe.


SUID, SGID et sticky bit#

Ces trois bits occupent un quatrième octet dans la représentation octale des permissions.

SUID (Set User ID) — bit 4000#

Quand le bit SUID est positionné sur un exécutable, le processus s’exécute avec l’UID effectif du propriétaire du fichier, non de l’utilisateur qui le lance.

# Afficher les permissions de /usr/bin/passwd
ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 janv. 25 2023 /usr/bin/passwd

Le s en position exécution du propriétaire indique le SUID. Quand alice exécute passwd, le processus tourne avec l’UID 0 (root), ce qui lui permet de modifier /etc/shadow.

Positionner le SUID :

chmod u+s /chemin/executable
# ou en octal :
chmod 4755 /chemin/executable

Cas d’usage légitimes :

  • /usr/bin/passwd — modification du mot de passe (accès à /etc/shadow)

  • /usr/bin/sudo — élévation de privilèges contrôlée

  • /usr/bin/ping — accès aux raw sockets (sur anciens systèmes)

  • /usr/bin/su — changement d’identité

Dangers du SUID :

  • Un binaire SUID root mal écrit est un vecteur d’escalade de privilèges

  • Les scripts shell avec SUID sont ignorés par le noyau Linux (sécurité)

  • Tout fichier SUID root trouvé dans /tmp ou des répertoires utilisateur est suspect

Avertissement

Ne jamais positionner le bit SUID sur des interprètes (bash, python, perl) ou des éditeurs de texte. Cela offrirait immédiatement un accès root à quiconque peut exécuter le fichier.

SGID (Set Group ID) — bit 2000#

Sur un exécutable : le processus s’exécute avec le GID effectif du groupe propriétaire.

ls -l /usr/bin/write
-rwxr-sr-x 1 root tty 14328 avril 3 2022 /usr/bin/write

Le s en position exécution du groupe indique le SGID.

Sur un répertoire : tous les fichiers créés dans ce répertoire héritent du groupe du répertoire, quel que soit le groupe primaire de l’utilisateur créateur.

# Créer un répertoire partagé pour l'équipe devs
mkdir /srv/projet
chown root:devs /srv/projet
chmod 2775 /srv/projet

# Tout fichier créé aura le groupe devs

C’est le mécanisme fondamental des répertoires partagés en entreprise.

Sticky bit — bit 1000#

Sur un répertoire : un utilisateur ne peut supprimer ou renommer que ses propres fichiers, même s’il a le droit d’écriture sur le répertoire.

ls -ld /tmp
drwxrwxrwt 18 root root 4096 mars 24 09:15 /tmp

Le t final indique le sticky bit. Sans lui, n’importe quel utilisateur pourrait supprimer les fichiers des autres dans /tmp.

Positionner le sticky bit :

chmod +t /repertoire/partage
# ou en octal :
chmod 1777 /tmp

Note

Sur les anciens systèmes Unix, le sticky bit sur les exécutables conservait le programme en mémoire swap. Ce comportement est obsolète sous Linux moderne.


ACL POSIX#

Les ACL (Access Control Lists) POSIX permettent de définir des permissions pour des utilisateurs ou groupes supplémentaires, au-delà du modèle propriétaire/groupe/autres.

Prérequis#

Le système de fichiers doit être monté avec l’option acl (activée par défaut sur ext4 et XFS depuis le noyau 2.6).

# Vérifier le support ACL
tune2fs -l /dev/sda1 | grep "Default mount options"
Default mount options:    user_xattr acl

Consulter les ACL — getfacl#

getfacl /srv/projet/rapport.txt
# file: srv/projet/rapport.txt
# owner: alice
# group: devs
user::rw-
user:bob:r--
user:charlie:rw-
group::r--
group:admins:rw-
mask::rw-
other::---

Modifier les ACL — setfacl#

# Donner les droits r-x à l'utilisateur bob
setfacl -m u:bob:rx /srv/projet/script.sh

# Donner les droits rw- au groupe audit
setfacl -m g:audit:rw /srv/projet/rapport.txt

# Supprimer l'ACL d'un utilisateur
setfacl -x u:bob /srv/projet/script.sh

# Supprimer toutes les ACL d'un fichier
setfacl -b /srv/projet/fichier.txt

Masque ACL#

Le masque (mask) est l”union maximale des droits accordés aux utilisateurs nommés, groupes nommés et au groupe propriétaire. Il agit comme un plafond.

# Réduire le masque à r-- (lecture seule pour tous les ACL)
setfacl -m mask::r-- /srv/projet/rapport.txt

Important

Quand chmod est utilisé sur un fichier avec ACL, il modifie le masque ACL (pas les entrées individuelles). Les droits du propriétaire et des « autres » sont toujours modifiés directement.

ACL par défaut sur les répertoires#

Les ACL par défaut s’appliquent aux nouveaux fichiers et sous-répertoires créés dans un répertoire.

# Définir une ACL par défaut : bob a toujours r-x sur les nouveaux fichiers
setfacl -d -m u:bob:rx /srv/projet/

# Vérifier
getfacl /srv/projet/
# file: srv/projet/
# owner: root
# group: devs
user::rwx
group::rwx
other::---
default:user::rwx
default:user:bob:r-x
default:group::rwx
default:mask::rwx
default:other::---

Interaction avec ls#

La présence d’ACL est signalée par un + après les permissions dans ls -l :

ls -l /srv/projet/rapport.txt
-rw-rw----+ 1 alice devs 2048 mars 24 10:00 rapport.txt

Capabilities Linux#

Les capabilities fragmentent les privilèges root en unités granulaires. Au lieu de donner tous les droits root à un programme, on lui accorde seulement les capabilities dont il a besoin.

Ensembles de capabilities#

Chaque thread possède cinq ensembles de capabilities :

Ensemble

Description

Permitted

Capabilities que le thread peut activer

Effective

Capabilities actuellement actives (vérifiées par le noyau)

Inheritable

Capabilities transmissibles aux processus fils via execve

Bounding

Limite supérieure pour les capabilities héritables

Ambient

Capabilities héritées par les programmes non-SUID sans capabilities fichier

Capabilities importantes#

Capability

Usage

CAP_NET_BIND_SERVICE

Écouter sur un port < 1024 sans être root

CAP_NET_RAW

Utiliser des raw sockets (ping, tcpdump)

CAP_SYS_ADMIN

Nombreuses opérations système (mount, sethostname…)

CAP_DAC_OVERRIDE

Contourner les vérifications de permissions DAC

CAP_SETUID

Changer d’UID arbitrairement

CAP_CHOWN

Changer le propriétaire d’un fichier

CAP_SYS_PTRACE

Utiliser ptrace sur n’importe quel processus

CAP_KILL

Envoyer des signaux à n’importe quel processus

CAP_NET_ADMIN

Configuration réseau avancée

Avertissement

CAP_SYS_ADMIN est surnommée « la nouvelle root ». Elle englobe des dizaines d’opérations privilégiées. L’accorder est presque équivalent à accorder root.

Lire les capabilities — getcap#

# Capabilities d'un fichier
getcap /usr/bin/ping
/usr/bin/ping cap_net_raw=ep

La notation =ep signifie : la capability est dans les ensembles effective et permitted du fichier.

# Lister toutes les capabilities du système
getcap -r / 2>/dev/null

Modifier les capabilities — setcap#

# Permettre à nginx de s'attacher au port 443 sans root
setcap cap_net_bind_service=ep /usr/sbin/nginx

# Supprimer toutes les capabilities d'un fichier
setcap -r /usr/sbin/nginx

# Vérifier les capabilities d'un processus en cours
cat /proc/$(pgrep nginx | head -1)/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000400
CapEff: 0000000000000400
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

Les valeurs sont des bitmasks hexadécimaux. L’outil capsh --decode=0x0400 les traduit en noms lisibles.

Remplacement du SUID#

Au lieu du bit SUID root sur ping :

# Ancienne méthode (SUID root)
chmod u+s /usr/bin/ping

# Méthode moderne (capability ciblée)
chmod u-s /usr/bin/ping
setcap cap_net_raw=ep /usr/bin/ping

Attributs étendus#

Les attributs étendus (xattr) permettent d’associer des métadonnées supplémentaires aux fichiers, en dehors du modèle de permissions standard.

Attributs chattr/lsattr#

Le système chattr/lsattr utilise des attributs spécifiques au système de fichiers (ext2/ext3/ext4) :

# Voir les attributs d'un fichier
lsattr /etc/passwd
----i--------e-- /etc/passwd
# Rendre un fichier immuable (root uniquement)
chattr +i /etc/resolv.conf

# Rendre un fichier append-only
chattr +a /var/log/audit/audit.log

# Retirer l'attribut immuable
chattr -i /etc/resolv.conf

Attributs importants#

Attribut

Flag

Description

Immutable

+i

Aucune modification, suppression, renommage ou lien possible — même root

Append-only

+a

Écriture en mode ajout uniquement (idéal pour les logs)

No-dump

+d

Exclu des sauvegardes dump

Secure delete

+s

Écrasement sécurisé lors de la suppression (fiabilité dépend du SSD/HDD)

Synchronous

+S

Écriture synchrone immédiate sur disque

No-atime

+A

Pas de mise à jour de l’atime (performance)

Note

L’attribut +i protège même contre root. Pour modifier un fichier immutable, il faut d’abord retirer l’attribut avec chattr -i. Cela en fait un outil de protection contre les modifications accidentelles ou les attaques par ransomware sur les fichiers critiques.

Attributs étendus génériques (xattr)#

# Lire les xattr d'un fichier
getfattr -d /srv/projet/document.pdf

# Définir un xattr personnalisé (namespace user.*)
setfattr -n user.classification -v "confidentiel" /srv/projet/document.pdf

# Lire un xattr spécifique
getfattr -n user.classification /srv/projet/document.pdf
# file: srv/projet/document.pdf
user.classification="confidentiel"

umask#

L’umask est un masque de bits qui soustrait des permissions lors de la création de nouveaux fichiers et répertoires.

Principe de calcul#

La permission effective est : permission_maximale & ~umask

  • Fichiers : permission maximale = 666 (pas d’exécution par défaut)

  • Répertoires : permission maximale = 777

Avec umask 022 :

  • Fichier créé : 666 & ~022 = 666 & 755 = 644rw-r--r--

  • Répertoire créé : 777 & ~022 = 777 & 755 = 755rwxr-xr-x

Avec umask 027 :

  • Fichier créé : 666 & ~027 = 640rw-r-----

  • Répertoire créé : 777 & ~027 = 750rwxr-x---

Consulter et modifier l’umask#

# Voir l'umask courant
umask
# 0022

# Voir l'umask en notation symbolique
umask -S
# u=rwx,g=rx,o=rx

# Modifier l'umask pour la session
umask 027

Configuration persistante#

# Pour tous les utilisateurs (Debian/Ubuntu)
# /etc/profile ou /etc/profile.d/umask.sh
umask 022

# Pour un utilisateur spécifique
# ~/.bashrc ou ~/.profile
umask 027

umask via PAM#

# /etc/pam.d/common-session (Debian)
session optional pam_umask.so umask=027

# /etc/login.defs
UMASK 027

umask pour les services systemd#

# Dans l'unité systemd du service
[Service]
UMask=0027

Tip

Pour les services qui créent des fichiers sensibles (clés, tokens), utilisez umask 0077. Les fichiers seront en 600 et les répertoires en 700, inaccessibles aux autres utilisateurs.


Audit des permissions#

Trouver les fichiers SUID/SGID#

# Tous les fichiers SUID
find / -perm -4000 -type f 2>/dev/null

# Tous les fichiers SGID
find / -perm -2000 -type f 2>/dev/null

# SUID ou SGID
find / -perm /6000 -type f 2>/dev/null

# SUID root uniquement (les plus dangereux)
find / -user root -perm -4000 -type f 2>/dev/null

Détecter les permissions dangereuses#

# Fichiers world-writable (hors /tmp et /proc)
find / -perm -0002 -type f ! -path "/proc/*" ! -path "/tmp/*" 2>/dev/null

# Fichiers sans propriétaire (UID/GID non existant)
find / -nouser -o -nogroup 2>/dev/null

# Répertoires world-writable sans sticky bit
find / -type d -perm -0002 ! -perm -1000 ! -path "/proc/*" 2>/dev/null

Vérifier les capabilities non-standard#

# Lister toutes les capabilities sur le système
getcap -r / 2>/dev/null | grep -v "^/usr/bin/ping\|^/usr/bin/traceroute"

Démonstrations Python#

Lecture des permissions avec le module stat#


import os
import stat
import pwd
import grp

def afficher_permissions(chemin):
    """Affiche les permissions détaillées d'un fichier."""
    try:
        s = os.stat(chemin)
    except PermissionError:
        print(f"{chemin} : accès refusé")
        return
    except FileNotFoundError:
        print(f"{chemin} : fichier introuvable")
        return

    mode = s.st_mode
    perm_octal = oct(stat.S_IMODE(mode))

    # Reconstruction rwx
    def bits_rwx(m):
        return (
            ("r" if m & 0o400 else "-") +
            ("w" if m & 0o200 else "-") +
            ("x" if m & 0o100 else "-") +
            ("r" if m & 0o040 else "-") +
            ("w" if m & 0o020 else "-") +
            ("x" if m & 0o010 else "-") +
            ("r" if m & 0o004 else "-") +
            ("w" if m & 0o002 else "-") +
            ("x" if m & 0o001 else "-")
        )

    # Bits spéciaux
    suid = "SUID " if mode & stat.S_ISUID else ""
    sgid = "SGID " if mode & stat.S_ISGID else ""
    sticky = "Sticky" if mode & stat.S_ISVTX else ""
    specials = (suid + sgid + sticky).strip() or "—"

    try:
        proprietaire = pwd.getpwuid(s.st_uid).pw_name
    except KeyError:
        proprietaire = str(s.st_uid)

    try:
        groupe = grp.getgrgid(s.st_gid).gr_name
    except KeyError:
        groupe = str(s.st_gid)

    type_fichier = {
        stat.S_IFREG: "fichier régulier",
        stat.S_IFDIR: "répertoire",
        stat.S_IFLNK: "lien symbolique",
        stat.S_IFCHR: "char device",
        stat.S_IFBLK: "block device",
        stat.S_IFIFO: "FIFO",
        stat.S_IFSOCK: "socket",
    }.get(stat.S_IFMT(mode), "inconnu")

    print(f"Fichier    : {chemin}")
    print(f"Type       : {type_fichier}")
    print(f"Octal      : {perm_octal}")
    print(f"rwx        : {bits_rwx(mode)}")
    print(f"Propriétaire: {proprietaire} (uid={s.st_uid})")
    print(f"Groupe     : {groupe} (gid={s.st_gid})")
    print(f"Bits spéciaux: {specials}")
    print(f"Taille     : {s.st_size} octets")
    print()

for fichier in ["/etc/passwd", "/etc/shadow", "/tmp"]:
    afficher_permissions(fichier)
Fichier    : /etc/passwd
Type       : fichier régulier
Octal      : 0o644
rwx        : rw-r--r--
Propriétaire: root (uid=0)
Groupe     : root (gid=0)
Bits spéciaux: —
Taille     : 2663 octets

Fichier    : /etc/shadow
Type       : fichier régulier
Octal      : 0o640
rwx        : rw-r-----
Propriétaire: root (uid=0)
Groupe     : shadow (gid=42)
Bits spéciaux: —
Taille     : 1216 octets

Fichier    : /tmp
Type       : répertoire
Octal      : 0o1777
rwx        : rwxrwxrwx
Propriétaire: root (uid=0)
Groupe     : root (gid=0)
Bits spéciaux: Sticky
Taille     : 620 octets

Heatmap des permissions#


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Fichiers représentatifs avec leurs permissions en octal
fichiers = [
    ("/etc/passwd",       0o644),
    ("/etc/shadow",       0o640),
    ("/etc/sudoers",      0o440),
    ("/bin/bash",         0o755),
    ("/usr/bin/passwd",   0o4755),  # SUID
    ("/usr/bin/sudo",     0o4111),  # SUID
    ("/tmp",              0o1777),  # Sticky
    ("/var/log/syslog",   0o640),
    ("/home/alice/.ssh",  0o700),
    ("/srv/partage",      0o2775),  # SGID
]

categories = ["u:r", "u:w", "u:x", "g:r", "g:w", "g:x", "o:r", "o:w", "o:x"]
noms = [f[0].split("/")[-1] or f[0] for f in fichiers]
noms[6] = "/tmp"
noms[8] = ".ssh"

matrice = np.zeros((len(fichiers), 9), dtype=float)

for i, (_, perm) in enumerate(fichiers):
    bits = perm & 0o777
    for j, bit in enumerate([0o400, 0o200, 0o100, 0o040, 0o020, 0o010, 0o004, 0o002, 0o001]):
        matrice[i, j] = 1.0 if (bits & bit) else 0.0

# Marquer les bits spéciaux en valeur différente
for i, (_, perm) in enumerate(fichiers):
    if perm & 0o4000:  # SUID
        matrice[i, 2] = 2.0
    if perm & 0o2000:  # SGID
        matrice[i, 5] = 2.0
    if perm & 0o1000:  # Sticky
        matrice[i, 8] = 3.0

fig, ax = plt.subplots(figsize=(11, 6))

from matplotlib.colors import ListedColormap
cmap = ListedColormap(["#f0f0f0", "#4c9be8", "#e84c4c", "#f0c040"])
im = ax.imshow(matrice, cmap=cmap, aspect="auto", vmin=0, vmax=3)

ax.set_xticks(range(9))
ax.set_xticklabels(categories, fontsize=10)
ax.set_yticks(range(len(fichiers)))
ax.set_yticklabels(noms, fontsize=9)

for i in range(len(fichiers)):
    for j in range(9):
        val = matrice[i, j]
        if val > 0:
            label = {1.0: "✓", 2.0: "S", 3.0: "T"}.get(val, "")
            couleur = "white" if val >= 2 else "#333333"
            ax.text(j, i, label, ha="center", va="center", fontsize=9, color=couleur, fontweight="bold")

ax.set_title("Matrice des permissions — fichiers système représentatifs", fontsize=13, pad=14)
ax.set_xlabel("Bits de permission", fontsize=10)
ax.set_ylabel("Fichier", fontsize=10)

legende = [
    mpatches.Patch(facecolor="#f0f0f0", edgecolor="gray", label="Pas de droit"),
    mpatches.Patch(facecolor="#4c9be8", label="Droit actif"),
    mpatches.Patch(facecolor="#e84c4c", label="SUID/SGID"),
    mpatches.Patch(facecolor="#f0c040", label="Sticky bit"),
]
ax.legend(handles=legende, loc="upper right", bbox_to_anchor=(1.18, 1.0), fontsize=9)

plt.show()
_images/c658a011509298cf3c544de0820045e457366af9eb132d48b70c9ad05fdac3a8.png

Simulation d’audit SUID#


import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

# Liste simulée de fichiers SUID avec métadonnées
audit_suid = [
    {"fichier": "/usr/bin/passwd",    "proprio": "root", "taille": 59976,  "legitime": True,  "risque": 2},
    {"fichier": "/usr/bin/sudo",      "proprio": "root", "taille": 166056, "legitime": True,  "risque": 2},
    {"fichier": "/usr/bin/su",        "proprio": "root", "taille": 67816,  "legitime": True,  "risque": 2},
    {"fichier": "/usr/bin/newgrp",    "proprio": "root", "taille": 39648,  "legitime": True,  "risque": 1},
    {"fichier": "/usr/bin/chfn",      "proprio": "root", "taille": 72712,  "legitime": True,  "risque": 1},
    {"fichier": "/usr/bin/mount",     "proprio": "root", "taille": 122784, "legitime": True,  "risque": 2},
    {"fichier": "/usr/bin/umount",    "proprio": "root", "taille": 88408,  "legitime": True,  "risque": 1},
    {"fichier": "/usr/sbin/pppd",     "proprio": "root", "taille": 440664, "legitime": True,  "risque": 2},
    # Fichiers suspects simulés
    {"fichier": "/tmp/.hidden/shell", "proprio": "root", "taille": 1234,   "legitime": False, "risque": 5},
    {"fichier": "/home/bob/backdoor", "proprio": "root", "taille": 8192,   "legitime": False, "risque": 5},
    {"fichier": "/opt/custom/helper", "proprio": "root", "taille": 12288,  "legitime": False, "risque": 4},
]

df = pd.DataFrame(audit_suid)

# --- Affichage textuel ---
print("=== Rapport d'audit SUID ===\n")
print(f"Total fichiers SUID trouvés : {len(df)}")
print(f"  Légitimes   : {df['legitime'].sum()}")
print(f"  Suspects    : {(~df['legitime']).sum()}\n")

print("Fichiers suspects :")
suspects = df[~df["legitime"]][["fichier", "proprio", "taille", "risque"]]
print(suspects.to_string(index=False))

# --- Visualisation ---
fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# Graphique 1 : distribution des niveaux de risque
couleurs_risque = {1: "#4caf50", 2: "#ff9800", 3: "#ff5722", 4: "#d32f2f", 5: "#7b1fa2"}
comptes = df["risque"].value_counts().sort_index()
bars = axes[0].bar(
    [f"Niveau {r}" for r in comptes.index],
    comptes.values,
    color=[couleurs_risque[r] for r in comptes.index],
    edgecolor="white", linewidth=1.2
)
axes[0].set_title("Distribution des niveaux de risque\n(fichiers SUID)", fontsize=11)
axes[0].set_ylabel("Nombre de fichiers")
axes[0].set_xlabel("Niveau de risque")
for bar, val in zip(bars, comptes.values):
    axes[0].text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.05,
                 str(val), ha="center", va="bottom", fontsize=10, fontweight="bold")

# Graphique 2 : scatter taille vs risque, coloré par légitimité
couleurs_pt = df["legitime"].map({True: "#4c9be8", False: "#e84c4c"})
axes[1].scatter(df["taille"] / 1024, df["risque"],
                c=couleurs_pt, s=100, alpha=0.8, edgecolors="white", linewidth=0.8)
for _, row in df.iterrows():
    nom_court = row["fichier"].split("/")[-1]
    axes[1].annotate(nom_court, (row["taille"] / 1024, row["risque"]),
                     textcoords="offset points", xytext=(5, 3), fontsize=7, alpha=0.85)
axes[1].set_title("Taille vs niveau de risque\n(fichiers SUID)", fontsize=11)
axes[1].set_xlabel("Taille (Ko)")
axes[1].set_ylabel("Niveau de risque")
axes[1].set_yticks(range(1, 6))

import matplotlib.patches as mpatches
legende = [
    mpatches.Patch(color="#4c9be8", label="Légitime"),
    mpatches.Patch(color="#e84c4c", label="Suspect"),
]
axes[1].legend(handles=legende, fontsize=9)

plt.suptitle("Audit des fichiers SUID — résultats simulés", fontsize=13, y=1.01)
plt.show()
=== Rapport d'audit SUID ===

Total fichiers SUID trouvés : 11
  Légitimes   : 8
  Suspects    : 3

Fichiers suspects :
           fichier proprio  taille  risque
/tmp/.hidden/shell    root    1234       5
/home/bob/backdoor    root    8192       5
/opt/custom/helper    root   12288       4
_images/aad97ca3a389bf06c0d2325ad5834ec88ad03ebdc0b6f282dec5e3240fd875f6.png

Résumé#

Ce chapitre a couvert les mécanismes de contrôle d’accès avancés de Linux :

Modèle DAC et bits spéciaux

  • Le modèle rwx/UID/GID reste la base : les permissions sont vérifiées via l’UID effectif du processus

  • SUID (4000) et SGID (2000) modifient l’identité effective lors de l’exécution ou l’héritage de groupe dans les répertoires

  • Le sticky bit (1000) protège les fichiers dans les répertoires partagés

ACL POSIX

  • getfacl/setfacl permettent des permissions granulaires par utilisateur ou groupe supplémentaire

  • Le masque ACL est le plafond effectif des permissions ACL

  • Les ACL par défaut propagent les règles aux nouveaux fichiers

Capabilities

  • Remplacement moderne du bit SUID : chaque privilege est accordé individuellement

  • Cinq ensembles (permitted, effective, inheritable, bounding, ambient) contrôlent la propagation

  • getcap/setcap pour inspecter et modifier

Attributs étendus

  • chattr +i (immutable) et +a (append-only) sont des protections complémentaires aux permissions standard

  • Résistent même aux actions de root

umask

  • Masque appliqué lors de la création : permission_max & ~umask

  • Configurable par shell, PAM ou unité systemd

Audit

  • find -perm -4000/2000 pour détecter les SUID/SGID

  • getcap -r / pour inventorier les capabilities

  • Les fichiers SUID hors des chemins système standards sont systématiquement suspects