![通过最少的进程创建有效地查找用户可以写入的文件](https://linux22.com/image/65114/%E9%80%9A%E8%BF%87%E6%9C%80%E5%B0%91%E7%9A%84%E8%BF%9B%E7%A8%8B%E5%88%9B%E5%BB%BA%E6%9C%89%E6%95%88%E5%9C%B0%E6%9F%A5%E6%89%BE%E7%94%A8%E6%88%B7%E5%8F%AF%E4%BB%A5%E5%86%99%E5%85%A5%E7%9A%84%E6%96%87%E4%BB%B6.png)
我是根。我想知道非 root 用户是否具有对某些文件(数千个)的写访问权限。如何在避免创建流程的同时高效地完成它?
答案1
长话短说
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的或者@阿保罗的)仅检查 的权限是file.txt
行不通的,因为file.txt
即使用户没有/
或 的搜索权限,他们也可以说是可写的/foo
。
以及类似的方法:
sudo -u "$user" find / -writeble
也不起作用,因为它不会报告用户没有读取访问权限的目录中的文件(因为无法列出其内容find
而运行$user
),即使他可以写入这些文件。
如果我们忘记 ACL、只读文件系统、FS 标志(如不可变)、其他安全措施(apparmor、SELinux,甚至可以区分不同类型的写入),只关注传统的权限和所有权属性,以获得给予(搜索或写入)许可,这已经相当复杂并且难以用find
.
你需要:
- 如果该文件归您所有,则您需要所有者的许可(或 uid 0)
- 如果该文件不属于您,但该组属于您,则您需要该组的权限(或具有 uid 0)。
- 如果它不属于您所有,也不属于您的任何组,那么其他权限适用(除非您的 uid 为 0)。
在find
语法上,这里以 uid 1 和 gids 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
)
最好的方法是以 root 身份下降树并以用户身份检查每个文件的权限。
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(这是一个find
进程加一,每隔几千个文件处理一次,sudo
并且通常构建在 shell 中)。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
路径打开它来修改 的内容。 - 同样的事情与绑定安装。他可能没有搜索权限
/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 具有写入权限的所有文件来解决 3 和 4,并报告具有这些 dev+inode 编号的所有文件路径。这次,我们可以使用更可靠的 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 for t
bit),除了:
-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
以给定用户身份运行,代码就应该适用于任何版本。
答案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éphane Chazelas 的评论,了解这种方法的过去、现在、未来以及潜在的危险和局限性。
答案3
您可以将选项与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
答案4
你可以做...
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,则结果未定义。
……以及遭遇……
[EACCES]
对路径前缀的某个组成部分的搜索权限被拒绝,或者文件存在并且 oflag 指定的权限被拒绝,或者文件不存在并且要创建的文件的父目录的写权限被拒绝,或者 O_TRUNC 为指定且写入权限被拒绝。
...作为指定这里:
该
tee
实用程序应将标准输入复制到标准输出,在零个或多个文件中进行复制。 tee 实用程序不应缓冲输出。如果
-a
未指定该选项,则应写入输出文件(请参阅文件读取、写入和创建)......POSIX.1-2008 需要相当于使用的功能O_APPEND...
因为它必须以同样的方式检查test -w
做...
-w
路径名为真,如果路径名解析为文件的现有目录条目,将授予该文件写入权限,如中定义文件读取、写入和创建。假如果路径名无法解决,或者如果路径名解析为文件的现有目录条目,不会授予该文件写入权限。
他们都检查易存取。