Qual è il modo più sicuro per scrivere a livello di codice su un file con privilegi di root?


35

Un'enorme applicazione deve, in un determinato momento, eseguire un piccolo numero di scritture su un file che richiede i permessi di root. Non è in realtà un file ma un'interfaccia hardware che è esposta a Linux come file.

Per evitare di dare i privilegi di root all'intera applicazione, ho scritto uno script bash che svolge i compiti critici. Ad esempio, il seguente script abiliterà la porta 17 dell'interfaccia hardware come output:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

Tuttavia, poiché suidè disabilitato per gli script bash sul mio sistema, mi chiedo quale sia il modo migliore per raggiungere questo obiettivo.

  1. Usa una soluzione alternativa presentata qui

  2. Chiamare lo script con sudodall'applicazione principale e modificare di conseguenza l'elenco dei sudoers, per evitare di richiedere una password quando si chiama lo script. Sono un po 'a disagio a dare i privilegi di sudo echo.

  3. Basta scrivere un programma C, con fprintfe impostarlo su suid root. Hardcode le stringhe e i nomi dei file e assicurarsi che solo root possa modificarlo. Oppure leggi le stringhe da un file di testo, allo stesso modo assicurandoti che nessuno possa modificare il file.

  4. Qualche altra soluzione che non mi è venuta in mente ed è più sicura o più semplice di quelle presentate sopra?


10
Perché non avviare semplicemente il programma con i privilegi di root, aprire il file e rilasciare i privilegi? È così che ogni server web o simili lo fa per i socket. In effetti, non si esegue come root, né è necessario un aiuto.
Damon,

Risposte:


33

Non devi dare sudo accesso a echo. In effetti, è inutile perché, ad esempio con sudo echo foo > bar, il reindirizzamento viene eseguito come utente originale, non come root.

Chiama il piccolo script con sudo , consentendo l' NOPASSWD:accesso SOLO a quello script (e a qualsiasi altro script simile) da parte degli utenti che hanno bisogno di accedervi.

Questo è sempre il modo migliore / più sicuro da usare sudo. Isolare il piccolo numero di comandi che richiedono i privilegi di root nei propri script separati e consentire all'utente non fidato o parzialmente fidato di eseguire solo quello script come root.

Il piccolo sudo script di ridotte non devono prendere args (o input) dall'utente (ovvero qualsiasi altro programma che chiama dovrebbe avere opzioni e args codificate) oppure dovrebbe validare con molta attenzione qualsiasi argomento / input che deve accetta dall'utente.

Sii paranoico nella convalida - piuttosto che cercare cose "conosciute cattive" da escludere, consentire solo cose "conosciute buone" e interrompere qualsiasi discrepanza o errore o qualsiasi cosa anche lontanamente sospetta.

La convalida dovrebbe avvenire il più presto possibile nello script (preferibilmente prima di fare qualsiasi altra cosa come root).


Avrei davvero dovuto menzionarlo quando ho scritto questa risposta per la prima volta, ma se il tuo script è uno script shell DEVE citare correttamente tutte le variabili. Prestare particolare attenzione a citare le variabili contenenti l'input fornito dall'utente in alcun modo, ma non dare per scontato che alcune variabili siano sicure, PREVENTILO TUTTI .

Ciò include le variabili di ambiente potenzialmente controllate dall'utente (ad es "$PATH". "$HOME", "$USER"Ecc. E sicuramente incluse "$QUERY_STRING"ed "HTTP_USER_AGENT"ecc. In uno script CGI). In effetti, citali tutti. Se devi costruire una riga di comando con più argomenti, usa un array per costruire l'elenco args e citare che -"${myarray[@]}" .

Ho già detto "citali tutti" abbastanza spesso? ricordalo. fallo.


18
Hai dimenticato di dire che lo script stesso dovrebbe essere di proprietà di root, con autorizzazioni 500.
Wildcard

13
Almeno rimuovi i permessi di scrittura, per l'amor del cielo. Questo era davvero il mio punto. Il resto si sta solo indurendo.
Wildcard il

10
Un programma C scritto con cura avrà una superficie di attacco più piccola di uno script di shell.
user253751

7
@immibis, possibilmente. Ma ci vorrà molto più tempo per scrivere ed eseguire il debug di uno script di shell. E richiede di avere un compilatore C (che è proibito su alcuni server di produzione per ridurre il rischio per la sicurezza rendendo più difficile la compilazione di exploit per gli aggressori). Inoltre, IMO uno script di shell scritto da un principiante a livello di sistema intermedio o programmatore ha meno probabilità di essere sfruttabile rispetto a un programma C scritto da qualcuno con abilità simili - soprattutto se deve accettare e convalidare i dati forniti dall'utente.
CAS

3
@JonasWielicki - penso che ora stiamo bene e veramente nel regno delle opinioni piuttosto che dei fatti. Puoi fare argomenti validi per shell o C (o perl o python o awk ecc.) Essendo più o meno inclini a errori sfruttabili. Quando, in realtà, dipende principalmente dall'abilità del programmatore e dall'attenzione ai dettagli (e stanchezza, fretta, cautela, ecc.). È un dato di fatto, tuttavia, che le lingue di livello inferiore tendono a richiedere la scrittura di più codice per ottenere ciò che può essere fatto in molte meno righe di codice in lingue di livello superiore .... e ogni LoC è un'altra opportunità per un errore da fare.
CAS

16

Controlla il proprietario sui file gpio:

ls -l /sys/class/gpio/

Molto probabilmente, scoprirai che sono di proprietà del gruppo gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

In tal caso, puoi semplicemente aggiungere il tuo utente al gpiogruppo per concedere l'accesso senza sudo:

sudo usermod -aG gpio myusername

