Wie suche ich nach Dateien mit unveränderlichen Attributen?

17

Aus Gründen der Konfigurationsüberwachung möchte ich in der Lage sein, mein ext3-Dateisystem nach Dateien zu durchsuchen, für die das unveränderliche Attribut (über chattr +i) festgelegt wurde. Ich kann keine Optionen für findoder ähnliches finden, die dies tun. Zu diesem Zeitpunkt muss ich leider mein eigenes Skript schreiben, um die lsattrAusgabe für jedes Verzeichnis zu analysieren . Gibt es ein Standarddienstprogramm, das einen besseren Weg bietet?

depquid
quelle
Ich hätte klarstellen müssen, dass ich in meinem Fall nur die Konfigurationsverwaltung und nicht die Erkennung von Eindringlingen überprüfe, damit ich mich nicht zu sehr um Zeilenumbrüche kümmern muss, da ich weiß, dass die Dateinamen, mit denen ich arbeite, keine haben werden Sie. Trotzdem sollte man die Newline-Ausgabe im Auge behalten, also lasse ich meine Frage so, wie sie ist.
Depquid

Antworten:

9

Dies kann teilweise durch Weiterleiten des grepBefehls an den lsattrBefehl erreicht werden.

lsattr -R | grep +i

Aber ich glaube , wenn Sie die gesamte erwähnen ext3Dateisystem die Suche einbeziehen könnte /proc, /devund einige andere Verzeichnisse , die , wenn die Berichte einige Fehler einfach ignorieren wollen. Sie können den Befehl wahrscheinlich wie folgt ausführen:

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

Vielleicht möchten Sie das grepein bisschen strenger machen, indem Sie die grepPCRE-Funktion von verwenden , um das "-i-" expliziter abzugleichen.

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

Dies funktioniert dann für Situationen wie diese:

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

Ist aber unvollkommen. Wenn um das unveränderliche Flag herum zusätzliche Attribute aktiviert sind, werden diese nicht abgeglichen, und dies wird durch Dateien verfälscht, deren Namen ebenfalls mit dem obigen Muster übereinstimmen, z.

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

Wir können das Muster etwas enger fassen:

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

Aber es ist immer noch ein bisschen zu zerbrechlich und würde zusätzliche Anpassungen abhängig von den Dateien in Ihrem Dateisystem erfordern. Ganz zu schweigen von @StephaneChazeles , der in Kommentaren erwähnt hat, dass dies ziemlich einfach durch das Einfügen von Zeilenumbrüchen mit einem Dateinamen gespielt werden kann, um das obige Muster zu umgehen grep.

Verweise

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

Ramesh
quelle
1
Ha ich habe den gleichen Thread gelesen und wollte gleich posten. Ich werde stattdessen meine Extras zu Ihren hinzufügen.
slm
@ slm, Sie sind herzlich eingeladen, Änderungen vorzunehmen :)
Ramesh
2
Wahrscheinlich nicht gut für Audits geeignet, da eine unveränderliche Datei gefälscht oder ausgeblendet werden kann, indem bei diesem Ansatz Zeilenumbrüche im Dateinamen verwendet werden. Außerdem ist es nicht ungewöhnlich, dass Dateinamen -i-im Namen enthalten sind (auf dem System, auf dem ich gerade angemeldet bin, befinden sich 34). Sie werden wahrscheinlich auch die -aOption wollen
Stéphane Chazelas
1
Wofür soll das +iim ersten Beispiel stehen, nur aus Neugier ? Das funktioniert bei mir nicht. Das Greifen nach -i-setzt außerdem voraus, dass die Attribute, die neben i(z. B. a) angezeigt werden, nicht festgelegt sind.
Depquid
1
Warum nicht einfach danach greifen ^....i? Oder zumindest so etwas, ^[^ ]*iwenn sich die iin einer anderen Position als der fünften befinden kann.
Ruslan
6

Da der Zweck des Skripts die Prüfung ist, ist es besonders wichtig, mit beliebigen Dateinamen richtig umzugehen, z. B. mit Namen, die Zeilenumbrüche enthalten. Dies macht es unmöglich, lsattrmehrere Dateien gleichzeitig zu verwenden, da die Ausgabe von lsattrin diesem Fall mehrdeutig sein kann.

