查找用户可以写入的文件,而过程创建最少


20

我是根 我想知道一个非root用户是否具有对某些文件(其中数千个文件)的写权限。如何在避免流程创建的同时有效地做到这一点?


请告诉我们您的实际工作!
F. Hauri


假设您不在乎竞争条件,为什么不只access(2)使用适当设置的real-UID进行呼叫(例如,通过setresuid(2)或便携式等效设备)?我的意思是,我很难从bash中做到这一点,但是我确信Perl / Python可以处理它。
凯文

1
@ Kevin,shell [ -w通常确实使用access(2)或等效的方法。除uid外,还需要设置gid(如su或sudo一样)。bash没有对此的内置支持,但是zsh有。
斯特凡Chazelas

@StéphaneChazelas-您可以chgrp在任何shell中使用。
mikeserv

Answers:


2

也许像这样:

#! /bin/bash

writable()
{
    local uid="$1"
    local gids="$2"
    local ids
    local perms

    ids=($( stat -L -c '%u %g %a' -- "$3" ))
    perms="0${ids[2]}"

    if [[ ${ids[0]} -eq $uid ]]; then
        return $(( ( perms & 0200 ) == 0 ))
    elif [[ $gids =~ (^|[[:space:]])"${ids[1]}"($|[[:space:]]) ]]; then
        return $(( ( perms & 020 ) == 0 ))
    else
        return $(( ( perms & 2 ) == 0 ))
    fi
}

user=foo
uid="$( id -u "$user" )"
gids="$( id -G "$user" )"

while IFS= read -r f; do
    writable "$uid" "$gids" "$f" && printf '%s writable\n' "$f"
done

上面为每个文件运行一个外部程序,即stat(1)

注意:这是基于bash(1)和Linux的化身stat(1)

注意2:请阅读以下StéphaneChazelas的评论,以了解这种方法的过去,现在,未来以及潜在的危险和局限性。


这可能表示文件是可写的,即使用户无法访问该文件所在的目录也是如此。
斯特凡Chazelas

假设文件名不包含换行符,并且在stdin上传递的文件路径都不以开头-。你可以修改它接受一个NUL分隔的列表,而不是read -d ''
斯特凡Chazelas

请注意,没有Linux stat之类的东西。Linux是在某些GNU和非GNU系统中发现的内核。尽管有一些util-linux专门为Linux编写的命令(例如from中的命令),但stat您所指的不是,它是一个GNU命令,已移植到大多数系统中,不仅是Linux。还要注意,您早在编写statGNU statstat内置zsh)之前就在Linux上有了命令。
斯特凡Chazelas

2
@StéphaneChazelas:请注意,没有Linux统计信息。-我相信我写了“ Linux的化身stat(1)”。我指的是一种stat(1)接受-c <format>语法的语言,而不是BSD语法-f <format>。我也相信很明显我没在说stat(2)。我敢肯定,关于常见命令历史的Wiki页面将非常有趣。
lcd047

1
@StéphaneChazelas:可以说文件是可写的,即使用户无权访问该文件所在的目录。-正确,可能是合理的限制。假定文件名不包含换行符 -True,并且可能是合理的限制。和在stdin上传递的文件路径开头不是 --编辑过,谢谢。
lcd047

17

TL; DR

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

您需要询问系统用户是否具有写权限。唯一可靠的方法是将有效uid,有效gid和补充gid切换到用户的有效uid,有效gid和补充gid,并使用access(W_OK)系统调用(即使在某些系统/配置上有一些限制)。

请记住,没有文件的写权限并不一定保证您不能在该路径下修改文件的内容。

更长的故事

让我们考虑一下,例如,一个$ user拥有对它的写访问权/foo/file.txt(假设都不是,/foo并且/foo/file.txt是符号链接)?

