Come cercare file con set di attributi immutabili?


17

Per motivi di verifica della configurazione, voglio essere in grado di cercare nel mio filesystem ext3 i file con l'attributo immutabile impostato (tramite chattr +i). Non riesco a trovare alcuna opzione findo simile che faccia questo. A questo punto, temo che dovrò scrivere il mio script per analizzare l' lsattroutput di ciascuna directory. Esiste un'utilità standard che fornisce un modo migliore?


Avrei dovuto chiarire che nel mio caso, sto controllando solo per la gestione della configurazione, non per il rilevamento delle intrusioni, quindi non devo preoccuparmi troppo delle newline, poiché so che i nomi dei file con cui sto lavorando non avranno loro. Tuttavia, vale la pena tenere presente la questione della newline, quindi lascerò la mia domanda così com'è.
depquid

Risposte:


9

Può essere parzialmente realizzato eseguendo il piping del grepcomando per lsattrcomandare.

lsattr -R | grep +i

Tuttavia, credo che quando si parla l'intero ext3file system la ricerca potrebbe comportare /proc, /deve alcune altre directory che se i rapporti un po 'di errore che si desidera semplicemente ignorare. Probabilmente puoi eseguire il comando come,

lsattr -R 2>/dev/null | grep -- "-i-"

Potresti voler rendere grepun po 'più rigoroso usando grepla funzione PCRE per abbinare più esplicitamente il "-i-".

lsattr -R 2>/dev/null | grep -P "(?<=-)i(?=-)"

Questo funzionerà quindi per situazioni come questa:

$ lsattr -R 2>/dev/null afile | grep -P "(?<=-)i(?=-)"
----i--------e-- afile

Ma è imperfetto. Se ci sono ulteriori attributi abilitati attorno al flag immutabile, allora non li abbineremo e questo verrà ingannato dai file i cui nomi coincidono anche con il modello sopra, come questo:

$ lsattr -R 2>/dev/null afile* | grep -P "(?<=-)i(?=-)"
----i--------e-- afile
-------------e-- afile-i-am

Possiamo stringere un po 'di più il modello in questo modo:

$ lsattr -a -R 2>/dev/null afile* | grep -P "(?<=-)i(?=-).* "
----i--------e-- afile

Ma è ancora un po 'troppo fragile e richiederebbe ulteriori modifiche a seconda dei file all'interno del tuo filesystem. Per non parlare del fatto che @StephaneChazeles ha menzionato nei commenti che ciò può essere facilmente risolto dall'inclusione di nuove righe con un nome di file a cui aggirare il modello sopra grep.

Riferimenti

https://groups.google.com/forum/#!topic/alt.os.linux/LkatROg2SlM


1
Ah, ho letto la stessa discussione e stavo per pubblicare. Aggiungerò invece i miei extra ai tuoi.
slm

@slm, sei il benvenuto per apportare eventuali modifiche :)
Ramesh,

2
Probabilmente non va bene per l'auditing in quanto si può falsificare o nascondere un file immutabile avendo caratteri di nuova riga nel nome del file con quell'approccio. Inoltre, non è raro che i nomi dei file abbiano il -i-suo nome (ce ne sono 34 sul sistema a cui sono attualmente connesso). Probabilmente vorrai anche l' -aopzione
Stéphane Chazelas

1
Solo per curiosità, cosa si +isuppone debba essere nel primo esempio? Non funziona per me. Inoltre, il grepping per -i-presuppone che gli attributi che appaiono adiacenti i(come a) non siano impostati.
depquid

1
Perché non semplicemente grep per ^....i? O almeno qualcosa come ^[^ ]*ise il ipuò essere in una posizione diversa dalla quinta.
Ruslan,

6

Dato che lo scopo dello script è il controllo, è particolarmente importante trattare correttamente nomi di file arbitrari, ad esempio con nomi contenenti nuove righe. Ciò rende impossibile l'utilizzo lsattrsu più file contemporaneamente, poiché l'output di lsattrpuò essere ambiguo in quel caso.

Puoi fare ricorso finde chiamare lsattrun file alla volta. Sarà piuttosto lento però.

find / -xdev -exec sh -c '
  for i do
     attrs=$(lsattr -d "$i"); attrs=${attrs%% *}
     case $attrs in
       *i*) printf "%s\0" "$i";;
     esac
  done' sh {} +

Consiglio di usare un linguaggio meno volgare come Perl, Python o Ruby e di fare il lavoro lsattrda solo. lsattropera emettendo un FS_IOC_GETFLAGSsyscall ioctl e recuperando i flag di inode del file . Ecco una prova di concetto di Python.