Sie können jeweils eine Datei findabrufen lsattr. Es wird allerdings ziemlich langsam.

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

Ich empfehle, eine weniger launische Sprache wie Perl, Python oder Ruby zu verwenden und die Arbeit lsattrselbst zu erledigen. lsattrführt einen FS_IOC_GETFLAGSioctl-Syscall aus und ruft die Inode-Flags der Datei ab . Hier ist ein Python Proof-of-Concept.

#!/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)
Gilles 'SO - hör auf böse zu sein'
quelle
1
FYI auf meinem System FS_IOC_GETFLAGSist 0x80046601.
Antonone
1
Der Wert von ist FS_IOC_GETFLAGSabhängig von sizeof(long). Siehe zB den folgenden Befehl bash , um herauszufinden , was das Makro erweitert , um in C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Ich habe daraus folgenden Ausdruck bekommen:, (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8)))der sich vereinfacht (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan
3

Um mit beliebigen Dateinamen (einschließlich solcher mit Zeilenvorschubzeichen) umzugehen, besteht der übliche Trick darin, Dateien darin zu finden, .//.anstatt .. Da //dies normalerweise beim Durchlaufen des Verzeichnisbaums nicht der Fall sein kann, sind Sie sicher, dass a //den Beginn eines neuen Dateinamens in der find(oder hier lsattr -R) Ausgabe signalisiert .

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

Beachten Sie, dass die Ausgabe weiterhin durch Zeilenumbrüche getrennt ist. Wenn Sie es nachbearbeiten müssen, müssen Sie es anpassen. Zum Beispiel könnten Sie ein hinzufügen -v ORS='\0', um es GNUs zuzuführen xargs -r0.

Beachten Sie auch, dass lsattr -R(mindestens 1.42.13) die Flags von Dateien, deren Pfad größer als PATH_MAX ist (normalerweise 4096), nicht gemeldet werden können. Daher kann jemand eine solche unveränderliche Datei ausblenden, indem er das übergeordnete Verzeichnis (oder eine der Pfadkomponenten, zu denen sie führen) verschiebt es, außer sich selbst als unveränderlich) in ein sehr tiefes Verzeichnis.

Ein Workaround wäre zu verwenden findmit -execdir:

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

Nun, mit -print0, das ist nachbearbeitbar, aber wenn Sie irgendetwas mit diesen Pfaden machen wollen, beachten Sie, dass jeder Systemaufruf auf Dateipfaden, die größer als PATH_MAX sind , immer noch fehlschlagen würde und Verzeichniskomponenten in dem Intervall umbenannt werden könnten.

Wenn wir einen zuverlässigen Bericht über einen Verzeichnisbaum erhalten möchten, der möglicherweise von anderen beschrieben werden lsattrkann, müssen wir noch einige weitere Probleme erwähnen, die mit dem Befehl selbst verbunden sind:

  • Die Art und Weise, wie lsattr -R .der Verzeichnisbaum durchquert wird, unterliegt den Wettkampfbedingungen. Man kann es zu Verzeichnissen außerhalb des Verzeichnisbaums bringen, an den weitergeleitet wird, .indem man im richtigen Moment einige Verzeichnisse durch Symlinks ersetzt.
  • hat sogar lsattr -d fileeine Rennbedingung. Diese Attribute gelten nur für reguläre Dateien oder Verzeichnisse. So lsattrfunktioniert ein lstat()erstes zu prüfen, ob die Datei der richtigen Arten ist und dann nicht open()gefolgt , ioctl()die Attribute abzurufen. Aber es ruft open()ohne O_NOFOLLOW(noch O_NOCTTY) auf. Jemand könnte durch fileeinen Symlink zum /dev/watchdogBeispiel zwischen lstat()und ersetzen open()und einen Neustart des Systems veranlassen. Es sollte also open(O_PATH|O_NOFOLLOW)gefolgt werden fstat(), openat()und ioctl()hier um die Rennbedingungen zu umgehen.
Stéphane Chazelas
quelle
2

Vielen Dank an Ramesh, slm und Stéphane, die mich in die richtige Richtung gelenkt haben (mir hat der -RSchalter gefehlt lsattr). Leider hat bisher keine der Antworten für mich richtig funktioniert.

Ich habe mir folgendes ausgedacht:

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

