TL; DR
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
Вам нужно спросить систему, есть ли у пользователя разрешение на запись. Единственный надежный способ - переключить эффективный uid, эффективный gid и gid дополнения на пользовательский и использовать access(W_OK)
системный вызов (даже если это имеет некоторые ограничения для некоторых систем / конфигураций).
И имейте в виду, что отсутствие разрешения на запись в файл не обязательно гарантирует, что вы не сможете изменить содержимое файла по этому пути.
Длинная история
Давайте рассмотрим , что требуется, например , для $ пользователю иметь доступ на запись /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 (например, неизменяемых), других мерах безопасности (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
и 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 и gids 1 и 2 будет иметь доступ для записи, u2g1/010/u2g3/777
но неu1g2/677/u1g1/777
например.
Теперь все эти решения пытаются определить пути к файлам, которые пользователь может открыть для записи, это отличается от путей, по которым пользователь может изменять содержимое. Чтобы ответить на этот более общий вопрос, необходимо принять во внимание несколько вещей:
- $ user может не иметь доступа для записи,
/a/b/file
но если он владеет file
(и имеет доступ к поиску /a/b
, и файловая система не доступна только для чтения, и у файла нет неизменяемого флага, и у него есть доступ оболочки к системе), тогда он сможет изменить права доступа 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
Мы могли бы обратиться к 3 и 4, записав номера устройств и индексов или все файлы, которые $ user имеет разрешение на запись, и сообщать обо всех путях к файлам, которые имеют эти номера 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 для 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
от имени данного пользователя.