Esiste una race condition inerente al modo in cui shebang (#!
) è tipicamente implementato:
- Il kernel apre l'eseguibile e scopre che inizia con
#!
. - Il kernel chiude l'eseguibile e apre invece l'interprete.
- Il kernel inserisce il percorso dello script nell'elenco degli argomenti (come
argv[1]
), ed esegue l'interprete.
Se gli script setuid sono consentiti con questa implementazione, un utente malintenzionato può invocare uno script arbitrario creando un collegamento simbolico a uno script setuid esistente, eseguendolo e organizzandosi per modificare il collegamento dopo che il kernel ha eseguito il passaggio 1 e prima che l'interprete riesca a aprendo il suo primo argomento. Per questo motivo, tutti gli uni moderni ignorano il bit setuid quando rilevano una barzelletta.
Un modo per proteggere questa implementazione sarebbe che il kernel bloccasse il file di script fino a quando l'interprete non lo ha aperto (si noti che ciò deve impedire non solo di scollegare o sovrascrivere il file, ma anche di rinominare qualsiasi directory nel percorso). Ma i sistemi unix tendono a evitare i blocchi obbligatori e i collegamenti simbolici renderebbero particolarmente difficile e invasiva una funzione di blocco corretta. Non credo che nessuno lo faccia in questo modo.
Alcuni sistemi unix implementano setuid sicuro shebang utilizzando una funzionalità aggiuntiva:il percorso /dev/fd/N
si riferisce al file già aperto sul descrittore di file N (quindi aprendo /dev/fd/N
è approssimativamente equivalente a dup(N)
).
- Il kernel apre l'eseguibile e scopre che inizia con
#!
. Diciamo che il descrittore di file per l'eseguibile è 3. - Il kernel apre l'interprete.
- Il kernel inserisce
/dev/fd/3
l'elenco degli argomenti (comeargv[1]
), ed esegue l'interprete.
Tutte le varianti unix moderne, incluso Linux, implementano /dev/fd
, ma la maggior parte non consente gli script setuid. OpenBSD, NetBSD e Mac OS X lo supportano se abiliti un'impostazione del kernel non predefinita. Su Linux, le persone hanno scritto patch per consentirlo, ma quelle patch non sono mai state unite. La pagina Shebang di Sven Mascheck contiene molte informazioni su Shebang tra unix, incluso il supporto setuid.
Inoltre, i programmi in esecuzione con privilegi elevati presentano rischi intrinseci che in genere sono più difficili da controllare nei linguaggi di programmazione di livello superiore, a meno che l'interprete non sia stato specificamente progettato per questo. Il motivo è che il codice di inizializzazione del runtime del linguaggio di programmazione può eseguire azioni con privilegi elevati, in base ai dati ereditati dal chiamante con privilegi inferiori, prima che il codice del programma abbia avuto l'opportunità di disinfettare questi dati. Il runtime C fa ben poco per il programmatore, quindi i programmi C hanno una migliore opportunità di assumere il controllo e disinfettare i dati prima che possa accadere qualcosa di brutto.
Supponiamo che tu sia riuscito a far funzionare il tuo programma come root, o perché il tuo sistema operativo supporta setuid shebang o perché hai usato un wrapper binario nativo (come sudo
). Hai aperto una falla nella sicurezza? Forse. Il problema qui non sui programmi interpretati e compilati. Il problema è se il tuo sistema di runtime si comporta in modo sicuro se eseguito con privilegi.
-
Qualsiasi eseguibile binario nativo collegato dinamicamente è in qualche modo interpretato dal caricatore dinamico (ad es.
/lib/ld.so
), che carica le librerie dinamiche richieste dal programma. Su molti unix, puoi configurare il percorso di ricerca per le librerie dinamiche attraverso l'ambiente (LD_LIBRARY_PATH
è un nome comune per la variabile d'ambiente) e carica anche librerie aggiuntive in tutti i binari eseguiti (LD_PRELOAD
). L'invocatore del programma può eseguire codice arbitrario nel contesto di quel programma inserendo unlibc.so
appositamente predisposto in$LD_LIBRARY_PATH
(tra le altre tattiche). Tutti i sistemi sani ignorano ilLD_*
variabili negli eseguibili setuid. -
In shell come sh, csh e derivate, le variabili d'ambiente diventano automaticamente parametri della shell. Attraverso parametri come
PATH
,IFS
e molti altri, l'invocatore dello script ha molte opportunità di eseguire codice arbitrario nel contesto degli script della shell. Alcune shell impostano queste variabili su valori predefiniti sani se rilevano che lo script è stato richiamato con privilegi, ma non so se esiste un'implementazione particolare di cui mi fiderei. -
La maggior parte degli ambienti di runtime (nativi, bytecode o interpretati) hanno caratteristiche simili. Pochi prendono precauzioni speciali negli eseguibili setuid, anche se quelli che eseguono codice nativo spesso non fanno niente di più sofisticato del collegamento dinamico (che prende precauzioni).
-
Perl è una notevole eccezione. Supporta esplicitamente gli script setuid in modo sicuro. In effetti, il tuo script può eseguire setuid anche se il tuo sistema operativo ha ignorato il bit setuid sugli script. Questo perché perl viene fornito con un root helper setuid che esegue i controlli necessari e richiama nuovamente l'interprete sugli script desiderati con i privilegi desiderati. Questo è spiegato nel manuale di perlsec. Una volta gli script perl setuid avevano bisogno di
#!/usr/bin/suidperl -wT
invece di#!/usr/bin/perl -wT
, ma sulla maggior parte dei sistemi moderni,#!/usr/bin/perl -wT
è sufficiente.
Si noti che l'utilizzo di un wrapper binario nativo non fa nulla di per sé per prevenire questi problemi. In effetti, può peggiorare la situazione, perché potrebbe impedire al tuo ambiente di runtime di rilevare che è stato richiamato con privilegi e aggirare la sua configurabilità di runtime.
Un wrapper binario nativo può rendere sicuro uno script di shell se il wrapper disinfetta l'ambiente. Lo script deve fare attenzione a non fare troppe ipotesi (ad esempio sulla directory corrente) ma questo va bene. Puoi usare sudo per questo, a condizione che sia impostato per disinfettare l'ambiente. Le variabili nella lista nera sono soggette a errori, quindi inserisci sempre nella whitelist. Con sudo, assicurati che env_reset
l'opzione è attivata, quel setenv
è disattivato e quel env_file
e env_keep
contengono solo variabili innocue.
Tutte queste considerazioni si applicano ugualmente a qualsiasi elevazione dei privilegi:setuid, setgid, setcap.
Riciclato da https://unix.stackexchange.com/questions/364/allow-setuid-on-shell-scripts/2910#2910
Principalmente perché
Molti kernel soffrono di una race condition che può permetterti di scambiare lo shellscript con un altro eseguibile di tua scelta tra il momento in cui il nuovo processo exec()ed diventa setuid e quando l'interprete dei comandi viene avviato. Se sei abbastanza persistente, in teoria potresti far eseguire al kernel qualsiasi programma tu voglia.
così come altri motivi trovati a quel collegamento (ma la condizione di gara del kernel è la più perniciosa). Gli script, ovviamente, si caricano in modo diverso rispetto ai programmi binari, ed è qui che si insinua l'errore.
Potresti essere divertito a leggere questo articolo del Dr. Dobb del 2001, che passa attraverso 6 passaggi verso la scrittura di script di shell SUID più sicuri, solo per raggiungere il passaggio 7:
Lezione sette -- Non utilizzare script di shell SUID.
Anche dopo tutto il nostro lavoro, è quasi impossibile creare script SUIDshell sicuri. (È impossibile sulla maggior parte dei sistemi.) A causa di questi problemi, alcuni sistemi (ad es. Linux) non rispettano SUID sugli shellscript.
Questa cronologia parla di quali varianti avevano correzioni per le condizioni di gara; l'elenco è più lungo di quanto si possa pensare... ma gli script setuid sono ancora largamente sconsigliati a causa di altri problemi o perché è più facile scoraggiarli piuttosto che ricordare se si sta utilizzando una variante sicura o non sicura.
Questo era un problema abbastanza grande, ai tempi, che è istruttivo leggere come Perl si avvicinò in modo aggressivo per compensarlo.