Como procurar arquivos com conjunto de atributos imutáveis?

17

Por motivos de auditoria de configuração, desejo poder pesquisar no meu sistema de arquivos ext3 arquivos que tenham o atributo imutável definido (via chattr +i). Não consigo encontrar nenhuma opção findou similar que faça isso. Neste ponto, receio que tenha que escrever meu próprio script para analisar a lsattrsaída de cada diretório. Existe um utilitário padrão que fornece uma maneira melhor?

depquid
fonte
Eu deveria ter esclarecido que, no meu caso, estou auditando apenas para gerenciamento de configurações, não para detecção de intrusões, por isso não preciso me preocupar muito com novas linhas, pois sei que os nomes de arquivos com os quais estou trabalhando não terão eles. No entanto, vale a pena ter em mente a questão da nova linha, então deixarei minha pergunta como está.
depquid

Respostas:

9

Isso pode ser parcialmente realizado canalizando o grepcomando para lsattrcomando.

lsattr -R | grep +i

No entanto, acredito que quando você menciona todo ext3o sistema de arquivos que a pesquisa pode envolver /proc, /deve alguns outros diretórios que, se informam algum erro, você apenas deseja ignorar. Você provavelmente pode executar o comando como,

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

Você pode querer tornar grepum pouco mais rigoroso usando grepo recurso PCRE para corresponder mais explicitamente ao "-i-".

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

Isso funcionará para situações como esta:

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

Mas é imperfeito. Se houver atributos adicionais ativados em torno do sinalizador imutável, não os corresponderemos, e isso será enganado pelos arquivos cujos nomes coincidem também com o padrão acima, como este:

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

Podemos reforçar o padrão um pouco mais como este:

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

Mas ainda é um pouco frágil e exigiria ajustes adicionais, dependendo dos arquivos do seu sistema de arquivos. Sem mencionar que @StephaneChazeles mencionou nos comentários que isso pode ser jogado com bastante facilidade pela inclusão de novas linhas com um nome de arquivo para ignorar o padrão acima grep.

Referências

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

Ramesh
fonte
1
Ha eu li o mesmo tópico e estava prestes a postar. Vou adicionar meus extras aos seus.
Slm
@slm, você é muito bem-vinda para fazer qualquer alteração :)
Ramesh
2
Provavelmente não é bom para auditoria, pois é possível falsificar ou ocultar um arquivo imutável por ter caracteres de nova linha no nome do arquivo com essa abordagem. Além disso, não é incomum que os nomes de arquivos tenham -i-seu nome (há 34 no sistema em que estou conectado no momento). Você provavelmente vai querer a -aopção também
Stéphane Chazelas
1
Apenas por curiosidade, o que é +isuposto ser no primeiro exemplo? Isso não funciona para mim. Além disso, grepping for -i-supõe que os atributos que aparecem adjacentes a i(como a) estejam desmarcados.
depquid
1
Por que não simplesmente grep for ^....i? Ou pelo menos algo como ^[^ ]*ise o ipode estar em outra posição que não a quinta.
Ruslan
6

Dado que o objetivo do script é a auditoria, é especialmente importante lidar corretamente com nomes de arquivos arbitrários, por exemplo, com nomes contendo novas linhas. Isso torna impossível o uso lsattrem vários arquivos simultaneamente, pois a saída de lsattrpode ser ambígua nesse caso.

Você pode recursar finde chamar lsattrum arquivo de cada vez. Vai ser bem 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 {} +

Eu recomendo usar uma linguagem menos irritadiça como Perl, Python ou Ruby e fazer o trabalho lsattrsozinho. lsattropera emitindo um FS_IOC_GETFLAGSsyscall ioctl e recuperando os sinalizadores de inode do arquivo . Aqui está uma prova de conceito do 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- parar de ser mau'
fonte
1
FYI no meu sistema FS_IOC_GETFLAGSé 0x80046601.
antonone
1
O valor de FS_IOC_GETFLAGSdepende sizeof(long). Veja por exemplo o seguinte comando bash para descobrir o que o expande macro no C: gcc -E - <<< $'#include <linux/fs.h>\nFS_IOC_GETFLAGS' | tail -n1. Obtive dela a seguinte expressão (((2U) << (((0 +8)+8)+14)) | ((('f')) << (0 +8)) | (((1)) << 0) | ((((sizeof(long)))) << ((0 +8)+8))):, que simplifica para (2U << 30) | ('f' << 8) | 1 | (sizeof(long) << 16).
Ruslan
3

Para lidar com nomes de arquivos arbitrários (incluindo aqueles que contêm caracteres de nova linha), o truque usual é encontrar arquivos dentro deles, em .//.vez de .. Como //normalmente não pode ocorrer ao percorrer a árvore de diretórios, você tem certeza de que um //sinaliza o início de um novo nome de arquivo na saída find(ou aqui 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()}'

Observe que a saída ainda será separada por nova linha. Se você precisar pós-processá-lo, precisará adaptá-lo. Por exemplo, você pode adicionar um -v ORS='\0'para poder alimentá-lo nos GNUs xargs -r0.

Observe também que lsattr -R(pelo menos 1.42.13) não pode relatar os sinalizadores de arquivos cujo caminho é maior que PATH_MAX (geralmente 4096), para que alguém possa ocultar um arquivo imutável movendo seu diretório pai (ou qualquer um dos componentes do caminho que levam a , exceto a si mesmo, pois é imutável) em um diretório muito profundo.

Uma solução alternativa seria usar findcom -execdir:

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

Agora, com -print0, isso é pós-processável, mas se você pretende fazer algo com esses caminhos, observe que qualquer chamada do sistema em caminhos de arquivo maiores que PATH_MAX ainda falhará e os componentes do diretório poderão ter sido renomeados no intervalo.

Se quisermos obter um relatório confiável em uma árvore de diretórios potencialmente gravável por outras pessoas, existem mais alguns problemas inerentes ao lsattrpróprio comando que precisaríamos mencionar:

  • a maneira como lsattr -R .percorre a árvore de diretórios, está sujeita às condições de corrida. Pode-se descer para diretórios fora da árvore de diretórios roteados .substituindo alguns diretórios por links simbólicos no momento certo.
  • ainda lsattr -d filetem uma condição de corrida. Esses atributos são aplicáveis ​​apenas a arquivos ou diretórios regulares. O mesmo lsattracontece lstat()primeiro para verificar se o arquivo é do tipo certo e, em seguida, é open()seguido por ioctl()para recuperar os atributos. Mas chama open()sem O_NOFOLLOW(nem O_NOCTTY). Alguém poderia substituir filecom um link simbólico para /dev/watchdog, por exemplo, entre o lstat()e open()e causa a reinicialização do sistema. Deve fazer open(O_PATH|O_NOFOLLOW)seguido por fstat(), openat()e ioctl()aqui para evitar as condições da corrida.
Stéphane Chazelas
fonte
2

Agradeço a Ramesh, slm e Stéphane por me apontarem na direção certa (estava faltando a -Ropção lsattr). Infelizmente, nenhuma das respostas até agora funcionou corretamente para mim.

Eu vim com o seguinte:

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

Isso protege contra a utilização de novas linhas para fazer com que um arquivo pareça imutável quando não estiver. Ele não protege contra arquivos definidos como imutáveis ​​e com novas linhas em seus nomes de arquivos. Mas como esse arquivo teria que ser criado dessa maneira pela raiz, posso ter certeza de que esses arquivos não existem no meu sistema de arquivos para o meu caso de uso. (Esse método não é adequado para a detecção de intrusões nos casos em que o usuário root pode estar comprometido, mas nenhum dos dois usa o mesmo lsattrutilitário do sistema, que também pertence ao mesmo usuário root.)

depquid
fonte
Somente o root pode adicionar o bit imutável a um arquivo, mas, potencialmente, outros usuários podem renomear componentes de caminho que levam a esses arquivos, portanto, o caminho do arquivo pode conter nova linha. Além disso, um usuário pode criar um caminho de arquivo (não imutável) que enganaria seu script ao pensar que outro arquivo é imutável.
Stéphane Chazelas
2

O uso find -execé muito lento, a análise de saída de nãolsattr é confiável, da mesma forma que als utilização de Python, como na resposta de Gilles, requer a escolha da constante para ioctlse o interpretador de Python é de 32 ou 64 bits ...

O problema em questão é de nível mais ou menos baixo, então vamos para um nível mais baixo: C ++ não é tão ruim quanto uma linguagem de script :) Como um bônus, ele tem acesso aos cabeçalhos C do sistema com toda a potência do pré-processador C.

O programa a seguir procura arquivos imutáveis, permanecendo dentro de um sistema de arquivos, ou seja, nunca cruza os pontos de montagem. Para pesquisar na árvore aparente, cruzando os pontos de montagem conforme necessário, remova o FTW_MOUNTsinalizador na nftwchamada. Também não segue links simbólicos. Para segui-los, remova o FTW_PHYSsinalizador.

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

Em vez de canalizar a saída para grep, por que não usar o awk para corresponder apenas ao 'i' no primeiro campo da saída?

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

Na verdade, eu executo isso diariamente via cron para varrer o diretório / etc em centenas de servidores e enviar a saída para o syslog. Posso gerar um relatório diário via Splunk:

lsattr -Ra 2>/dev/null /etc|awk '$1 ~ /i/ && $1 !~ /^\// {print "Immutable_file="$2}'|logger -p local0.notice -t find_immutable
Rootdev
fonte
Seu primeiro trecho de código possui um erro de digitação e o segundo não encontra arquivos imutáveis ​​no meu sistema.
depquid
Corrigido erro de digitação no primeiro comando. Talvez o segundo não esteja encontrando arquivos imutáveis ​​porque não há nenhum?
Rootdev
Não notei que o segundo comando estava apenas olhando /etc. Mas ambos os comandos incorretamente encontrar um arquivo não-imutável criado comtouch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid
Diz no meu post original que estou executando isso via cron para verificar o diretório / etc. Não posso evitar se você não leu a postagem ou o comando antes de executá-lo. E sim, você provavelmente pode criar um caso de ponta para enganar praticamente qualquer pesquisa, se quiser, mas como você foi tão rápido em apontar o erro de digitação no meu comando original (faltando o último '), vou apontar que seu comando não funciona como está escrito, portanto não criará nada! :-)
Rootdev
Foi mal. Tente isto:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
depquid