Dies schützt vor Zeilenumbrüchen, die verwendet werden, um eine Datei unveränderlich erscheinen zu lassen, wenn dies nicht der Fall ist. Es schützt nicht vor Dateien, die unveränderlich sind und deren Dateinamen Zeilenumbrüche enthalten. Da eine solche Datei jedoch von root auf diese Weise erstellt werden müsste, kann ich mir sicher sein, dass solche Dateien in meinem Dateisystem für meinen Anwendungsfall nicht vorhanden sind. (Diese Methode ist nicht für die Erkennung von Eindringlingen geeignet, wenn der Root-Benutzer möglicherweise kompromittiert wird. In diesem Fall wird jedoch nicht das lsattrDienstprogramm des gleichen Systems verwendet, das auch demselben Root-Benutzer gehört.)

depquid
quelle
Nur root kann das unveränderliche Bit zu einer Datei hinzufügen, aber möglicherweise können andere Benutzer die Pfadkomponenten, die zu diesen Dateien führen, später umbenennen, sodass der Dateipfad möglicherweise Newline enthält. Ein Benutzer kann auch einen Dateipfad (nicht unveränderlich) erstellen, der Ihr Skript täuschen würde, wenn er glaubt, dass eine andere Datei unveränderlich ist.
Stéphane Chazelas
2

Die Verwendung von find -execist zu langsam, die Analyse der Ausgabe von lsattrist unzuverlässig, ähnlich wie bei derls Verwendung von Python. In der Antwort von Gilles muss die Konstante ausgewählt werden, ioctlje nachdem, ob der Python-Interpreter 32- oder 64-Bit ist.

Das Problem ist mehr oder weniger auf niedriger Ebene, also lasst uns auf niedriger Ebene gehen: C ++ ist nicht so schlecht als Skriptsprache :) Als Bonus hat es Zugriff auf System-C-Header mit der vollen Leistung des C-Präprozessors.

Das folgende Programm sucht nach unveränderlichen Dateien, die sich innerhalb eines Dateisystems befinden, dh niemals Mount-Punkte überschreiten. Entfernen Sie die FTW_MOUNTMarkierung im nftwAufruf , um den sichtbaren Baum zu durchsuchen und die Einhängepunkte nach Bedarf zu kreuzen . Es folgen auch keine Symlinks. Entfernen Sie die Markierung, um ihnen zu folgen 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;
    }
}
Ruslan
quelle
-1

Warum nicht einfach awk verwenden, um nur das 'i' im ersten Feld der Ausgabe abzugleichen, anstatt die Ausgabe an grep weiterzuleiten?

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

Tatsächlich führe ich dies täglich über cron aus, um das Verzeichnis / etc auf Hunderten von Servern zu scannen und die Ausgabe an syslog zu senden. Ich kann dann einen täglichen Bericht über Splunk erstellen:

lsattr -Ra 2>/dev/null /etc|awk '$1 ~ /i/ && $1 !~ /^\// {print "Immutable_file="$2}'|logger -p local0.notice -t find_immutable
Rootdev
quelle
Dein erstes Code-Snippet ist ein Tippfehler und dein zweites findet keine unveränderlichen Dateien auf meinem System.
Depquid
Tippfehler im ersten Befehl behoben. Vielleicht findet der zweite keine unveränderlichen Dateien, weil es keine gibt?
Rootdev
Ich bemerkte nicht, dass der zweite Befehl nur hineinsah /etc. Beide Befehle finden jedoch fälschlicherweise eine nicht unveränderliche Datei, die mittouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid
In meinem ursprünglichen Beitrag steht, dass ich das über cron ausführe, um das Verzeichnis / etc zu scannen. Ich kann nichts dafür, wenn Sie weder den Beitrag noch den Befehl gelesen haben, bevor Sie ihn ausgeführt haben. Und ja, Sie können wahrscheinlich einen Kantenfall konstruieren, um so gut wie jede Suche zu täuschen, wenn Sie Lust dazu haben, aber da Sie so schnell auf den Tippfehler in meinem ursprünglichen Befehl hingewiesen haben (ohne das letzte '), werde ich darauf hinweisen Ihr Befehl funktioniert nicht so, wie er geschrieben wurde, also werden Sie nichts erstellen! :-)
Rootdev
Mein Fehler. Versuchen Sie touch "`echo -e 'bogus\n---------i---e-- changeable'`"
Folgendes