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
是符号链接)?
他需要:
- 搜索访问
/
(无需read
)
- 搜索访问
/foo
(无需read
)
- 写入权限
/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: id
,sed
和find
)
最好的做法是将树作为根目录下移,并以用户身份检查每个文件的权限。
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(这是一个find
过程加一sudo
和sh
处理每个几千文件,[
并且printf
通常建在外壳)。
或搭配perl
:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
(3个工艺总数:find
,sudo
和perl
)。
或搭配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
。
现在,所有这些解决方案都试图识别用户可以打开以进行写入的文件的路径,这与用户可以修改内容的路径不同。要回答这个更通用的问题,需要考虑以下几件事:
- $ user可能无权对其进行写访问,
/a/b/file
但如果他拥有file
(并且具有对的搜索访问权/a/b
,并且文件系统不是只读的,并且文件没有不可变标志,并且可以对系统进行shell访问),那么他就可以更改的权限file
并授予自己访问权限。
- 如果他拥有
/a/b
但没有搜索权限,则为同一件事。
- $ user可能无权访问,
/a/b/file
因为他没有对/a
或的搜索访问权/a/b
,但是该文件可能在/b/c/file
例如具有硬链接,在这种情况下,他可以/a/b/file
通过通过其/b/c/file
路径打开文件来修改其内容。
- 与bind-mounts相同。他可能没有搜索权限
/a
,但/a/b
可能绑定安装在中/c
,因此他可以file
通过/c/file
其他路径打开进行写操作。
- 他可能没有对的写入权限
/a/b/file
,但是如果他具有写入权限,则/a/b
可以file
在那里删除或重命名并将其替换为自己的版本。他将更改文件的内容,/a/b/file
即使那是另一个文件也是如此。
- 如果他具有写入权限
/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
以给定用户身份运行即可。