Dovrai disconnetterti e riconnetterti per rendere effettiva la modifica.


Non funziona In effetti, il proprietario del gruppo di tutto ciò che /sys/class/gpio/è in gpio, ma anche dopo essermi aggiunto a quel gruppo, ricevo ancora il "permesso negato" ogni volta che provo a scrivere qualcosa lì.
vsz,

1
Il problema è che i file in /sys/class/gpio/sono in realtà solo collegamenti simbolici a /sys/devices/platform/soc/<some temporary name>/gpiocui sia il proprietario che il gruppo sono root.
vsz

4
@vsz Hai provato chgrp gpio /sys/devices/platform/soc/*/gpio? Forse qualcosa del genere potrebbe essere inserito in un file di avvio.
jpa,

si, ma non è così semplice. Dato che sono sempre generati in modo diverso, ho dovuto usare qualcosa di similechgrp gpio `readlink -f /sys/class/gpio/gpio18`/*
vsz

7

Una soluzione per questo (utilizzata in particolare sul desktop Linux ma applicabile anche in altri casi) è utilizzare D-Bus per attivare un piccolo servizio in esecuzione come root e polkit per eseguire l'autorizzazione. Questo è fondamentalmente ciò per cui Polkit è stato progettato ; dalla sua documentazione introduttiva :

polkit fornisce un'API di autorizzazione destinata a essere utilizzata da programmi privilegiati ("MECCANISMI") che offre servizi a programmi non privilegiati ("CLIENTI"). Consulta la pagina di manuale di polkit per l'architettura di sistema e l'immagine generale.

Invece di eseguire il tuo programma di supporto, il grande programma senza privilegi invierebbe una richiesta sul bus. Il tuo helper potrebbe essere in esecuzione come un demone avviato all'avvio del sistema o, meglio, esserlo attivato secondo necessità da systemd . Quindi, quell'helper userebbe polkit per verificare che la richiesta provenga da un luogo autorizzato. (O, in questo caso, se ciò sembra eccessivo, è possibile utilizzare un altro meccanismo di autenticazione / autorizzazione codificato).

Ho trovato un buon articolo di base sulla comunicazione tramite D-Bus e, sebbene non lo abbia ancora testato, questo sembra essere un esempio di base per aggiungere polkit al mix .

In questo approccio, nulla deve essere contrassegnato come setuid.


5

Un modo per farlo è quello di creare un programma setuid-root scritto in C che faccia solo ciò che è necessario e niente di più. Nel tuo caso, non è necessario esaminare alcun input dell'utente.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

Non c'è modo di sovvertirlo attraverso variabili d'ambiente o altro, perché tutto ciò che fa è effettuare un paio di chiamate di sistema.

Unico inconveniente: è necessario verificare il valore di ritorno di ogni chiamata di sistema.

Upside: il controllo degli errori è davvero semplice: se c'è qualche errore, basta perrore salvarsi: uscire con stato diverso da zero. Se c'è un errore, indagare con strace. Non è necessario questo programma per fornire messaggi di errore davvero piacevoli.


2
Potrei essere tentato di aprire entrambi i file prima di scrivere qualsiasi cosa per proteggere dall'assenza del secondo. E potrei eseguire questo programma tramite in sudomodo che non debba essere impostato da solo. Per inciso, è <fcntl.h>per open().
Jonathan Leffler,

3

Bless tee anziché echo per sudo è un modo comune per affrontare una situazione in cui è necessario limitare i permessi di root. Il reindirizzamento a / dev / null è quello di impedire la fuoriuscita di qualsiasi output - il t fa quello che vuoi.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null

5
Se permetti teedi essere eseguito con sudo, stai permettendo a QUALSIASI file di essere sovrascritto o accodato (incluso, ad esempio, /etc/passwdsolo aggiungere un nuovo account uid = 0). Puoi anche consentire l'esecuzione di tutti i comandi sudo(BTW /etc/sudoersappartiene all'insieme di "QUALSIASI file", quindi può essere sovrascritto sudo tee)
cas

2
Apparentemente, puoi limitare i file su cui è teepossibile scrivere, come descritto in questa risposta su una domanda separata. Assicurati di leggere anche i commenti, poiché alcune persone hanno avuto problemi con la sintassi originale utilizzata e suggeriscono correzioni a quel problema.
Alex,

1
@alex, sì, puoi farlo con qualsiasi comando in sudo - limita gli argomenti consentiti. La configurazione può diventare molto lunga e complicata se si desidera consentire teeo qualsiasi cosa con cui operare su molti file sudo.
CAS

0

È possibile creare uno script che esegua l'attività desiderata. Quindi, crea un nuovo account utente che può accedere solo fornendo una chiave utilizzata da OpenSSH.

Nota: chiunque sarà in grado di eseguire quello script se ha il file chiave, quindi assicurati che il file chiave OpenSSH non sia leggibile da chiunque desideri impedire l'esecuzione dell'attività.

Nella configurazione OpenSSH (nel file authorized_keys), prima di specificare la chiave, specificare un comando (seguito da uno spazio), come descritto nel testo "esempio":

command="myscript" keydata optionalComment

Questa opzione di configurazione può limitare la chiave OpenSSH all'esecuzione solo di un comando specifico. Ora hai sudo concedere le autorizzazioni, ma la configurazione OpenSSH è la parte della soluzione che viene realmente utilizzata per limitare / limitare ciò che quell'utente è in grado di fare, in modo che l'utente non stia eseguendo altri comandi. Anche questo non ha un file di configurazione "sudo" così complicato, quindi se usi OpenBSD o se il nuovo "doas" ("do as") di OpenBSD inizia a diventare più popolare (con le versioni future di qualunque sistema operativo che usi) , non dovrai essere sfidato da molta complessità nella configurazione sudo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.