¿Cómo buscar archivos con un conjunto de atributos inmutable?

17

Por razones de auditoría de configuración, quiero poder buscar en mi sistema de archivos ext3 archivos que tengan el conjunto de atributos inmutables (via chattr +i). No puedo encontrar ninguna opción findo similar que haga esto. En este punto, me temo que tendré que escribir mi propio script para analizar la lsattrsalida de cada directorio. ¿Existe una utilidad estándar que ofrezca una mejor manera?

depquid
fuente
Debería haber aclarado que en mi caso, estoy auditando solo para la administración de la configuración, no para la detección de intrusos, por lo que no tengo que preocuparme demasiado por las nuevas líneas, ya que sé que los nombres de archivo con los que estoy trabajando no tendrán ellos. No obstante, vale la pena tener en cuenta el problema de la nueva línea, por lo que dejaré mi pregunta tal como está.
depquid

Respuestas:

9

Se puede lograr parcialmente canalizando el grepcomando al lsattrcomando.

lsattr -R | grep +i

Sin embargo, creo que cuando mencionas todo ext3el sistema de archivos, la búsqueda puede involucrar /proc, /devy algunos otros directorios que si informan algún error solo quieres ignorar. Probablemente pueda ejecutar el comando como,

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

Es posible que desee hacer grepun poco más estricto mediante el uso grepde la función PCRE para que coincida más explícitamente con el "-i-".

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

Esto funcionará para situaciones como esta:

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

Pero es imperfecto. Si hay atributos adicionales habilitados alrededor del indicador inmutable, entonces no los haremos coincidir, y esto se verá engañado por los archivos cuyos nombres coincidan también con el patrón anterior, como este:

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

Podemos ajustar el patrón un poco más de esta manera:

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

Pero todavía es un poco demasiado frágil y requeriría ajustes adicionales dependiendo de los archivos dentro de su sistema de archivos. Sin mencionar que @StephaneChazeles ha mencionado en los comentarios que esto se puede jugar con bastante facilidad mediante la inclusión de nuevas líneas con un nombre de archivo para omitir el patrón anterior grep.

Referencias

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

Ramesh
fuente
1
Ja, leí el mismo hilo y estaba a punto de publicar. Agregaré mis extras a los tuyos.
slm
@slm, eres bienvenido a hacer cualquier cambio :)
Ramesh
2
Probablemente no sea bueno para la auditoría, ya que uno puede falsificar u ocultar un archivo inmutable al tener caracteres de nueva línea en el nombre del archivo con ese enfoque. Además, no es raro que los nombres de archivo tengan -i-su nombre (hay 34 en el sistema en el que estoy conectado actualmente). Probablemente también querrás la -aopción
Stéphane Chazelas,
1
Solo por curiosidad, ¿para qué se +isupone que es en el primer ejemplo? A mi no me funciona. Además, grepping for -i-supone que los atributos que aparecen adyacentes a i(como a) no están configurados.
depquid
1
¿Por qué no simplemente grep ^....i? O al menos algo así como ^[^ ]*isi ipuede estar en otra posición que no sea la quinta.
Ruslan
6

Dado que el propósito del script es la auditoría, es especialmente importante tratar correctamente con nombres de archivos arbitrarios, por ejemplo, con nombres que contienen líneas nuevas. Esto hace que sea imposible de usar lsattren varios archivos simultáneamente, ya que la salida de lsattrpuede ser ambigua en ese caso.

Puede findrecurrir y llamar lsattra un archivo a la vez. Sin embargo, será bastante lento.

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

