Comment rechercher des fichiers avec un ensemble d'attributs immuable?

17

Pour des raisons d'audit de configuration, je souhaite pouvoir rechercher dans mon système de fichiers ext3 des fichiers dont l'attribut immuable est défini (via chattr +i). Je ne trouve aucune option findou similaire pour cela. À ce stade, je crains de devoir écrire mon propre script pour analyser la lsattrsortie de chaque répertoire. Existe-t-il un utilitaire standard qui offre un meilleur moyen?

depquid
la source
J'aurais dû clarifier que dans mon cas, j'audite uniquement pour la gestion de la configuration, pas pour la détection d'intrusion, donc je n'ai pas à me soucier trop des nouvelles lignes, car je sais que les noms de fichiers avec lesquels je travaille n'auront pas leur. Néanmoins, le problème de la nouvelle ligne mérite d'être gardé à l'esprit, alors je vais laisser ma question telle quelle.
depquid

Réponses:

9

Cela peut être partiellement accompli en canalisant la grepcommande pour lsattrcommander.

lsattr -R | grep +i

Cependant, je pense que lorsque vous mentionnez l'ensemble ext3du système de fichiers que la recherche peut impliquer /proc, /devainsi que certains autres répertoires qui, s'ils signalent une erreur, vous souhaitez simplement ignorer. Vous pouvez probablement exécuter la commande en tant que,

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

Vous voudrez peut-être rendre le grepun peu plus strict en utilisant grepla fonction PCRE de pour faire correspondre plus explicitement le "-i-".

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

Cela fonctionnera alors pour des situations comme celle-ci:

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

Mais est imparfait. S'il y a des attributs supplémentaires activés autour de l'indicateur immuable, alors nous ne les ferons pas correspondre, et cela sera trompé par des fichiers dont les noms correspondent également au modèle ci-dessus, comme celui-ci:

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

Nous pouvons resserrer le modèle un peu plus comme ceci:

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

Mais il est toujours un peu trop fragile et nécessiterait des ajustements supplémentaires en fonction des fichiers de votre système de fichiers. Sans oublier que @StephaneChazeles a mentionné dans les commentaires que cela peut être joué assez facilement par l'inclusion de nouvelles lignes avec un nom de fichier pour contourner le modèle ci-dessus grep.

Les références

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

Ramesh
la source
1
Ha j'ai lu le même fil et j'allais poster. Je vais plutôt ajouter mes extras aux vôtres.
slm
@slm, vous êtes les bienvenus pour apporter des modifications :)
Ramesh
2
Probablement pas bon pour l'audit car on peut simuler ou masquer un fichier immuable en ayant des caractères de nouvelle ligne dans le nom du fichier avec cette approche. De plus, il n'est pas rare que les noms de fichiers aient -i-leur nom (il y en a 34 sur le système sur lequel je suis actuellement connecté). Vous voudrez probablement aussi l' -aoption
Stéphane Chazelas
1
Par simple curiosité, à quoi sert +ile premier exemple? Ça ne marche pas pour moi. De plus, la recherche de -i-suppose que les attributs qui apparaissent adjacents à i(tels que a) ne sont pas définis.
depquid
1
Pourquoi ne pas tout simplement grimper ^....i? Ou au moins quelque chose comme ^[^ ]*isi le ipeut être dans une autre position que la cinquième.
Ruslan
6

Étant donné que le but du script est l'audit, il est particulièrement important de traiter correctement les noms de fichiers arbitraires, par exemple les noms contenant des retours à la ligne. Cela rend impossible l'utilisation lsattrsimultanée sur plusieurs fichiers, car la sortie de lsattrpeut être ambiguë dans ce cas.

Vous pouvez recurse findet appeler lsattrsur un fichier à la fois. Ce sera assez lent cependant.

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

Je recommande d'utiliser un langage moins grincheux comme Perl, Python ou Ruby et de faire le travail lsattrpar vous-même. lsattrfonctionne en émettant un FS_IOC_GETFLAGSappel système ioctl et en récupérant les indicateurs d'inode du fichier . Voici une preuve de concept 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)
Gilles 'SO- arrête d'être méchant'
la source
1
FYI sur mon système FS_IOC_GETFLAGSest 0x80046601.
antonone
1
La valeur de FS_IOC_GETFLAGSdépend de sizeof(long). Voir par exemple la commande bash suivante pour savoir ce que les macro dans dilate C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. J'en ai tiré l'expression suivante:, (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8)))qui se simplifie en (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan
3

Pour gérer les noms de fichiers arbitraires (y compris ceux contenant des caractères de nouvelle ligne), l' astuce habituelle consiste à rechercher des fichiers à l'intérieur .//.au lieu de .. Étant donné que //cela ne peut normalement pas se produire lors de la traversée de l'arborescence des répertoires, vous êtes sûr qu'un //signale le début d'un nouveau nom de fichier dans la sortie find(ou ici 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()}'

Notez que la sortie sera toujours séparée par des sauts de ligne. Si vous devez le post-traiter, vous devrez l'adapter. Par exemple, vous pouvez ajouter un -v ORS='\0'pour pouvoir le transmettre aux GNU xargs -r0.

Notez également que lsattr -R(au moins 1.42.13) ne peut pas signaler les indicateurs de fichiers dont le chemin est plus grand que PATH_MAX (généralement 4096), donc quelqu'un pourrait masquer un tel fichier immuable en déplaçant son répertoire parent (ou l'un des composants de chemin qui mènent à , sauf lui-même car il est immuable) dans un répertoire très profond.

