Jak wyszukiwać pliki z niezmiennym zestawem atrybutów?


17

Z przyczyn związanych z inspekcją konfiguracji chcę móc przeszukiwać mój system plików ext3 w poszukiwaniu plików, które mają niezmienny atrybut (via chattr +i). Nie mogę znaleźć żadnych opcji findpodobnych lub podobnych. W tym momencie obawiam się, że będę musiał napisać własny skrypt, aby przeanalizować lsattrdane wyjściowe dla każdego katalogu. Czy istnieje standardowe narzędzie, które zapewnia lepszy sposób?


Powinienem był wyjaśnić, że w moim przypadku kontroluję tylko zarządzanie konfiguracją, a nie wykrywanie włamań, więc nie muszę się zbytnio przejmować nowymi liniami, ponieważ wiem, że nazwy plików, z którymi pracuję, nie będą miały im. Niemniej jednak warto pamiętać o nowej linii, więc moje pytanie pozostawiam bez zmian.
depquid

Odpowiedzi:


9

Można to częściowo osiągnąć poprzez przekazanie greppolecenia do lsattrpolecenia.

lsattr -R | grep +i

Uważam jednak, kiedy można wymienić cały ext3system plików poszukiwanie może wiązać /proc, /devi kilka innych katalogów, które jeśli raportach jakiś błąd po prostu chcesz ignorować. Prawdopodobnie możesz uruchomić polecenie jako

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

Być może zechcesz grepnieco bardziej zaostrzyć, używając funkcji grepPCRE w celu wyraźniejszego dopasowania do „-i-”.

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

Będzie to wtedy działać w takich sytuacjach:

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

Ale jest niedoskonały. Jeśli wokół niezmiennej flagi są włączone dodatkowe atrybuty, nie dopasujemy ich, a to zostanie oszukane przez pliki, których nazwy również pasują do powyższego wzorca, takie jak:

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

Możemy zaostrzyć wzór bardziej w ten sposób:

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

Ale wciąż jest zbyt delikatny i wymagałby dodatkowych poprawek w zależności od plików w systemie plików. Nie wspominając już o tym, jak @StephaneChazeles wspomniał w komentarzach, że można w to dość łatwo grać, włączając nowe linie z nazwą pliku, aby pominąć powyższy wzorzec grep.

Bibliografia

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


1
Ha, przeczytałem ten sam wątek i miałem zamiar opublikować. Zamiast tego dodam moje dodatki do twoich.
slm

@slm, możesz wprowadzić wszelkie zmiany :)
Ramesh

2
Prawdopodobnie nie nadaje się do audytu, ponieważ można sfałszować lub ukryć niezmienny plik, mając takie znaki w nazwie pliku. Często zdarza się, że nazwy plików mają -i-w nazwie (w systemie, na którym aktualnie jestem zalogowany, jest 34). Prawdopodobnie będziesz również chciał -aopcję
Stéphane Chazelas

1
Właśnie z ciekawości, po co +ima być ten pierwszy przykład? To nie działa dla mnie. Ponadto grepping dla -i-zakłada, że ​​atrybuty, które pojawiają się obok i(takie jak a) są nieustawione.
depquid

1
Dlaczego nie po prostu grep ^....i? Lub przynajmniej coś takiego, ^[^ ]*ijeśli imoże być w innej pozycji niż piąta.
Ruslan

6

Biorąc pod uwagę, że celem skryptu jest kontrola, szczególnie ważne jest prawidłowe postępowanie z dowolnymi nazwami plików, np. Z nazwami zawierającymi znaki nowej linii. Uniemożliwia to jednoczesne używanie lsattrwielu plików, ponieważ lsattrw takim przypadku wynik może być niejednoznaczny.

Możesz powtarzać się findi wywoływać lsattrjeden plik naraz. Ale będzie dość wolno.

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 {} +

Polecam używanie mniej zepsutego języka, takiego jak Perl, Python lub Ruby i lsattrsamodzielne wykonywanie pracy . lsattrdziała poprzez wydanie FS_IOC_GETFLAGSwywołania systemowego ioctl i pobranie flag i-węzłów pliku . Oto dowód koncepcji Pythona.

#!/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 w moim systemie FS_IOC_GETFLAGSto 0x80046601.
antonon

1
Wartość FS_IOC_GETFLAGSzależy od sizeof(long). Patrz na przykład następujące polecenie bash, aby dowiedzieć się, co makro rozwija się w katalogu C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Otrzymałem z niego następujące wyrażenie (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8))):, które upraszcza (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan

To dusi się na fifo.
Roadowl

3

Aby poradzić sobie z dowolnymi nazwami plików (w tym zawierającymi znaki nowego wiersza), zwykłą sztuczką jest znajdowanie plików w środku .//.zamiast .. Ponieważ //normalnie nie może się zdarzyć podczas przeglądania drzewa katalogów, masz pewność, że //sygnał wyjściowy find(lub tutaj lsattr -R) sygnalizuje początek nowej nazwy pliku .

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()}'

Zauważ, że wyjście nadal będzie oddzielone znakiem nowej linii. Jeśli musisz go przetworzyć później, musisz go dostosować. Na przykład, możesz dodać a, -v ORS='\0'aby móc karmić go GNU xargs -r0.

Zauważ również, że lsattr -R(przynajmniej 1.42.13) nie może zgłosić flag plików, których ścieżka jest większa niż PATH_MAX (zwykle 4096), więc ktoś może ukryć taki niezmienny plik, przenosząc jego katalog nadrzędny (lub dowolny ze składników ścieżki, które prowadzą do z wyjątkiem siebie, ponieważ jest niezmienny) do bardzo głębokiego katalogu.