他需要:

  1. 搜索访问/(无需read
  2. 搜索访问/foo(无需read
  3. 写入权限/foo/file.txt

您已经可以看到仅检查的许可的方法(例如@ lcd047@apaul的方法file.txt将不起作用,因为file.txt即使用户没有对/或的搜索许可,它们也可以说是可写的/foo

和类似的方法:

sudo -u "$user" find / -writeble

也不起作用,因为它不会报告用户没有读取权限的目录中的文件(因为find运行$user时无法列出其内容),即使他可以写这些文件。

如果我们忘记了ACL,只读文件系统,FS标志(如不可变的),其他安全措施(保护器,SELinux,甚至可以区分不同类型的书写),而只关注传统的权限和所有权属性,授予(搜索或写入)权限,这已经非常复杂并且很难用表达find

你需要:

  • 如果文件是您拥有的,则需要拥有者的权限(或具有uid 0)
  • 如果文件不属于您,但该组是您的文件之一,则您需要该组的许可(或具有uid 0)。
  • 如果它不属于您,也不属于任何组,则将应用其他权限(除非您的uid为0)。

find语法上,以用户为uid 1和gid 1和2的示例为例:

find / -type d \
  \( \
    -user 1 \( -perm -u=x -o -prune \) -o \
    \( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) -o -type l -o \
    -user 1 \( ! -perm -u=w -o -print \) -o \
    \( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

这一个李子的目录,用户没有正确的和其他类型的文件(排除,因为他们没有相关的符号链接),检查写访问搜索。

如果您还想考虑对目录的写访问权:

find / -type d \
  \( \
    -user 1 \( -perm -u=x -o -prune \) -o \
    \( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user 1 \( ! -perm -u=w -o -print \) -o \
    \( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

或者对于$user从用户数据库中检索到的任意对象及其组成员身份:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" \( -perm -u=x -o -prune \) -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" \( ! -perm -u=w -o -print \) -o \
    \( -group $groups \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

(这是在总流程3: idsedfind

最好的做法是将树作为根目录下移,并以用户身份检查每个文件的权限。

 find / ! -type l -exec sudo -u "$user" sh -c '
   for file do
     [ -w "$file" ] && printf "%s\n" "$file"
   done' sh {} +

(这是一个find过程加一sudosh处理每个几千文件,[并且printf通常建在外壳)。

或搭配perl

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

(3个工艺总数:findsudoperl)。

或搭配zsh

files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
  [ -w $f ] && print -r -- $f
}

(总共0个进程,但将整个文件列表存储在内存中)

这些解决方案依赖于access(2)系统调用。那不是重现系统用来检查访问权限的算法,而是让系统使用相同的算法(考虑到权限,ACL,不可变标志,只读文件系统... ),它将在您尝试打开文件进行写入时使用,因此您将最接近获得可靠的解决方案。

要测试此处给出的解决方案以及用户,组和权限的各种组合,可以执行以下操作:

perl -e '
  for $u (1,2) {
    for $g (1,2,3) {
      $d1="u${u}g$g"; mkdir$d1;
      for $m (0..511) {
        $d2=$d1.sprintf"/%03o",$m; mkdir $d2; chown $u, $g, $d2; chmod $m,$d2;
        for $uu (1,2) {
          for $gg (1,2,3) {
            $d3="$d2/u${uu}g$gg"; mkdir $d3;
            for $mm (0..511) {
              $f=$d3.sprintf"/%03o",$mm;
              open F, ">","$f"; close F;
              chown $uu, $gg, $f; chmod $mm, $f
            }
          }
        }
      }
    }
  }'

在1和2之间切换用户,然后在1、2和3之间分组,并将自己限制为权限的低9位,因为已经创建了9458694文件。对于目录,然后对于文件。

这将创建的所有可能组合u<x>g<y>/<mode1>/u<z>g<w>/<mode2>。uid为1且gid为1和2的用户将具有u2g1/010/u2g3/777但不具有写权限u1g2/677/u1g1/777

现在,所有这些解决方案都试图识别用户可以打开以进行写入的文件的路径,这与用户可以修改内容的路径不同。要回答这个更通用的问题,需要考虑以下几件事:

  1. $ user可能无权对其进行写访问,/a/b/file但如果他拥有file(并且具有对的搜索访问权/a/b,并且文件系统不是只读的,并且文件没有不可变标志,并且可以对系统进行shell访问),那么他就可以更改的权限file并授予自己访问权限。
  2. 如果他拥有/a/b但没有搜索权限,则为同一件事。
  3. $ user可能无权访问,/a/b/file因为他没有对/a或的搜索访问权/a/b,但是该文件可能在/b/c/file例如具有硬链接,在这种情况下,他可以/a/b/file通过通过其/b/c/file路径打开文件来修改其内容。
  4. bind-mounts相同。他可能没有搜索权限/a,但/a/b可能绑定安装在中/c,因此他可以file通过/c/file其他路径打开进行写操作。
  5. 他可能没有对的写入权限/a/b/file,但是如果他具有写入权限,则/a/b可以file在那里删除或重命名并将其替换为自己的版本。他将更改文件的内容,/a/b/file即使那是另一个文件也是如此。
  6. 如果他具有写入权限/a(他可以将其重命名/a/b/a/c,创建一个新/a/b目录并在其中新建一个目录),也是file一样。

查找$user将能够修改的路径。要寻址1或2,我们不能再依赖access(2)系统调用了。我们可以调整find -perm方法以假定对目录的搜索访问权,或者在您成为所有者后立即写对文件的访问权:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" -print -o \
    \( -group $groups \) \( ! -perm -g=w -o -print \) -o \
    ! -perm -o=w -o -print

我们可以通过记录设备和inode编号或$ user拥有写入权限的所有文件并报告具有这些dev + inode编号的所有文件路径来寻址3和4。这次,我们可以使用更可靠 access(2)的方法:

就像是:

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
  perl -l -0ne '
    ($w,$p) = /(.)(.*)/;
    ($dev,$ino) = stat$p or next;
    $writable{"$dev,$ino"} = 1 if $w;
    push @{$p{"$dev,$ino"}}, $p;
    END {
      for $i (keys %writable) {
        for $p (@{$p{$i}}) {
          print $p;
        }
      }
    }'

乍看之下,图5和图6 t有点复杂。当应用于目录时,这是受限制的删除位,可防止用户(目录所有者以外的用户)删除或重命名他们不拥有的文件(即使他们具有对该目录的写访问权限)。

例如,如果我们回到前面的示例,如果您具有对的写权限/a,那么您应该能够重命名/a/b/a/c,然后在其中重新创建/a/b目录和新目录file。但是如果t钻头被设置了/a而且您不拥有所有权/a,那么只有您拥有才能做到/a/b。这给出了:

  • 如果您拥有一个目录(按照1),则可以授予自己写访问权限,并且t位不适用(并且可以将其删除),因此可以在其中删除/重命名/重新创建任何文件或目录,因此下方的所有文件路径都可以用任何内容重写。
  • 如果您不拥有它但具有写访问权,则:
    • 要么t没有设置该位,并且您处于与上述相同的情况(所有文件路径都是您的)。
    • 或已设置,然后您将无法修改您不拥有或没有写权限的文件,因此出于我们寻找可修改文件路径的目的,这与完全没有写权限相同。

因此,我们可以通过以下方式处理1、2、5和6的全部问题:

find / -type d \
  \( \
    -user "$user" -prune -exec find {} + -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" \( -type d -o -print \) -o \
    \( -group $groups \) \( ! -perm -g=w -o \
       -type d ! -perm -1000 -exec find {} + -o -print \) -o \
    ! -perm -o=w -o \
    -type d ! -perm -1000 -exec find {} + -o \
    -print

3和4的解决方案是独立的,您可以合并其输出以获得完整列表:

{
  find / ! -type l -print0 |
    sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
    perl -0lne '
      ($w,$p) = /(.)(.*)/;
      ($dev,$ino) = stat$p or next;
      $writable{"$dev,$ino"} = 1 if $w;
      push @{$p{"$dev,$ino"}}, $p;
      END {
        for $i (keys %writable) {
          for $p (@{$p{$i}}) {
            print $p;
          }
        }
      }'
  find / -type d \
    \( \
      -user "$user" -prune -exec sh -c 'exec find "$@" -print0' sh {} + -o \
      \( -group $groups \) \( -perm -g=x -o -prune \) -o \
      -perm -o=x -o -prune \
    \) ! -type d -o -type l -o \
      -user "$user" \( -type d -o -print0 \) -o \
      \( -group $groups \) \( ! -perm -g=w -o \
         -type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o -print0 \) -o \
      ! -perm -o=w -o \
      -type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o \
      -print0
} | perl -l -0ne 'print unless $seen{$_}++'

应当清楚的是,到目前为止,您已经阅读了所有内容,其中的至少一部分仅涉及权限和所有权,而不涉及可能授予或限制写访问权限的其他功能(只读FS,ACL,不可变标志,其他安全功能) ...)。并且在我们分几个阶段进行处理的过程中,如果在脚本运行时创建/删除/重命名文件/目录或修改其权限/所有权,则其中某些信息可能是错误的,例如在拥有数百万个文件的繁忙文件服务器上。

便携性说明

所有这些代码都是标准的(POSIX,Unix t),除了:

  • -print0是GNU扩展,现在也得到其他一些实现的支持。对于find缺乏支持的实现,您可以改用-exec printf '%s\0' {} +并替换-exec sh -c 'exec find "$@" -print0' sh {} +-exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +
  • perl不是POSIX指定的命令,但可以广泛使用。您需要perl-5.6.0或以上的-Mfiletest=access
  • zsh不是POSIX指定的命令。这zsh上面的代码应与zsh的-3(1995)及以上的工作。
  • sudo不是POSIX指定的命令。该代码应适用于任何版本,只要系统配置允许perl以给定用户身份运行即可。

什么是搜索访问权限?我从未在传统权限中听说过它:读取,写入,执行。
bela83

2
@ bela83,对目录的执行权限(您不执行目录)转换为search。那就是访问其中文件的能力。如果您具有读取权限,则可以列出目录的内容,但是除非对其具有搜索(x位)权限,否则您不能对目录中的文件执行任何操作。您还可以具有搜索权限,但不具有读取权限,这意味着其中的文件对您隐藏了,但是如果您知道它们的名称,就可以访问它们。一个典型的示例是php会话文件目录(类似于/ var / lib / php)。
斯特凡Chazelas

2

您可以将选项与find命令结合使用,这样它将找出具有指定模式和所有者的文件。例如:

$ find / \( -group staff -o -group users \) -and -perm -g+w

上面的命令将列出所有属于“ staff”或“ users”组并对该组具有写许可权的条目。

您还应该检查用户拥有的条目,并且所有文件都是可写的,因此:

$ find / \( -user yourusername -or \
             \(  \( -group staff -o -group users \) -and -perm -g+w \
             \) -or \
            -perm -o+w \
         \)

但是,此命令将不匹配具有扩展ACL的条目。因此,您可能会su发现所有可写条目:

# su - yourusername
$ find / -writable

那就是说带有r-xrwxrwx yourusername:anygroupr-xr-xrwx anyone:staff可写的文件。
斯特凡Chazelas

它还将报告为目录yourusername中无权访问的可写文件。
斯特凡Chazelas

1

该方法取决于您实际测试的内容。

  1. 您是否要确保可以进行写访问?
  2. 您是否要确保缺少写访问权限?

这是因为有很多方法可以得出2),而Stéphane的答案很好地说明了这一点(牢记是不变的),并且回想起还有一些物理方法,例如卸下驱动器或将其设为只读。硬件级别(软盘选项卡)。我猜您成千上万的文件位于不同的目录中,并且您想要一份报告,或者正在对照主列表进行检查。(另一个对Puppet的滥用只是在等待发生)。

您可能希望Stéphane的perl树遍历并在需要时将输出与列表“连接”(su还会在父目录上捕获丢失的execute?)。如果代孕是一个性能问题,您是否对“大量”用户这样做?还是在线查询?如果这是永久性的持续性要求,则可能是时候考虑第三方产品了。


0

你可以做...

find / ! -type d -exec tee -a {} + </dev/null

...有关用户无法按照以下格式写入stderr 的所有文件的列表...

"tee: cannot access %s\n", <pathname>" 

...或类似。

请参阅以下注释,以获取有关此方法可能存在的问题的注释,以及以下有关其可能起作用的解释。不过,更为理智的是,您应该可能应该只使用 find常规文件,例如:

find / -type f -exec tee -a {} + </dev/null

简而言之,tee当尝试open()使用两个标志中的任何一个文件时,将打印错误...

O_WRONLY

仅开放用于写作。

O_RDWR

开放供阅读和写作。如果将此标志应用于FIFO,则结果不确定。

...和s ...

[EACCES]

路径前缀的某个组件上的搜索许可被拒绝,或者该文件存在并且由oflag指定的许可被拒绝,或者该文件不存在并且对要创建的文件的父目录的写许可被拒绝,或者O_TRUNC是指定,并且写入权限被拒绝。

......作为指定在这里

tee实用程序将标准输入复制到标准输出,零个或多个文件进行复印。tee实用程序不得缓冲输出。

如果-a未指定该选项,则应写入输出文件(请参阅文件读取,写入和创建)。

... POSIX.1-2008需要等同于使用O_APPEND的功能...

因为它必须检查相同的方式test -w...

-w 路径名

如果路径名解析为文件的现有目录条目,则将为True ,该文件将被授予对其写入文件的权限,如File Read,Write和Creation中所定义。假,如果路径不能得到解决,或者如果路径名解析为一个文件的现有目录条目,其权限写入该文件将不被批准。

他们都检查EACCESS


用这种方法,您可能会遇到并发打开文件的数量限制(除非文件数量很少)。注意设备和命名管道的副作用。您将收到有关套接字的其他错误消息。
斯特凡Chazelas

@StéphaneChazelas-所有星期二-我认为tee悬挂也可能是真的,除非每次运行明确中断一次。不过,这是我想到的最接近的东西[ -w-它的效果应该接近,因为它可以确保用户可以OAPPEND该文件。与任何一个选项相比,pax使用-o格式选项和/或-t进行检查要容易得多,EACCESS但是每次我建议pax人们似乎都不会这样做。而且,无论如何,pax我发现唯一符合标准的东西就是AST-在这种情况下,您最好使用它们ls
mikeserv
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.