#!/usr/bin/env python2
import array, fcntl, os, sys
FS_IOC_GETFLAGS = 0x80086601
EXT3_IMMUTABLE_FL = 0x00000010
count = 0
def check(filename):
    fd = os.open(filename, os.O_RDONLY)
    a = array.array('L', [0])
    fcntl.ioctl(fd, FS_IOC_GETFLAGS, a, True)
    if a[0] & EXT3_IMMUTABLE_FL: 
        sys.stdout.write(filename + '\0')
        global count
        count += 1
    os.close(fd)
for x in sys.argv[1:]:
    for (dirpath, dirnames, filenames) in os.walk(x):
        for name in dirnames + filenames:
            check(os.path.join(dirpath, name))
if count != 0: exit(1)

1
FYI sul mio sistema FS_IOC_GETFLAGSè 0x80046601.
antonone,

1
Il valore di FS_IOC_GETFLAGSdipende da sizeof(long). Si veda ad esempio il seguente comando bash per scoprire quali sono i espande macro a in C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Ne ho tratto la seguente espressione (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8))):, che semplifica (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan,

3

Per gestire nomi di file arbitrari (compresi quelli che contengono caratteri di nuova riga), il solito trucco è trovare i file all'interno .//.anziché .. Poiché //normalmente non può verificarsi durante l'attraversamento dell'albero delle directory, si è certi che a //segnali l'inizio di un nuovo nome file nell'output find(o qui lsattr -R).

lsattr -R .//. | awk '
  function process() {
    i = index(record, " ")
    if (i && index(substr(record,1,i), "i"))
      print substr(record, i+4)
  }
  {
    if (/\/\//) {
      process()
      record=$0
    } else {
      record = record "\n" $0
    }
  }
  END{process()}'

Si noti che l'output sarà comunque separato da nuova riga. Se è necessario post-elaborarlo, è necessario adattarlo. Ad esempio, potresti aggiungere a -v ORS='\0'per poterlo alimentare su GNU xargs -r0.

Si noti inoltre che lsattr -R(almeno 1.42.13) non è possibile segnalare i flag di file il cui percorso è più grande di PATH_MAX (in genere 4096), quindi qualcuno potrebbe nascondere un file così immutabile spostando la sua directory principale (o qualsiasi componente del percorso che porta a esso, tranne se stesso in quanto è immutabile) in una directory molto profonda.

Una soluzione sarebbe usare findcon -execdir:

find . -execdir sh -c '
  a=$(lsattr -d "$1") &&
    case ${a%% *} in
      (*i*) ;;
      (*) false
    esac' sh {} \; -print0

Ora, con -print0, è post-processabile, ma se si intende fare qualcosa con quei percorsi, si noti che qualsiasi chiamata di sistema su percorsi di file superiori a PATH_MAX fallirebbe comunque e che i componenti della directory avrebbero potuto essere rinominati nell'intervallo.

Se vogliamo ottenere un rapporto affidabile su un albero di directory potenzialmente scrivibile da altri, ci sono alcuni altri problemi inerenti al lsattrcomando stesso che dovremmo menzionare:

  • il modo in cui lsattr -R .attraversa l'albero delle directory, è soggetto alle condizioni di gara. Si può far scendere le directory all'esterno dell'albero delle directory indirizzate .sostituendo alcune directory con collegamenti simbolici al momento giusto.
  • lsattr -d fileha persino una condizione di gara. Tali attributi sono applicabili solo a file o directory regolari. Quindi lsattrfa un lstat()primo controllo per verificare che il file sia dei tipi giusti e quindi open()seguito ioctl()per recuperare gli attributi. Ma chiama open()senza O_NOFOLLOW(né O_NOCTTY). Qualcuno potrebbe sostituire filecon un collegamento simbolico, ad /dev/watchdogesempio, tra lstat()e open()e far riavviare il sistema. Dovrebbe essere open(O_PATH|O_NOFOLLOW)seguito da fstat(), openat()e ioctl()qui per evitare le condizioni di gara.

2

Grazie a Ramesh, slm e Stéphane per avermi indicato nella giusta direzione (mi mancava l' -Rinterruttore per lsattr). Sfortunatamente, nessuna delle risposte finora ha funzionato correttamente per me.

Ho pensato a quanto segue:

lsattr -aR .//. | sed -rn '/i.+\.\/\/\./s/\.\/\///p'

Questo protegge da newline utilizzate per far apparire un file immutabile quando non lo è. Esso non protegge i file che sono impostati come immutabile e che hanno a capo nei loro nomi di file. Ma dal momento che un tale file dovrebbe essere creato in questo modo dalla radice, posso essere sicuro che tali file non esistano nel mio filesystem per il mio caso d'uso. (Questo metodo non è adatto per il rilevamento delle intrusioni nei casi in cui l'utente root può essere compromesso, ma non utilizza nemmeno la stessa lsattrutility del sistema che è anche di proprietà dello stesso utente root.)


Solo root può aggiungere il bit immutabile a un file, ma potenzialmente altri utenti possono in seguito rinominare i componenti del percorso che portano a tali file, quindi il percorso del file può contenere newline. Inoltre un utente può creare un percorso di file (non immutabile) che ingannerebbe il tuo script nel pensare che un altro file sia immutabile.
Stéphane Chazelas,

2

L'uso find -execè troppo lento, l'analisi dell'output di lsattrè inaffidabile in modo simile a quello dils , l'utilizzo di Python come nella risposta di Gilles richiede di scegliere la costante a ioctlseconda che l'interprete Python sia a 32 o 64 bit ...

Il problema a portata di mano è più o meno di basso livello, quindi andiamo al livello inferiore: C ++ non è poi così male come un linguaggio di scripting :) Come bonus, ha accesso alle intestazioni di sistema C con piena potenza del preprocessore C.

