Trova i file che un utente può scrivere, in modo efficiente con la creazione minima del processo


20

Io sono root. Vorrei sapere se un utente non root ha accesso in scrittura ad alcuni file - migliaia di essi. Come farlo in modo efficiente evitando la creazione di processi?


Per favore, mostraci cosa fai veramente!
F. Hauri,


Supponendo che non ti interessi delle condizioni di gara, perché non chiamare semplicemente access(2)con un UID reale impostato correttamente (ad es. Tramite setresuid(2)o l'equivalente portatile)? Voglio dire, mi farebbe fatica a farlo da bash, ma sono sicuro che Perl / Python può gestirlo.
Kevin,

1
@Kevin, le shell ' [ -wusano generalmente l'accesso (2) o equivalente. È inoltre necessario impostare le gid oltre a uid (come su o sudo do). bash non ha il supporto incorporato per quello ma zsh lo fa.
Stéphane Chazelas,

@ StéphaneChazelas - puoi usarlo chgrpin qualsiasi shell.
Mikeserv,

Risposte:


2

Forse così:

#! /bin/bash

writable()
{
    local uid="$1"
    local gids="$2"
    local ids
    local perms

    ids=($( stat -L -c '%u %g %a' -- "$3" ))
    perms="0${ids[2]}"

    if [[ ${ids[0]} -eq $uid ]]; then
        return $(( ( perms & 0200 ) == 0 ))
    elif [[ $gids =~ (^|[[:space:]])"${ids[1]}"($|[[:space:]]) ]]; then
        return $(( ( perms & 020 ) == 0 ))
    else
        return $(( ( perms & 2 ) == 0 ))
    fi
}

user=foo
uid="$( id -u "$user" )"
gids="$( id -G "$user" )"

while IFS= read -r f; do
    writable "$uid" "$gids" "$f" && printf '%s writable\n' "$f"
done

Quanto sopra esegue un singolo programma esterno per ogni file, vale a dire stat(1).

Nota: questo presuppone bash(1)e l'incarnazione di Linux di stat(1).

Nota 2: leggere i commenti di Stéphane Chazelas di seguito per i pericoli e le limitazioni di questo approccio passati, presenti, futuri e potenziali.


Ciò potrebbe dire che un file è scrivibile anche se l'utente non ha accesso alla directory in cui si trova.
Stéphane Chazelas,

Ciò presuppone che i nomi dei file non contengano caratteri di nuova riga e che i percorsi dei file passati su stdin non inizino con -. Puoi modificarlo per accettare un elenco delimitato da NUL invece conread -d ''
Stéphane Chazelas,

Nota che non esiste una statistica Linux . Linux è il kernel trovato in alcuni sistemi GNU e non GNU. Mentre ci sono comandi (come da util-linux) che sono stati scritti appositamente per Linux, quello a statcui ti riferisci non lo è, è un comando GNU che è stato portato su molti sistemi, non solo Linux. Nota anche che hai avuto un statcomando su Linux molto prima della statscrittura di GNU (il statcomando incorporato zsh).
Stéphane Chazelas,

2
@ Stéphane Chazelas: Nota che non esiste una statistica Linux. - Credo di aver scritto "l'incarnazione di Linux stat(1)". Mi riferisco a un stat(1)che accetta la -c <format>sintassi, al contrario, diciamo, della sintassi BSD -f <format>. Credo anche che fosse abbastanza chiaro a cui non mi riferivo stat(2). Sono sicuro che una pagina wiki sulla storia dei comandi comuni sarebbe piuttosto interessante.
lcd047,

1
@ Stéphane Chazelas: Questo potrebbe dire che un file è scrivibile anche se l'utente non ha accesso alla directory in cui si trova. - Vero, e probabilmente una limitazione ragionevole. Ciò presuppone che i nomi dei file non contengano caratteri di nuova riga : True e probabilmente una limitazione ragionevole. e i percorsi dei file passati su stdin non iniziano con - - A cura, grazie.
lcd047,

17

TL; DR

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

È necessario chiedere al sistema se l'utente dispone dell'autorizzazione di scrittura. L'unico modo affidabile è di cambiare l'effettivo uid, efficace gid e integrazione gid a quello dell'utente e utilizzare la access(W_OK)chiamata di sistema (anche se ha alcune limitazioni su alcuni sistemi / configurazioni).

E tieni presente che non avere il permesso di scrivere su un file non garantisce necessariamente che non puoi modificare il contenuto del file in quel percorso.

La storia più lunga

Consideriamo ciò che serve, ad esempio, per un utente $ per avere accesso in scrittura /foo/file.txt(supponendo che nessuno dei due sia /fooe /foo/file.txtsiano collegamenti simbolici)?

Ha bisogno:

  1. cerca l' accesso a /(non è necessario read)
  2. cerca l' accesso a /foo(non è necessario read)
  3. accesso in scrittura a/foo/file.txt

Puoi già vedere che gli approcci (come @ lcd047 o @ apaul's ) che controllano solo il permesso di file.txtnon funzioneranno perché potrebbero dire che file.txtsono scrivibili anche se l'utente non ha il permesso di ricerca su /o /foo.

E un approccio come:

sudo -u "$user" find / -writeble

Non funzionerà nemmeno perché non segnalerà i file nelle directory in cui l'utente non ha accesso in lettura (in quanto in findesecuzione come $usernon può elencare il loro contenuto) anche se può scrivere a loro.

Se dimentichiamo ACL, file system di sola lettura, flag FS (come immutabili), altre misure di sicurezza (apparmor, SELinux, che può persino distinguere tra diversi tipi di scrittura) e concentrarsi solo su autorizzazioni tradizionali e attributi di proprietà, per ottenere un dato il permesso (cerca o scrivi), è già abbastanza complicato e difficile da esprimere find.

Hai bisogno:

  • se il file è di tua proprietà, devi disporre dell'autorizzazione per il proprietario (o hai uid 0)
  • se il file non è di tua proprietà, ma il gruppo è uno dei tuoi, allora hai bisogno di tale autorizzazione per il gruppo (o hai uid 0).
  • se non appartiene a te e non a nessuno dei tuoi gruppi, si applicano le altre autorizzazioni (a meno che il tuo uid sia 0).

In findsintassi, qui come esempio con un utente di uid 1 e gids 1 e 2, sarebbe:

find / -type d \
  \( \
    -user 1 \( -perm -u=x -o -prune \) -o \
    \( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) -o -type l -o \
    -user 1 \( ! -perm -u=w -o -print \) -o \
    \( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

Quello elimina le directory per le quali l'utente non ha il diritto di ricerca e per altri tipi di file (esclusi i collegamenti simbolici in quanto non rilevanti), controlla l'accesso in scrittura.

Se vuoi considerare anche l'accesso in scrittura alle directory:

find / -type d \
  \( \
    -user 1 \( -perm -u=x -o -prune \) -o \
    \( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user 1 \( ! -perm -u=w -o -print \) -o \
    \( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

O per un membro arbitrario $usere il suo gruppo recuperati dal database utente:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" \( -perm -u=x -o -prune \) -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" \( ! -perm -u=w -o -print \) -o \
    \( -group $groups \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

(che è 3 processi in totale: id, sede find)

Il migliore qui sarebbe discendere l'albero come root e controllare le autorizzazioni come utente per ogni file.

 find / ! -type l -exec sudo -u "$user" sh -c '
   for file do
     [ -w "$file" ] && printf "%s\n" "$file"
   done' sh {} +

(questo è un findprocesso più uno sudoed shelabora ogni poche migliaia di file [e di printfsolito sono integrati nella shell).

O con perl:

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

(3 processi complessivamente: find, sudoe perl).

O con zsh:

files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
  [ -w $f ] && print -r -- $f
}

(0 processo in totale, ma memorizza l'intero elenco di file in memoria)

Tali soluzioni si basano sulla access(2)chiamata di sistema. Cioè invece di riprodurre l'algoritmo che il sistema utilizza per verificare i permessi di accesso, stiamo chiedendo al sistema di fare quel controllo con lo stesso algoritmo (che tiene conto delle autorizzazioni dell'account, ACL, flag immutabili, file system di sola lettura ... ) utilizzeresti se provassi ad aprire il file per la scrittura, quindi è il più vicino che otterrai una soluzione affidabile.

Per testare le soluzioni fornite qui, con le varie combinazioni di utente, gruppo e permessi, puoi fare:

perl -e '
  for $u (1,2) {
    for $g (1,2,3) {
      $d1="u${u}g$g"; mkdir$d1;
      for $m (0..511) {
        $d2=$d1.sprintf"/%03o",$m; mkdir $d2; chown $u, $g, $d2; chmod $m,$d2;
        for $uu (1,2) {
          for $gg (1,2,3) {
            $d3="$d2/u${uu}g$gg"; mkdir $d3;
            for $mm (0..511) {
              $f=$d3.sprintf"/%03o",$mm;
              open F, ">","$f"; close F;
              chown $uu, $gg, $f; chmod $mm, $f
            }
          }
        }
      }
    }
  }'

Variando l'utente tra 1 e 2 e il gruppo tra 1, 2 e 3 e limitandoci ai 9 bit inferiori delle autorizzazioni poiché sono già stati creati 9458694 file. Quello per le directory e poi di nuovo per i file.

Questo crea tutte le possibili combinazioni di u<x>g<y>/<mode1>/u<z>g<w>/<mode2>. L'utente con uid 1 e gids 1 e 2 avrebbe accesso in scrittura u2g1/010/u2g3/777ma non u1g2/677/u1g1/777per esempio.

Ora, tutte quelle soluzioni cercano di identificare i percorsi dei file che l'utente può aprire per la scrittura, che è diverso dai percorsi in cui l'utente può essere in grado di modificare il contenuto. Per rispondere a questa domanda più generica, ci sono diverse cose da prendere in considerazione:

  1. $ user potrebbe non avere accesso in scrittura /a/b/filema se possiede file(e ha accesso alla ricerca /a/b, e il file system non è di sola lettura e il file non ha il flag immutabile e ha accesso shell al sistema), quindi sarebbe in grado di modificare le autorizzazioni di filee concedere a se stesso l'accesso.
  2. Stessa cosa se possiede /a/bma non ha accesso alla ricerca.
  3. $ user potrebbe non avere accesso /a/b/fileperché non ha accesso alla ricerca /ao /a/b, ma quel file potrebbe avere un collegamento reale /b/c/filead esempio, nel qual caso potrebbe essere in grado di modificare il contenuto /a/b/fileaprendolo tramite il suo /b/c/filepercorso.
  4. Stessa cosa con i bind-mount . Egli non può avere accesso alla ricerca /a, ma /a/bpuò essere bind-montato in /c, in modo da poter aprire fileper la scrittura attraverso il suo /c/filealtro percorso.
  5. Potrebbe non avere i permessi di scrittura /a/b/file, ma se ha accesso in scrittura /a/bpuò rimuoverlo o rinominarlo filee sostituirlo con la sua versione. Modificherebbe il contenuto del file /a/b/fileanche se si tratterebbe di un file diverso.
  6. Stessa cosa se ha accesso in scrittura /a(che poteva cambiare titolo /a/ba /a/c, creare una nuova /a/bdirectory e un nuovo filein esso.

Per trovare i percorsi che $usersarebbero in grado di modificare. Per indirizzare 1 o 2, non possiamo più fare affidamento sulla access(2)chiamata di sistema. Potremmo adattare il nostro find -permapproccio per assumere l'accesso alla ricerca nelle directory o accedere in scrittura ai file non appena sei il proprietario:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" -print -o \
    \( -group $groups \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

Potremmo indirizzare 3 e 4, registrando il dispositivo e i numeri di inode o tutti i file $ user ha il permesso di scrivere e riporta tutti i percorsi di file che hanno quei numeri di dev + inode. Questa volta, possiamo usare gli access(2)approcci più affidabili :

Qualcosa di simile a:

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
  perl -l -0ne '
    ($w,$p) = /(.)(.*)/;
    ($dev,$ino) = stat$p or next;
    $writable{"$dev,$ino"} = 1 if $w;
    push @{$p{"$dev,$ino"}}, $p;
    END {
      for $i (keys %writable) {
        for $p (@{$p{$i}}) {
          print $p;
        }
      }
    }'

5 e 6 sono a prima vista complicati dal tbit delle autorizzazioni. Se applicato su directory, questo è il bit di eliminazione limitato che impedisce agli utenti (diversi dal proprietario della directory) di rimuovere o rinominare i file che non possiedono (anche se hanno accesso in scrittura alla directory).

Per esempio, se torniamo al nostro esempio precedente, se si ha accesso in scrittura /a, allora si dovrebbe essere in grado di rinominare /a/ba /a/c, e quindi ricreare una /a/bdirectory e un nuovo filelì. Ma se il tbit è impostato /ae non possiedi /a, puoi farlo solo se lo possiedi /a/b. Questo dà:

  • Se possiedi una directory, come da 1, puoi concederti l'accesso in scrittura e il bit t non si applica (e potresti rimuoverlo comunque), quindi puoi eliminare / rinominare / ricreare qualsiasi file o directory lì dentro, quindi tutti i percorsi dei file sottostanti sono tuoi da riscrivere con qualsiasi contenuto.
  • Se non lo possiedi ma hai accesso in scrittura, quindi:
    • O il tbit non è impostato e sei nello stesso caso di cui sopra (tutti i percorsi dei file sono tuoi).
    • oppure è impostato e quindi non è possibile modificare i file di cui non si è proprietari o ai quali non si ha accesso in scrittura, quindi per il nostro scopo di trovare i percorsi dei file che è possibile modificare, è lo stesso che non si ha affatto il permesso di scrittura.

Quindi possiamo affrontare tutti 1, 2, 5 e 6 con:

find / -type d \
  \( \
    -user "$user" -prune -exec find {} + -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" \( -type d -o -print \) -o \
    \( -group $groups \) \( ! -perm -g=w -o \
       -type d ! -perm -1000 -exec find {} + -o -print \) -o \
    ! -perm -o=w -o \
    -type d ! -perm -1000 -exec find {} + -o \
    -print

Questo e la soluzione per 3 e 4 sono indipendenti, puoi unire il loro output per ottenere un elenco completo:

{
  find / ! -type l -print0 |
    sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
    perl -0lne '
      ($w,$p) = /(.)(.*)/;
      ($dev,$ino) = stat$p or next;
      $writable{"$dev,$ino"} = 1 if $w;
      push @{$p{"$dev,$ino"}}, $p;
      END {
        for $i (keys %writable) {
          for $p (@{$p{$i}}) {
            print $p;
          }
        }
      }'
  find / -type d \
    \( \
      -user "$user" -prune -exec sh -c 'exec find "$@" -print0' sh {} + -o \
      \( -group $groups \) \( -perm -g=x -o -prune \) -o \
      -perm -o=x -o -prune \
    \) ! -type d -o -type l -o \
      -user "$user" \( -type d -o -print0 \) -o \
      \( -group $groups \) \( ! -perm -g=w -o \
         -type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o -print0 \) -o \
      ! -perm -o=w -o \
      -type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o \
      -print0
} | perl -l -0ne 'print unless $seen{$_}++'

Come dovrebbe essere chiaro se hai letto tutto finora, parte di esso riguarda almeno le autorizzazioni e la proprietà, non le altre funzionalità che possono concedere o limitare l'accesso in scrittura (FS di sola lettura, ACL, flag immutabili, altre funzionalità di sicurezza ...). E mentre lo elaboriamo in più fasi, alcune di queste informazioni potrebbero essere errate se i file / le directory vengono creati / eliminati / rinominati o le loro autorizzazioni / proprietà modificate mentre lo script è in esecuzione, come su un file server occupato con milioni di file .

Note sulla portabilità

Tutto quel codice è standard (POSIX, Unix per tbit) tranne:

  • -print0è un'estensione GNU ora supportata anche da alcune altre implementazioni. Con findimplementazioni che non ne hanno il supporto, puoi -exec printf '%s\0' {} +invece utilizzare e sostituire -exec sh -c 'exec find "$@" -print0' sh {} +con -exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +.
  • perlnon è un comando specificato da POSIX ma è ampiamente disponibile. Hai bisogno perl-5.6.0o sopra per -Mfiletest=access.
  • zshnon è un comando specificato da POSIX. Quel zshcodice sopra dovrebbe funzionare con zsh-3 (1995) e successivi.
  • sudonon è un comando specificato da POSIX. Il codice dovrebbe funzionare con qualsiasi versione purché la configurazione del sistema consenta l'esecuzione perlcome utente specificato.

Che cos'è un accesso di ricerca ? Non ne ho mai sentito parlare nei permessi tradizionali: leggi, scrivi, esegui.
bela83,

2
@ bela83, eseguire l' autorizzazione su una directory (non eseguire directory) si traduce in ricerca . Questa è la possibilità di accedere ai file al suo interno. È possibile elencare il contenuto di una directory se si dispone dell'autorizzazione di lettura, ma non è possibile eseguire alcuna operazione con i file al suo interno a meno che non si disponga anche xdell'autorizzazione di ricerca ( bit) sulla directory. Puoi anche avere permessi di ricerca ma non leggere , il che significa che i file lì dentro sono nascosti a te, ma se conosci il loro nome, puoi accedervi. Un esempio tipico è la directory del file di sessione php (qualcosa come / var / lib / php).
Stéphane Chazelas,

2

È possibile combinare le opzioni con il findcomando, in questo modo verranno individuati i file con la modalità e il proprietario specificati. Per esempio:

$ find / \( -group staff -o -group users \) -and -perm -g+w

Il comando precedente elenca tutte le voci che appartengono al gruppo "staff" o "utenti" e dispongono dell'autorizzazione di scrittura per quel gruppo.

Dovresti anche controllare le voci di proprietà del tuo utente e i file che sono scrivibili in tutto il mondo, quindi:

$ find / \( -user yourusername -or \
             \(  \( -group staff -o -group users \) -and -perm -g+w \
             \) -or \
            -perm -o+w \
         \)

Tuttavia, questo comando non corrisponderà alle voci con ACL esteso. Quindi puoi suscoprire tutte le voci scrivibili:

# su - yourusername
$ find / -writable

Ciò significherebbe che un file con r-xrwxrwx yourusername:anygroupo r-xr-xrwx anyone:staffè scrivibile.
Stéphane Chazelas,

Segnalerebbe anche come i file scrivibili che si trovano nelle directory yourusernamenon hanno accesso.
Stéphane Chazelas,

1

L'approccio dipende da cosa stai davvero testando.

  1. Vuoi assicurarti che l' accesso in scrittura sia possibile?
  2. Vuoi garantire la mancanza di accesso in scrittura?

Questo perché ci sono molti modi per arrivare a 2) e la risposta di Stéphane li copre bene (l'immutabile è uno da ricordare) e ricorda che ci sono anche mezzi fisici, come smontare l'unità o renderla di sola lettura in un livello hardware (schede floppy). Immagino che le tue migliaia di file siano in directory diverse e tu desideri un rapporto o stai controllando un elenco principale. (Un altro abuso di Puppet aspetta solo che accada).

Probabilmente vuoi il traversal dell'albero di Perl di Stéphane e "unire" l'output con un elenco, se necessario (su prenderà anche l'esecuzione mancante nelle directory principali?). Se la maternità surrogata è un problema di prestazioni, lo stai facendo per un "gran numero" di utenti? O è una query online? Se questo è un requisito permanente permanente, potrebbe essere il momento di prendere in considerazione un prodotto di terze parti.


0

Tu puoi fare...

find / ! -type d -exec tee -a {} + </dev/null

... per un elenco di tutti i file in cui l'utente non può scrivere come scritto su stderr nel modulo ...

"tee: cannot access %s\n", <pathname>" 

...o simili.

Vedi i commenti qui sotto per le note sui problemi che questo approccio potrebbe avere e la spiegazione di seguito sul perché potrebbe funzionare. Più saggiamente, però, dovresti probabilmente solo find file regolari come:

find / -type f -exec tee -a {} + </dev/null

In breve, teeverranno stampati errori quando si tenta di open()un file con uno dei due flag ...

O_WRONLY

Aperto solo per scrittura.

O_RDWR

Aperto per leggere e scrivere. Il risultato non è definito se questo flag viene applicato a un FIFO.

... e incontri ...

[EACCES]

L'autorizzazione di ricerca viene negata su un componente del prefisso del percorso oppure il file esiste e le autorizzazioni specificate da oflag vengono negate oppure il file non esiste e l'autorizzazione di scrittura viene negata per la directory padre del file da creare oppure O_TRUNC è specificato e il permesso di scrittura è negato.

... come specificato qui :

L' teeutilità copia l'input standard nell'output standard, eseguendo una copia in zero o più file. L'utility tee non deve bufferizzare l'output.

Se l' -aopzione non è specificata, i file di output devono essere scritti (vedere File Leggi, Scrivi e Creazione ) ...

... POSIX.1-2008 richiede funzionalità equivalenti all'utilizzo di O_APPEND ...

Perché deve controllare allo stesso modo test -w...

-w percorso

Vero se il nome percorso si risolve in una voce di directory esistente per un file per il quale verrà concessa l'autorizzazione a scrivere nel file, come definito in Lettura, scrittura e creazione del file . Falso se il nome percorso non può essere risolto o se il nome percorso viene risolto in una voce di directory esistente per un file per il quale non verrà concessa l'autorizzazione a scrivere nel file.

Entrambi controllano EACCESS .


È probabile che tu abbia un limite al numero di file aperti contemporaneamente con quell'approccio (a meno che il numero di file sia basso). Fai attenzione agli effetti collaterali con dispositivi e pipe denominate. Verrà visualizzato un messaggio di errore diverso per i socket.
Stéphane Chazelas,

@ StéphaneChazelas - Tutte le tue - Penso che potrebbe anche essere vero che si teebloccherà se non esplicitamente interrotto una volta per corsa. Era la cosa più vicina a cui potrei pensare [ -w... però - i suoi effetti dovrebbero essere vicini in quanto garantisce che l'utente possa OAPPENDERE il file. Molto più facile di entrambe le opzioni sarebbe paxcon le -oopzioni di formato e / o -tper il controllo EACCESS- ma ogni volta che suggerisco la paxgente sembra scrollarsela di dosso. E, comunque, l'unico paxche ho trovato che soddisfa lo standard è l'AST - nel qual caso potresti anche usare il loro ls.
mikeserv,
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.