Recomiendo usar un lenguaje menos irritable como Perl, Python o Ruby y hacer el trabajo lsattrpor ti mismo. lsattropera emitiendo un FS_IOC_GETFLAGSsyscall ioctl y recuperando los indicadores de inodo del archivo . Aquí hay una prueba de concepto de 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- deja de ser malvado'
fuente
1
FYI en mi sistema FS_IOC_GETFLAGSes 0x80046601.
antonone
1
El valor de FS_IOC_GETFLAGSdepende de sizeof(long). Véase por ejemplo el siguiente comando bash para averiguar cuál es el macro se expande en C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Obtuve la siguiente expresión:, (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8)))que se simplifica a (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan
3

Para tratar con nombres de archivos arbitrarios (incluidos los que contienen caracteres de nueva línea), el truco habitual es encontrar archivos dentro en .//.lugar de .. Como //normalmente no puede ocurrir al atravesar el árbol de directorios, está seguro de que //indica el inicio de un nuevo nombre de archivo en la salida find(o aquí 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()}'

Tenga en cuenta que la salida seguirá estando separada por una nueva línea. Si necesita procesarlo posteriormente, deberá adaptarlo. Por ejemplo, podría agregar un -v ORS='\0'para poder alimentarlo a GNU xargs -r0.

También tenga en cuenta que lsattr -R(al menos 1.42.13) no puede informar los indicadores de archivos cuya ruta es más grande que PATH_MAX (generalmente 4096), por lo que alguien podría ocultar un archivo tan inmutable moviendo su directorio principal (o cualquiera de los componentes de ruta que conducen a , excepto en sí mismo, ya que es inmutable) en un directorio muy profundo.

Una solución alternativa sería usar findcon -execdir:

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

Ahora, con -print0, eso es postprocesable, pero si tiene la intención de hacer algo con esas rutas, tenga en cuenta que cualquier llamada al sistema en rutas de archivos mayores que PATH_MAX todavía fallaría y los componentes del directorio podrían haber cambiado de nombre en el intervalo.

Si queremos obtener un informe confiable sobre un árbol de directorios que otros usuarios puedan escribir, hay algunos problemas más inherentes al lsattrcomando en sí que deberíamos mencionar:

  • la forma en que lsattr -R .atraviesa el árbol de directorios está sujeto a condiciones de carrera. Uno puede hacer que descienda a directorios fuera del árbol de directorios enrutado .reemplazando algunos directorios con enlaces simbólicos en el momento correcto.
  • incluso lsattr -d filetiene una condición de carrera. Esos atributos solo son aplicables a archivos o directorios normales. Así que lsattrhace un lstat()primero para comprobar que el archivo es de los tipos correctos y luego no open()siguió ioctl()para recuperar los atributos. Pero llama open()sin O_NOFOLLOW(ni O_NOCTTY). Alguien podría reemplazar filecon un enlace simbólico, /dev/watchdogpor ejemplo, entre lstat()y open()y hacer que el sistema se reinicie. Debe hacerlo open(O_PATH|O_NOFOLLOW)seguido fstat(), openat()y ioctl()aquí para evitar las condiciones de carrera.
Stéphane Chazelas
fuente
2

Gracias a Ramesh, slm y Stéphane por señalarme en la dirección correcta (me faltaba el -Rinterruptor lsattr). Desafortunadamente, ninguna de las respuestas hasta ahora funcionó correctamente para mí.

Se me ocurrió lo siguiente:

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

Esto protege contra las nuevas líneas que se utilizan para hacer que un archivo parezca inmutable cuando no lo es. No , no protege contra los archivos que están establecidos como inmutables y tienen nuevas líneas en sus nombres de archivo. Pero dado que dicho archivo tendría que ser creado de esa manera por root, puedo estar seguro de que dichos archivos no existen en mi sistema de archivos para mi caso de uso. (Este método no es adecuado para la detección de intrusiones en los casos en que el usuario raíz puede verse comprometido, pero tampoco está utilizando la misma lsattrutilidad del sistema que también es propiedad del mismo usuario raíz).

depquid
fuente
Solo root puede agregar el bit inmutable a un archivo, pero potencialmente otros usuarios pueden cambiar el nombre de los componentes de ruta que conducen a esos archivos, por lo que la ruta del archivo puede contener nueva línea. Además, un usuario puede crear una ruta de archivo (no inmutable) que engañaría a su script al pensar que otro archivo es inmutable.
Stéphane Chazelas
2

El uso find -execes demasiado lento, la salida de análisis de nolsattr es confiable de manera similar a la dels , usar Python como en la respuesta de Gilles requiere elegir la constante ioctldependiendo de si el intérprete de Python es de 32 o 64 bits ...

El problema en cuestión es más o menos de bajo nivel, así que pasemos a un nivel inferior: C ++ no es tan malo como un lenguaje de script :) Como beneficio adicional, tiene acceso a los encabezados C del sistema con toda la potencia del preprocesador C.

El siguiente programa busca archivos inmutables, permaneciendo dentro de un sistema de archivos, es decir, nunca cruza los puntos de montaje. Para buscar el árbol aparente, cruzando puntos de montaje según sea necesario, elimine la FTW_MOUNTbandera en la nftwllamada. Además, no sigue enlaces simbólicos. Para seguirlos, elimine la FTW_PHYSbandera.

#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
fuente
-1

En lugar de canalizar la salida a grep, ¿por qué no usar awk para que solo coincida con la 'i' en el primer campo de la salida?

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

De hecho, ejecuto esto diariamente a través de cron para escanear el directorio / etc en cientos de servidores y enviar la salida a syslog. Entonces puedo generar un informe diario a través de Splunk:

lsattr -Ra 2>/dev/null /etc|awk '$1 ~ /i/ && $1 !~ /^\// {print "Immutable_file="$2}'|logger -p local0.notice -t find_immutable
Rootdev
fuente
Su primer fragmento de código tiene un error tipográfico y el segundo no encuentra archivos inmutables en mi sistema.
depquid
Se corrigió el error tipográfico en el primer comando. ¿Quizás el segundo no encuentra ningún archivo inmutable porque no hay ninguno?
Rootdev
No noté que el segundo comando solo estaba mirando /etc. Pero ambos comandos encuentran incorrectamente un archivo no inmutable creado contouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid el
Dice en mi publicación original que lo estoy ejecutando a través de cron para escanear el directorio / etc. No puedo evitarlo si no leíste la publicación o el comando antes de ejecutarlo. Y sí, probablemente pueda construir un caso límite para engañar a casi cualquier búsqueda si lo desea, pero dado que fue tan rápido en señalar el error tipográfico en mi comando original (falta el último '), señalaré que su comando no funciona como está escrito, ¡así que no creará nada! :-)
Rootdev
Culpa mía. Pruebe esto:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid