Skip to main content
  1. Blog/

chmod +s - il bit che dimentichi e l'attaccante trova

·7 mins
Alessio Barnini
Author
Alessio Barnini
Table of Contents

TL;DR
  • SUID forza l'EUID al proprietario del file al momento dell'esecuzione - se il proprietario è root, ogni utente che lo esegue ottiene EUID=0
  • Un interprete con SUID root (python3, perl, bash) è escalation immediata: nessuna vulnerabilità da sfruttare, nessun exploit da compilare
  • find / -perm -4000 -type f 2>/dev/null in 30 secondi elenca tutto quello che conta
  • Detection: baseline snapshot dei SUID in CI/CD + auditd rule su execve con euid=0 e auid!=unset
$ history
  • find -perm -4000 -type f 2>/dev/null
  • stat /usr/bin/python3
  • id
  • cat /proc/self/status
  • python3 -c "import os; os.execl('/bin/sh', 'sh', '-p')"
  • auditctl -l
  • ausearch -m execve -k suid_exec
  • ls -la /usr/bin/python3

Sono le 02:41. Il SIEM ha alzato un flag su prod-api-03: processo root con parent python3, nessun deploy in corso, nessuna maintenance window schedulata. Il processo è già terminato quando apro il ticket. Non c'è output, non c'è file scritto. Solo un'esecuzione anomala durata undici secondi.

Undici secondi sono più che sufficienti.

SUID - Permessi Speciali del Sistema


Come funziona il bit SUID
#

Il bit SUID (Set User ID) è un meccanismo del kernel Linux che modifica il comportamento dell'execve(): quando un eseguibile ha il bit s impostato e il proprietario è root, il kernel eleva l'EUID (Effective User ID) a 0 per tutta la durata dell'esecuzione, indipendentemente da chi ha lanciato il processo.

La distinzione tra RUID e EUID è il punto critico. RUID è chi sei davvero - l'utente che ha fatto login. EUID è chi sei in questo momento per il kernel quando verifica i permessi. Quasi tutto il permission model Linux usa EUID: apertura di file, bind su porte privilegiate, modifica di processi altrui.

execve("/usr/bin/python3")
kernel controlla SUID bit sul file
        ├── proprietario: root (uid=0)
        ├── SUID bit: attivo
EUID processo = 0 (anche se RUID = 1001)

L'unica differenza tra un interprete normale e uno con SUID root è quel bit. Nessuna vulnerabilità, nessun buffer overflow. Solo un flag in mezzo ai permessi che la maggior parte dei tool di monitoring non controlla in real time.


Il primo segnale
#

Il processo nel SIEM è già sparito, ma auditd gira su tutte le macchine di produzione. La prima query è sugli eventi execve con euid=0 nelle ultime quattro ore:

ausearch -m execve -k suid_exec --start 02:00:00 --end 03:00:00 -i
# output simulato
type=SYSCALL msg=audit(1711155723.441:8847): arch=c000003e syscall=59
success=yes exit=0 a0=... a1=... a2=... ppid=31204 pid=31205
uid=1001 gid=1001 euid=0 suid=0 fsuid=0 egid=1001 sgid=1001
fsgid=1001 tty=(none) ses=47 comm="sh" exe="/bin/sh"
subj=unconfined_u:... key="suid_exec"

Il campo che conta: uid=1001 euid=0. Un utente non privilegiato (uid=1001, l'account di servizio deploy) ha eseguito qualcosa che gli ha dato EUID root. Il binario eseguito è /bin/sh. Il parent process era python3.

# recupero il parent PID per contestualizzare
ausearch -m execve -p 31204 --start 02:00:00 -i
# output simulato
type=SYSCALL ... comm="python3" exe="/usr/bin/python3"
uid=1001 gid=1001 euid=0 ...

/usr/bin/python3 eseguito con EUID=0 da uid=1001. Questo è il punto di ingresso.


La traccia
#

stat /usr/bin/python3
# output simulato
  File: /usr/bin/python3
  Size: 5479736
  Access: (4755/-rwsr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)

4755. Il 4 davanti è il SUID bit. -rwsr-xr-x: la s al posto della x nel gruppo owner. Qualcuno ha eseguito chmod u+s /usr/bin/python3 su questo server.

Per capire quando è successo:

stat --format="%n %y %z" /usr/bin/python3
# output simulato
/usr/bin/python3 2026-03-22 23:14:07.341 +0000 (mtime)
                 2026-03-22 23:14:07.341 +0000 (ctime)

Le 23:14 di ieri sera. Undici ore prima dell'alert. Cerco nei log chi era connesso al server in quella finestra:

ausearch -m USER_AUTH --start 23:00:00 --end 23:30:00 -i | grep "uid=1001\|uid=0"
# output simulato
type=USER_AUTH msg=audit(1711151647.002:7123): pid=28941 uid=0
  acct="ops-user" exe="/usr/bin/sudo" ... res=success

ops-user ha usato sudo alle 23:14. Probabilmente stava configurando qualcosa. Il cambio di permessi su python3 è quasi certamente stato un incidente: volevamo chmod 755 e abbiamo scritto chmod 4755. O abbiamo lanciato chmod u+s invece di un altro comando.

Il punto non è che qualcuno ha voluto sabotare il server. Il punto è che un errore di digitazione di dieci caratteri su un server di produzione ha lasciato una backdoor root accessibile a qualunque processo che potesse chiamare python3.


Il pivot - la ricostruzione dell'exploit
#

Dall'audit log dell'attaccante (o di chi ha scoperto l'apertura e l'ha sfruttata):

# dal punto di vista dell'utente deploy (uid=1001)
id
# uid=1001(deploy) gid=1001(deploy) groups=1001(deploy)

python3 -c "import os; os.execl('/bin/sh', 'sh', '-p')"

id
# uid=1001(deploy) gid=1001(deploy) euid=0(root) groups=1001(deploy)

Il flag -p di /bin/sh disabilita il reset automatico dell'EUID: senza -p, alcune shell moderne abbassano volontariamente l'EUID al RUID per sicurezza. Con -p, la shell mantiene i privilegi elevati.

# verifica dal /proc per chi non si fida dei comandi utente
cat /proc/self/status | grep -E "^[UG]id"
# output simulato
Uid:	1001	0	0	0
Gid:	1001	1001	1001	1001

A questo punto l'attaccante ha EUID=0. Può leggere /etc/shadow, modificare /etc/sudoers, scrivere authorized_keys in /root/.ssh/, installare un cron job root, estrarre segreti da /proc/PID/environ di qualunque processo.


Diagramma - scenario e kill chain
#

graph TD
    A[ops-user sudo session] -->|chmod 4755 typo| B[python3 SUID root]
    B --> C{Attacco}
    C -->|uid=1001 deploy account| D[python3 -c os.execl sh -p]
    D --> E[shell EUID=0]
    E --> F[shadow read]
    E --> G[cron root install]
    E --> H[authorized_keys root]
sequenceDiagram
    participant U as ops-user
    participant S as prod-api-03
    participant A as deploy uid=1001
    participant SIEM

    U->>S: chmod 4755 python3 typo
    Note over S: SUID bit attivo su python3

    A->>S: python3 os.execl sh -p
    S-->>A: shell con EUID=0

    A->>S: cat /etc/shadow
    A->>S: write root authorized_keys

    S->>SIEM: execve euid=0 uid=1001
    Note over SIEM: unprivileged user running as root

Tabella IoC
#

TipoValoreContestoMITRE ATT&CK
TecnicaSUID bit su interprete/usr/bin/python3 con chmod 4755T1548.001
Eventoexecve con uid!=0 e euid=0auditd log prod-api-03T1548.001
Comandopython3 -c "import os; os.execl('/bin/sh', 'sh', '-p')"escalation da deploy accountT1059.006
Artefattomtime /usr/bin/python3 2026-03-22T23:14:07Zcambio permessi non schedulatoT1222.002
Accountuid=1001 (deploy) con euid=0session 47, tty=noneT1078.003

Mitigazione
#

Rimozione immediata:

# verifica lo stato attuale
stat --format="%A %n" /usr/bin/python3

# rimozione SUID
chmod u-s /usr/bin/python3

# verifica
stat --format="%A %n" /usr/bin/python3
# -rwxr-xr-x /usr/bin/python3

Baseline e monitoring continuo:

# snapshot dei SUID in CI/CD o al boot
find / -perm -4000 -type f 2>/dev/null | sort > /var/lib/suid-baseline.txt

# confronto periodico (cron o systemd timer)
find / -perm -4000 -type f 2>/dev/null | sort | diff /var/lib/suid-baseline.txt -

Regola auditd per SUID exec:

# /etc/audit/rules.d/suid.rules
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=unset -k suid_exec
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid!=unset -k suid_exec

# applica senza reboot
auditctl -R /etc/audit/rules.d/suid.rules

Checklist di prevenzione:

  • Verificare SUID in pipeline di deploy: find / -perm -4000 deve matchare la baseline
  • inotifywait -m /usr/bin /usr/local/bin -e attrib per alert real-time su cambio permessi
  • Separare account di deploy da account operativi - ops-user non dovrebbe avere accesso diretto a prod-api-03
  • Per binari che richiedono privilegi elevati valutare Linux capabilities (cap_net_bind_service, cap_dac_read_search) invece del SUID: scope più ristretto, stessa funzionalità
  • Se stai usando container: --no-new-privileges in Docker, allowPrivilegeEscalation: false in Kubernetes securityContext

exit 0
#

Undici ore tra il chmod e l'exploit. Su un sistema senza auditd, quella finestra potrebbe non avere un log. Su un sistema senza baseline dei SUID, quel bit potrebbe rimanere per mesi.

La difficoltà tecnica di questa escalation è zero. Non serve un exploit, non serve un CVE, non serve nemmeno sapere cosa si sta facendo: python3 -c "import os; os.execl('/bin/sh', 'sh', '-p')" è su ogni cheatsheet pubblico. Il valore del SUID bit non è nella complessità - è nella normalità. È un file system che ha un permesso che sembra ragionevole finché non lo è.

Il punto non è "non fare typo". Il punto è che un sistema ben difeso dovrebbe rilevare questo cambio in meno di un minuto, non a posteriori da un alert SIEM undici ore dopo.


Concetti correlati: iam · linux permissions

MITRE ATT&CK: T1548.001 - Setuid and Setgid

Related