Une solution de contournement consisterait à utiliser findavec -execdir:

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

Maintenant, avec -print0, c'est post-processable, mais si vous avez l'intention de faire quoi que ce soit avec ces chemins, notez que tout appel système sur des chemins de fichier supérieurs à PATH_MAX échouera toujours et les composants de répertoire auraient pu être renommés dans l'intervalle.

Si nous voulons obtenir un rapport fiable sur une arborescence de répertoires potentiellement accessible en écriture par d'autres, il y a quelques autres problèmes inhérents à la lsattrcommande elle-même que nous devons mentionner:

  • le chemin lsattr -R .traverse l'arborescence des répertoires, il est soumis à des conditions de concurrence. On peut le faire descendre dans des répertoires en dehors de l'arborescence de répertoires routés .en remplaçant certains répertoires par des liens symboliques au bon moment.
  • a même lsattr -d fileune condition de course. Ces attributs ne s'appliquent qu'aux fichiers ou répertoires standard. Alors , lsattrfait un lstat()premier à vérifier que le fichier est des bons types et ne open()suivi ioctl()pour récupérer les attributs. Mais il appelle open()sans O_NOFOLLOW(ni O_NOCTTY). Quelqu'un pourrait remplacer filepar un lien symbolique /dev/watchdogpar exemple entre le lstat()et open()et provoquer le redémarrage du système. Il faut le faire open(O_PATH|O_NOFOLLOW)suivi fstat(), openat()et ioctl()ici pour éviter les conditions de course.
Stéphane Chazelas
la source
2

Merci à Ramesh, slm et Stéphane de m'avoir pointé dans la bonne direction (il me manquait le -Rswitch pour lsattr). Malheureusement, aucune des réponses n'a jusqu'à présent fonctionné correctement pour moi.

J'ai trouvé ce qui suit:

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

Cela protège contre les sauts de ligne utilisés pour faire apparaître un fichier comme immuable alors qu'il ne l'est pas. Il ne protège pas contre les fichiers qui sont définis comme immuables et qui ont des nouvelles lignes dans leurs noms de fichiers. Mais comme un tel fichier devrait être créé de cette façon par root, je peux être sûr que de tels fichiers n'existent pas sur mon système de fichiers pour mon cas d'utilisation. (Cette méthode ne convient pas à la détection d'intrusion dans les cas où l'utilisateur root peut être compromis, mais aucun n'utilise l' lsattrutilitaire du même système qui appartient également au même utilisateur root.)

depquid
la source
Seul root peut ajouter le bit immuable à un fichier, mais potentiellement d'autres utilisateurs peuvent renommer ultérieurement les composants de chemin d'accès qui mènent à ces fichiers, de sorte que le chemin d'accès au fichier peut contenir une nouvelle ligne. Un utilisateur peut également créer un chemin de fichier (non immuable) qui pourrait tromper votre script en pensant qu'un autre fichier est immuable.
Stéphane Chazelas
2

L'utilisation find -execest trop lente, l'analyse de la sortie de lsattrn'est pas fiable de la même manière que celle dels , l'utilisation de Python comme dans la réponse de Gilles nécessite de choisir la constante pour ioctlselon que l'interpréteur Python est 32 ou 64 bits ...

Le problème est plus ou moins bas, alors allons plus bas: C ++ n'est pas si mal comme langage de script :) En bonus, il a accès aux en-têtes C du système avec toute la puissance du préprocesseur C.

Le programme suivant recherche des fichiers immuables, en restant dans un système de fichiers, c'est-à-dire qu'il ne traverse jamais de points de montage. Pour rechercher l'arbre apparent, en traversant les points de montage si nécessaire, supprimez le FTW_MOUNTdrapeau dans l' nftwappel. De plus, il ne suit pas les liens symboliques. Pour les suivre, supprimez le FTW_PHYSdrapeau.

#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
la source
-1

Au lieu de diriger la sortie vers grep, pourquoi ne pas simplement utiliser awk pour ne faire correspondre que le «i» dans le premier champ de la sortie?

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

En fait, je l'exécute quotidiennement via cron pour analyser le répertoire / etc sur des centaines de serveurs et envoyer la sortie à syslog. Je peux ensuite générer un rapport quotidien via Splunk:

lsattr -Ra 2>/dev/null /etc|awk '$1 ~ /i/ && $1 !~ /^\// {print "Immutable_file="$2}'|logger -p local0.notice -t find_immutable
Rootdev
la source
Votre premier extrait de code a une faute de frappe et votre deuxième ne trouve pas de fichiers immuables sur mon système.
depquid
Correction d'une faute de frappe dans la première commande. Peut-être que le second ne trouve aucun fichier immuable car il n'y en a pas?
Rootdev
Je n'ai pas remarqué que la deuxième commande regardait seulement /etc. Mais les deux commandes trouvent incorrectement un fichier non immuable créé avectouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid
Il est dit dans mon article d'origine que je l'exécute via cron pour scanner le répertoire / etc. Je ne peux pas m'en empêcher si vous n'avez lu ni le message ni la commande avant de l'exécuter. Et oui, vous pouvez probablement construire un cas de bord pour tromper à peu près n'importe quelle recherche si vous en avez envie, mais puisque vous avez été si rapide à souligner la faute de frappe dans ma commande d'origine (manque la dernière '), je soulignerai que votre commande ne fonctionne pas comme écrit, donc ne créera rien! :-)
Rootdev
Ma faute. Essayez ceci:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid