Як шукати файли з незмінним набором атрибутів?


17

З причини аудиту конфігурації я хочу мати змогу шукати у моїй файловій системі ext3 файли, у яких встановлений незмінний атрибут (via chattr +i). Я не можу знайти жодних варіантів для findподібного або подібного. На даний момент, я боюся, що мені доведеться написати власний сценарій для розбору lsattrрезультатів для кожного каталогу. Чи є стандартна утиліта, яка забезпечує кращий спосіб?


Я мав би уточнити, що в моєму випадку я перевіряю лише конфігураційне управління, а не виявлення вторгнень, тому мені не доведеться надто переживати про нові рядки, оскільки я знаю, що імена файлів, з якими я працюю, не матимуть їх. Тим не менше, питання про новий рядок варто пам’ятати, тому я залишу своє питання таким, яким є.
Декап

Відповіді:


9

Частково це можна здійснити, проклавши grepкоманду lsattrкомандувати.

lsattr -R | grep +i

Однак я вважаю, що ви згадуєте всю ext3файлову систему, до якої може входити пошук /proc, /devа також деякі інші каталоги, які, якщо повідомляють про якусь помилку, ви просто хочете проігнорувати. Ви, ймовірно, можете запустити команду як,

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

Можливо, ви хочете зробити grepтрохи більш суворим, використовуючи засоби grepPCRE, щоб більш чітко відповідати "-i-".

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

Потім це буде працювати в таких ситуаціях:

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

Але є недосконалим. Якщо навколо непорушного прапора включені додаткові атрибути, ми не будемо їх узгоджувати, і це буде обдурене файлами, чиї імена також збігаються з вищевказаним шаблоном, наприклад, таким:

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

Ми можемо трохи підтягнути схему приблизно так:

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

Але це все ще занадто крихко і вимагає додаткових налаштувань залежно від файлів у вашій файловій системі. Не кажучи вже про те, як @StephaneChazeles в коментарях згадував, що це можна легко зіграти, включивши нові рядки з ім'ям файлів для обходу вищевказаного шаблону grep.

Список літератури

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


1
Ха я прочитав ту саму нитку і збирався дописувати. Я замість цього додаду ваші додаткові.
slm

@slm, ви можете запросити будь-які зміни :)
Рамеш

2
Напевно, це не добре для аудиту, оскільки можна підробити або приховати незмінний файл, якщо у такому підході в імені файлів є символи нового рядка. Крім того, не рідкість, щоб імена файлів мали -i-своє ім’я (у системі, на якій я зараз увійшов, є 34). Напевно, вам також захочеться -aваріант
Стефан Шазелас

1
Щойно з цікавості, для чого +iпередбачається перший приклад? Це не працює для мене. Крім того, поздоровлення -i-передбачає, що атрибути, які є суміжними з i(наприклад, a), не встановлені.
Декап

1
Чому б просто не поздоровитись ^....i? Або принаймні щось на кшталт, ^[^ ]*iякщо iможе бути в іншій позиції, ніж п'ята.
Руслан

6

Зважаючи на те, що метою сценарію є аудит, особливо важливо правильно поводитися з довільними іменами файлів, наприклад з іменами, що містять нові рядки. Це робить неможливим використанняlsattr одночасне на декількох файлах, оскільки вихід lsattrу цьому випадку може бути неоднозначним.

Ви можете повторно записувати один файл findі запускати його lsattrза один раз. Хоча це буде досить повільно.

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

Я рекомендую використовувати менш хитрую мову, наприклад Perl, Python або Ruby, і виконувати роботу lsattrсамостійно. lsattrфункціонує шляхом випуску FS_IOC_GETFLAGSсистемного виклику ioctl та отримання прапорців inode файлу . Ось концепція підтвердження Python.

#!/usr/bin/env python2
import array, fcntl, os, sys
S_IFMT =  0o170000
S_IFDIR = 0o040000
S_IFREG = 0o100000
FS_IOC_GETFLAGS = 0x80086601
EXT3_IMMUTABLE_FL = 0x00000010
count = 0
def check(filename):
    mode = os.lstat(filename).st_mode
    if mode & S_IFMT not in [S_IFREG, S_IFDIR]:
        return
    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 в моїй системі FS_IOC_GETFLAGSє 0x80046601.
антонон