Obejściem byłoby użycie findz -execdir:

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

Teraz, z -print0, to jest przetwarzalne, ale jeśli zamierzasz cokolwiek zrobić z tymi ścieżkami, zwróć uwagę, że każde wywołanie systemowe na ścieżkach plików większych niż PATH_MAX nadal nie powiedzie się, a komponenty katalogu mogą zostać zmienione w przedziale czasowym.

Jeśli mamy uzyskać rzetelny raport o drzewie katalogów, który może zostać zapisany przez innych, istnieje kilka innych problemów związanych z samym lsattrpoleceniem, o których powinniśmy wspomnieć:

  • sposób lsattr -R .przechodzenia przez drzewo katalogów, podlega warunkom wyścigu. Można sprawić, że zejdzie do katalogów spoza drzewa katalogów kierowanych do ., zastępując niektóre katalogi dowiązaniami symbolicznymi w odpowiednim momencie.
  • lsattr -d filema nawet warunki wyścigowe. Te atrybuty mają zastosowanie tylko do zwykłych plików lub katalogów. Tak lsattrrobi lstat()najpierw sprawdzić, czy plik ma odpowiednich typów i wtedy nie open()następuje ioctl()do pobierania atrybutów. Ale wywołuje open()bez O_NOFOLLOW(ani O_NOCTTY). Ktoś mógłby zastąpić filew jego miejsce linku do /dev/watchdogna przykład pomiędzy lstat()a open()i spowodować, że system restartu. Powinno to open(O_PATH|O_NOFOLLOW)nastąpić fstat(), openat()a ioctl()tutaj, aby uniknąć warunków wyścigu.

2

Dzięki Ramesh, slm i Stéphane za skierowanie mnie we właściwym kierunku (brakowało mi -Rprzełącznika lsattr). Niestety, żadna z dotychczasowych odpowiedzi nie działała poprawnie dla mnie.

Wymyśliłem następujące:

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

Chroni to przed użyciem nowego wiersza, aby plik wyglądał na niezmienny, gdy tak nie jest. Robi nie chronią przed plików, które są ustawione jako niezmienne i mają nowe linie w ich nazwach. Ale ponieważ taki plik musiałby zostać utworzony w ten sposób przez root, mogę być pewien, że takie pliki nie istnieją w moim systemie plików dla mojego przypadku użycia. (Ta metoda nie jest odpowiednia do wykrywania włamań w przypadkach, w których użytkownik root może zostać narażony na szwank, ale wtedy nie używa tego samego lsattrnarzędzia systemowego, które jest również własnością tego samego użytkownika root).


Tylko root może dodać niezmienny bit do pliku, ale potencjalnie inni użytkownicy mogą później zmienić nazwy komponentów ścieżki, które prowadzą do tych plików, więc ścieżka do pliku może zawierać znak nowej linii. Użytkownik może również utworzyć ścieżkę do pliku (niezmienną), która oszukałaby skrypt w przekonaniu, że inny plik jest niezmienny.
Stéphane Chazelas

2

Używanie find -execjest zbyt wolne, przetwarzanie parsowania lsattrjest niewiarygodne podobniels jak w przypadku używania Pythona, tak jak w odpowiedzi Gillesa, wymaga wybrania stałej ioctlzależnej od tego, czy interpreter Pythona jest 32- czy 64-bitowy ...

Problem jest mniej więcej na niskim poziomie, więc zejdźmy na niższy poziom: C ++ nie jest taki zły jak język skryptowy :) Jako bonus, ma dostęp do nagłówków systemu C z pełną mocą preprocesora C.

Poniższy program wyszukuje niezmienne pliki, pozostając w jednym systemie plików, tzn. Nigdy nie przekracza punktów montowania. Aby przeszukać widoczne drzewo, w razie potrzeby przekraczając punkty montowania, usuń FTW_MOUNTflagę w nftwwywołaniu. Również nie podąża za dowiązaniami symbolicznymi. Aby to zrobić, usuń FTW_PHYSflagę.

#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

Zamiast przesyłać dane wyjściowe do grep, dlaczego nie użyć awk, aby dopasować tylko „i” w pierwszym polu wyniku?

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

W rzeczywistości uruchamiam to codziennie przez cron, aby skanować katalog / etc na setkach serwerów i wysyłać dane wyjściowe do syslog. Następnie mogę wygenerować codzienny raport za pośrednictwem Splunk:

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

Twój pierwszy fragment kodu ma literówkę, a drugi nie znajduje niezmiennych plików w moim systemie.
depquid

Naprawiono literówkę w pierwszym poleceniu. Być może drugi nie znajduje żadnych niezmiennych plików, ponieważ nie ma żadnych?
Rootdev

Nie zauważyłem, że drugie polecenie tylko się zagląda /etc. Ale oba polecenia niepoprawnie znajdują niezmienny plik utworzony przy pomocytouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid

W moim oryginalnym poście jest napisane, że uruchamiam to za pomocą crona, aby przeskanować katalog / etc. Nic na to nie poradzę, jeśli nie przeczytałeś ani postu, ani polecenia przed jego uruchomieniem. I tak, prawdopodobnie możesz skonstruować skrzynkę brzegową, aby oszukiwać przy każdym wyszukiwaniu, jeśli masz na to ochotę, ale ponieważ tak szybko wskazałeś literówkę w moim oryginalnym poleceniu (brakuje ostatniego '), zaznaczę, że twoje polecenie nie działa tak, jak napisano, więc nic nie stworzy! :-)
Rootdev

Mój błąd. Spróbuj tego:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.