Il seguente programma cerca file immutabili, rimanendo all'interno di un filesystem, cioè non attraversa mai i punti di mount. Per cercare l'albero apparente, attraversando i punti di montaggio secondo necessità, rimuovere il FTW_MOUNTflag nella nftwchiamata. Inoltre non segue i collegamenti simbolici. Per seguirli, rimuovere la FTW_PHYSbandiera.

#define _FILE_OFFSET_BITS 64
#include <iostream>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <sys/stat.h>
#include <ftw.h>

bool isImmutable(const char* path)
{
    static const int EXT3_IMMUTABLE_FLAG=0x10;

    const int fd=open(path,O_RDONLY|O_NONBLOCK|O_LARGEFILE);
    if(fd<=0)
    {
        perror(("Failed to open file \""+std::string(path)+"\"").c_str());
        return false;
    }
    unsigned long attrs;
    if(ioctl(fd,FS_IOC_GETFLAGS,&attrs)==-1)
    {
        perror(("Failed to get flags for file \""+std::string(path)+"\"").c_str());
        close(fd);
        return false;
    }
    close(fd);
    return attrs & EXT3_IMMUTABLE_FLAG;
}

int processPath(const char* path, const struct stat* info, int type, FTW* ftwbuf)
{
    switch(type)
    {
    case FTW_DNR:
        std::cerr << "Failed to read directory: " << path << "\n";
        return 0;
    case FTW_F:
        if(isImmutable(path))
            std::cout << path << '\n';
        return 0;
    }
    return 0;
}

int main(int argc, char** argv)
{
    if(argc!=2)
    {
        std::cerr << "Usage: " << argv[0] << " dir\n";
        return 1;
    }
    static const int maxOpenFDs=15;
    if(nftw(argv[1],processPath,maxOpenFDs,FTW_PHYS|FTW_MOUNT))
    {
        perror("nftw failed");
        return 1;
    }
}

-1

Invece di eseguire il piping dell'output su grep, perché non usare semplicemente awk per far corrispondere solo la 'i' nel primo campo dell'output?

lsattr -Ra 2>/dev/null /|awk '$1 ~ /i/ && $1 !~ /^\// {print}'

In effetti, lo eseguo quotidianamente tramite cron per scansionare la directory / etc su centinaia di server e inviare l'output a syslog. Posso quindi generare un rapporto giornaliero tramite Splunk:

lsattr -Ra 2>/dev/null /etc|awk '$1 ~ /i/ && $1 !~ /^\// {print "Immutable_file="$2}'|logger -p local0.notice -t find_immutable

Il tuo primo frammento di codice ha un refuso e il secondo non trova file immutabili sul mio sistema.
depquid

Risolto l'errore di battitura nel primo comando. Forse il secondo non trova alcun file immutabile perché non ce ne sono?
Rootdev,

Non ho notato che il secondo comando stava solo cercando /etc. Ma entrambi i comandi trovano erroneamente un file non immutabile creato contouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid

Dice nel mio post originale che lo sto eseguendo tramite cron per scansionare la directory / etc. Non posso farci niente se non hai letto il post o il comando prima di eseguirlo. E sì, probabilmente puoi costruire un caso limite per ingannare qualsiasi ricerca se ne hai voglia, ma poiché sei stato così veloce da sottolineare l'errore nel mio comando originale (manca l'ultimo '), ti faccio notare che il tuo comando non funziona come scritto, quindi non creerà nulla! :-)
Rootdev,

Colpa mia. Prova questo:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid

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.