1
Значення FS_IOC_GETFLAGSзалежить від sizeof(long). Дивіться, наприклад, наступну команду bash, щоб дізнатися, до чого макрос розширюється в C : gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Я отримав від нього такий вираз:, (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8)))який спрощує (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Руслан

Це задихається від фіфо.
Родоль

@Roadowl Дійсно. lsattrпропускає тих, хто попереджає повідомлення. Я проклеював свій код підтвердження концепції, щоб їх мовчки пропустити.
Жил "ТАК - перестань бути злим"

3

Для роботи з довільними іменами файлів (включаючи ті, що містять символи нового рядка), звичайний трюк - знайти файли всередині, .//.а не .. Оскільки //зазвичай не може виникати під час переходу до дерева каталогів, ви впевнені, що //сигналізує про початок нового імені файлу у find(або тут 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()}'

Зауважте, що вихідні дані все ще будуть відокремлені в новому рядку. Якщо вам потрібно буде потім обробити його, вам доведеться його адаптувати. Наприклад, ви можете додати a, -v ORS='\0'щоб мати змогу подавати його до GNU xargs -r0.

Також зауважте, що lsattr -R(принаймні 1,42.13) не може повідомити про прапори файлів, шлях яких перевищує PATH_MAX (зазвичай 4096), тому хтось міг приховати такий незмінний файл, перемістивши його батьківський каталог (або будь-який з компонентів шляху, що призводять до це, за винятком самого себе, як це непорушно) в дуже глибокий каталог.

Робота навколо була б корисна findдля -execdir:

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

Тепер, з цим -print0, це вже обробляється, але якщо ви збираєтесь робити що-небудь із цими шляхами, зауважте, що будь-який системний виклик на файлових шляхах, більших за PATH_MAX , все одно не вдасться, і компоненти каталогу могли бути перейменовані в інтервал.

Якщо ми отримаємо надійний звіт про дерево каталогів, яке потенційно може бути записано іншими, є ще кілька проблем, притаманних самій lsattrкоманді, які нам потрібно буде зазначити:

  • те, як lsattr -R .переходить дерево каталогів, воно підпорядковане умовам гонки. Можна змусити його спускатися до каталогів поза деревом каталогів, спрямованим на це ., замінюючи деякі каталоги символьними посиланнями в потрібний момент.
  • навіть lsattr -d fileмає перегони. Ці атрибути застосовні лише до звичайних файлів або каталогів. Так що lsattrробить lstat()першим , щоб перевірити , що файл правих типів , а потім робить open()потім , ioctl()щоб витягти атрибути. Але він дзвонить open()без O_NOFOLLOW(ні O_NOCTTY). Хто - то могли б замінити fileз лінком , щоб /dev/watchdog, наприклад , між lstat()і open()і викликати перезавантаження системи. Він повинен робити open(O_PATH|O_NOFOLLOW)потім fstat(), openat()і ioctl()тут , щоб уникнути умов гонки.

2

Завдяки Рамешу, slm та Stéphane за те, що спрямували мене в правильному напрямку (мені не вистачало -Rперемикача lsattr). На жаль, жодна з відповідей поки що не працювала для мене правильно.

Я придумав таке:

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

Це захищає від нових рядків, які використовуються для того, щоб файл видався незмінним, коли його немає. Він не захищає від файлів, які встановлені як незмінні та містять нові рядки у своїх іменах. Але оскільки такий файл повинен бути створений таким чином під корінь, я можу бути впевнений, що такі файли не існують у моїй файловій системі для мого випадку використання. (Цей метод не підходить для виявлення вторгнень у випадках, коли користувач root може бути порушений, але тоді також не використовується lsattrутиліта тієї самої системи, яка також належить тому ж користувачу root.)


Лише корінь може додати до файлу незмінний біт, але потенційно інші користувачі можуть пізніше перейменувати компоненти контуру, які ведуть до цих файлів, тому шлях до файлу може містити новий рядок. Також користувач може створити шлях до файлу (не незмінний), який би обдурив ваш сценарій, думаючи, що інший файл є незмінним.
Стефан Хазелас

2

Використання find -execзанадто повільно, синтаксичний аналіз виведення lsattrє ненадійною аналогічно доказуls , використовуючи Python , як і в відповідь Жиля вимагає , щоб вибрати константу в ioctlзалежності від того , інтерпретатор Python є 32- або 64-розрядний ...

Проблема є більш-менш низьким рівнем, тому давайте знизимось: C ++ не так вже й погано, як мова сценаріїв :) Як бонус, він має доступ до заголовків системи C з повною потужністю препроцесора C.

Наступна програма шукає непорушні файли, перебуваючи в одній файловій системі, тобто ніколи не перетинає точки монтажу. Для пошуку очевидного дерева, перекресливши точки монтажу, якщо потрібно, зніміть FTW_MOUNTпрапор у nftwвиклику. Крім того, воно не слідує за посиланнями. Для цього слід видалити FTW_PHYSпрапор.

#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

Замість того, щоб переводити висновок на grep, чому б просто не використати awk, щоб відповідати лише "я" в першому полі виводу?

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

Справді, я щодня запускаю це через cron, щоб сканувати каталог / etc на сотнях серверів і надсилати вихід у syslog. Тоді я можу створювати щоденний звіт через Splunk:

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

У вашому першому фрагменті коду є помилка друку, а ваш другий не знаходить непорушних файлів у моїй системі.
depquid

Виправлена ​​помилка в першій команді. Можливо, другий не знаходить жодних незмінних файлів, оскільки їх немає?
Rootdev

Я не помітив, що друга команда лише шукала /etc. Але обидві команди неправильно знаходять touch `"echo -e "bogus\n---------i---e-- changeable"`"
незмінений

У моєму початковому дописі написано, що я запускаю це через cron, щоб сканувати каталог / etc. Я не можу йому допомогти, якщо ви не прочитали ані повідомлення, ані команди перед його запуском. І так, ви, ймовірно, можете побудувати крайовий випадок, щоб обдурити будь-який пошук, якщо вам це подобається, але оскільки ви так швидко вказували на друкарську помилку в моїй оригінальній команді (пропускаючи останню '), я зазначу, що ваша команда не працює як написано, тому нічого не створить! :-)
Rootdev

Моє ліжко. Спробуйте так:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.