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's ), які перевіряють лише дозвіл file.txt
, не працюватимуть, тому що вони можуть сказати, що вони підлягаютьfile.txt
запису, навіть якщо користувач не має дозволу на пошук до /
або /foo
.
І такий підхід, як:
sudo -u "$user" find / -writeble
Не працюватиме також тому, що не буде повідомляти про файли в каталогах, користувач не має доступу до читання (як find
запущений, оскільки $user
не може перерахувати їх вміст), навіть якщо він може писати до них.
Якщо ми забули про файли ACL, файлові системи лише для читання, прапорці FS (наприклад, незмінні), інші заходи безпеки (apparmor, SELinux, які навіть можуть відрізняти різні типи запису) та зосередимось лише на традиційних атрибутах дозволу та власності, щоб отримати надати (шукати чи писати) дозвіл, це вже досить складно і важко виразити find
.
Тобі потрібно:
- якщо вами належить файл, вам потрібен цей дозвіл для власника (або маєте uid 0)
- якщо файл не належить вам, але група одна з ваших, тоді вам потрібен цей дозвіл для групи (або маєте uid 0).
- якщо він не належить вам і не належить ні до однієї з ваших груп, тоді інші дозволи застосовуються (якщо ваш номер користувача не дорівнює 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
)
Найкраще тут було б спустити дерево як корінь і перевірити права доступу для користувача для кожного файлу.
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
наприклад.
Тепер усі ці рішення намагаються визначити шляхи до файлів, які користувач може відкрити для запису, що відрізняється від шляхів, де користувач може змінити вміст. Щоб відповісти на більш загальне запитання, слід врахувати кілька речей:
- $ користувач може не мати доступу до запису,
/a/b/file
але якщо він володіє file
(і має доступ до пошуку /a/b
, а файлова система не є лише для читання, і файл не має незмінного прапора, і він має доступ до оболонки до системи), тоді він зможе змінити дозволи дозволу file
та надати собі доступ.
- Те саме, якщо він є власником,
/a/b
але не має доступу до нього.
- $ Користувач не може мати доступ до ,
/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, записуючи прилади та номери inode або всі файли $ 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, ви можете надати собі доступ для запису, а т-біт не застосовується (і ви можете його видалити в будь-якому випадку), так що ви можете видалити / перейменувати / відтворити будь-який файл або dirs там, всі шляхи до файлів є вашими, щоб переписати будь-який вміст.
- Якщо ви не володієте ним, але маєте доступ до запису, то:
- Або
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
як даний користувач.