Как искать файлы с установленным неизменным атрибутом?

17

По причинам, связанным с проверкой конфигурации, я хочу иметь возможность искать в моей файловой системе ext3 файлы, для которых установлен неизменный атрибут (через chattr +i). Я не могу найти какие-либо варианты findили аналогичные, которые делают это. На данный момент, я боюсь, мне придется написать свой собственный скрипт для разбора lsattrвывода для каждого каталога. Есть ли стандартная утилита, которая обеспечивает лучший способ?

depquid
источник
Я должен был уточнить, что в моем случае я проверяю только для управления конфигурацией, а не для обнаружения вторжений, поэтому мне не нужно слишком беспокоиться о новых строках, так как я знаю, что имена файлов, с которыми я работаю, не будут иметь их. Тем не менее, стоит помнить о новой строке, поэтому я оставлю свой вопрос как есть.
depquid

Ответы:

9

Это может быть частично выполнено путем передачи grepкоманды в lsattrкоманду.

lsattr -R | grep +i

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

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

Возможно, вы захотите сделать grepэто немного более строгим, используя grepсредство PCRE для более явного соответствия "-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, вы можете вносить любые изменения :)
Рамеш
2
Вероятно, не годится для аудита, так как можно подделать или скрыть неизменяемый файл, используя символы новой строки в имени файла при таком подходе. Кроме того, это не редкость, когда имена файлов имеют -i-свое имя (в системе, в которой я в настоящий момент вошел 34). Вы, вероятно, захотите и этот -aвариант
Стефан Шазелас
1
Просто из любопытства, что +iдолжно быть в первом примере? Это не работает для меня. Кроме того, grepping для -i-предполагает, что атрибуты, которые появляются рядом с i(например, a), не установлены.
depquid
1
Почему бы просто не grep для ^....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
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
К вашему сведению в моей системе FS_IOC_GETFLAGSесть 0x80046601.
антонон
1
Значение FS_IOC_GETFLAGSзависит от sizeof(long). См , например , следующая команда Баша , чтобы выяснить , что макрос в 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).
Руслан
3

Чтобы иметь дело с произвольными именами файлов (включая те, которые содержат символы новой строки), обычная хитрость заключается в том, чтобы искать файлы внутри, .//.а не .. Поскольку //при обходе дерева каталогов обычно это не происходит, вы уверены, что в выходных данных (или здесь ) появляется //сигнал начала нового имени файла .findlsattr -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

Спасибо Рамешу, Слм и Стефану за то, что они указали мне правильное направление (мне не хватало -Rпереключателя lsattr). К сожалению, ни один из ответов до сих пор не работал правильно для меня.

Я придумал следующее:

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

Это защищает от перевода строки, используемого для отображения файла как неизменяемого, если это не так. Он не защищает от файлов, которые установлены как неизменяемые и имеют новые строки в именах файлов. Но так как такой файл должен быть создан таким образом root, я могу быть уверен, что таких файлов не существует в моей файловой системе для моего варианта использования. (Этот метод не подходит для обнаружения вторжений в случаях, когда пользователь root может быть скомпрометирован, но при этом ни тот, ни другой не использует ту же системную lsattrутилиту, которая также принадлежит тому же пользователю root).

depquid
источник
Только 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, чтобы соответствовать только «i» в первом поле вывода?

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
Rootdev
источник
Ваш первый фрагмент кода содержит опечатку, а второй - не находит неизменяемые файлы в моей системе.
depquid
Исправлена ​​опечатка в первой команде. Возможно, второй не находит неизменных файлов, потому что их нет?
Рутдев
Я не заметил, что вторая команда только смотрела /etc. Но обе команды неправильно находят неизменный файл, созданный с помощьюtouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid
В моем оригинальном сообщении говорится, что я запускаю его через cron для сканирования каталога / etc. Я ничего не могу поделать, если вы не читали ни пост, ни команду перед запуском. И да, вы, вероятно, можете построить крайний случай, чтобы обмануть практически любой поиск, если вам так хочется, но, поскольку вы так быстро указали на опечатку в моей первоначальной команде (пропуская последнюю '), я укажу, что ваша команда не работает так, как написано, поэтому ничего не создаст! :-)
Рутдев
Виноват. Попробуйте это:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid