如何搜索具有不可变属性集的文件?


17

出于配置审核的原因,我希望能够在ext3文件系统中搜索具有不可变属性集的文件(通过 chattr +i)的文件。我找不到find执行此操作的任何选项或类似选项。此时,恐怕我必须编写自己的脚本来解析lsattr每个目录的输出。是否有提供更好方法的标准实用程序?


我应该澄清一下,在我的情况下,我仅在审核配置管理而不是入侵检测,所以我不必太担心换行符,因为我知道我正在使用的文件名不会他们。尽管如此,换行问题还是值得牢记的,所以我将保持原样。
2014年

Answers:


9

可以通过将grep命令传递给lsattr命令来部分实现。

lsattr -R | grep +i

但是,我相信当您提到整个ext3文件系统时,搜索可能涉及到/proc/dev还有一些其他目录,如果报告某些错误,则只想忽略它们。您可能可以将命令运行为

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

您可能希望grep通过使用grepPCRE工具来更严格地匹配“ -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

@slm,非常欢迎您进行任何更改:)
Ramesh

2
可能不利于审计,因为通过这种方法可以在文件名中使用换行符来伪造或隐藏不可变的文件。同样,文件名-i-中的名字也很常见(我当前登录的系统上有34个文件名)。你可能想的-a选项,以及
斯特凡Chazelas

1
出于好奇,+i在第一个示例中应该做什么?它对我不起作用。另外,grepping for -i-假设未设置与相邻的属性i(例如a)。
2014年

1
为什么不简单地使用grep ^....i?或至少类似的东西,^[^ ]*i如果i可以位于第五位以外的其他位置。
Ruslan

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之类的不那么胡扯的语言,并自己完成工作lsattrlsattr通过发出FS_IOC_GETFLAGSioctl syscall并检索文件的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
FYI我的系统FS_IOC_GETFLAGS0x80046601
安东尼

1
的值FS_IOC_GETFLAGS取决于sizeof(long)。请参见下面的bash命令,以了解宏在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)
Ruslan

这使fifo感到窒息。
Roadowl

3

要处理任意文件名(包括包含换行符的文件名),通常的技巧是在.//.而不是中查找文件.。由于//遍历目录树通常不会发生,因此请确保//信号在find(或here 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()}'

请注意,输出仍将以换行符分隔。如果您需要对其进行后期处理,则必须对其进行调整。例如,您可以添加-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

感谢Ramesh,slm和Stéphane向我指出了正确的方向(我错过了的-R开关lsattr)。不幸的是,到目前为止,没有一个答案对我有用。

我想出了以下几点:

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

这样可以防止换行符使文件在不换行时显得不可变。它不能防止设置为不可变且文件名中包含换行符的文件。但是由于必须通过root用户以这种方式创建这样的文件,因此我可以确信在我的用例中此类文件不存在于我的文件系统中。(此方法不适用于可能损害root用户但又没有使用同样lsattr由root用户拥有的相同系统实用程序的入侵检测的情况。)


只有root可以将不可变位添加到文件中,但是可能其他用户以后可以重命名导致这些文件的路径组件,因此文件路径可能包含换行符。用户还可以创建一个文件路径(不是不可变的),该路径会使您的脚本误以为另一个文件是不可变的。
斯特凡Chazelas

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

为什么不使用awk只匹配输出的第一个字段中的“ i”,而不是将输出通过管道传递到grep?

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

您的第一个代码段有错字,而第二个代码段在我的系统上找不到不可变的文件。
2014年

修复了第一个命令中的错字。也许第二个没有找到任何不可变的文件,因为没有文件?
Rootdev 2014年

我没注意到第二个命令只是在查找/etc。但是两个命令都错误地找到了使用touch `"echo -e "bogus\n---------i---e-- changeable"`"
depquid

它在我的原始帖子中说我正在通过cron运行它以扫描/ etc目录。如果您在运行它之前没有阅读帖子或命令,那么我无济于事。是的,如果您愿意,您可以构造一个边缘案例来欺骗几乎所有搜索,但是由于您如此迅速地指出了原始命令中的错字(缺少最后一个'),因此我要指出您的命令无法正常运行,因此不会创建任何内容!:-)
Rootdev 2014年

我的错。试试这个:touch "`echo -e 'bogus\n---------i---e-- changeable'`"
2014年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.