Знаходьте файли, які користувач може записувати, ефективно з мінімальним створенням процесів


20

Я корінь. Мені хотілося б знати, чи має користувач, який не має права доступу, мати доступ до запису до деяких файлів - їх тисячі. Як це зробити ефективно, уникаючи створення процесу?


Покажіть, будь ласка, що ви насправді робите!
Ф. Хаурі

Те саме питання, що і unix.stackexchange.com/a/88591
Stéphane Chazelas

Якщо припустити, що вам не байдуже умови перегонів, то чому б просто не зателефонувати access(2)з відповідним чином встановленим реальним UID (наприклад, через setresuid(2)або портативний еквівалент)? Я маю на увазі, що мені буде важко це зробити з баша, але я впевнений, що Perl / Python впорається з цим.
Кевін

1
@Kevin, оболонки [ -wзазвичай використовують доступ (2) або еквівалент. Вам також потрібно встановити gids на додаток до uid (як su або sudo do). bash не має вбудованої підтримки для цього, але zsh робить.
Стефан Шазелас

@ StéphaneChazelas - ви можете використовувати chgrpв будь-якій оболонці.
mikeserv

Відповіді:


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: Будь ласка, прочитайте коментарі Стефана Шазела нижче щодо минулого, сьогодення, майбутнього та потенційних небезпек та обмежень цього підходу.


Це може сказати, що файл можна записати, навіть якщо користувач не має доступу до каталогу, де він є.
Стефан Шазелас

Це передбачає, що імена файлів не містять символів нового рядка, і шляхи до файлів, передані на stdin, не починаються з -. Ви можете змінити його, щоб прийняти список обмежених NUL замість цьогоread -d ''
Stéphane Chazelas

Зауважте, що не існує такої речі, як статистика Linux . Linux - це ядро, яке зустрічається в деяких системах GNU та non-GNU. Хоча є команди (на зразок від util-linux), спеціально написані для Linux, statви посилаєтесь на це не, це команда GNU, яка переноситься на більшість систем, а не тільки на Linux. Також зауважте, що ви мали statкоманду в Linux задовго до того, як statбуло написано GNU ( statzsh buildin).
Стефан Шазелас

2
@ Stéphane Chazelas: Зауважте, що немає такої речі, як статистика Linux. - Я вважаю, що написав "втілення Linux stat(1)". Я маю на увазі a, stat(1)який приймає -c <format>синтаксис, на відміну від, скажімо, BSD-синтаксису -f <format>. Я також вважаю, що це було досить ясно, на яке я не звертався stat(2). Я впевнений, що вікі-сторінка про історію загальних команд була б дуже цікава.
lcd047

1
@ Stéphane Chazelas: Це може сказати, що файл можна записати, навіть якщо користувач не має доступу до каталогу, де він є. - Правда і, мабуть, розумне обмеження. Це передбачає, що імена файлів не містять символу нового рядка - True та, ймовірно, розумне обмеження. і шляхи до файлів, передані stdin, не починаються з - - Відредаговано, дякую.
lcd047

17

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є посиланнями)?

Йому потрібно:

  1. пошук доступу до /(немає необхідності read)
  2. пошук доступу до /foo(немає необхідності read)
  3. написати доступ до/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наприклад.

Тепер усі ці рішення намагаються визначити шляхи до файлів, які користувач може відкрити для запису, що відрізняється від шляхів, де користувач може змінити вміст. Щоб відповісти на більш загальне запитання, слід врахувати кілька речей:

  1. $ користувач може не мати доступу до запису, /a/b/fileале якщо він володіє file(і має доступ до пошуку /a/b, а файлова система не є лише для читання, і файл не має незмінного прапора, і він має доступ до оболонки до системи), тоді він зможе змінити дозволи дозволу fileта надати собі доступ.
  2. Те саме, якщо він є власником, /a/bале не має доступу до нього.
  3. $ Користувач не може мати доступ до , /a/b/fileтому що він не має доступу до пошукової /aабо /a/b, але цей файл може мати жорстке посилання на /b/c/file, наприклад, в цьому випадку він може бути в змозі змінити зміст /a/b/file, відкривши його через свій /b/c/fileшлях.
  4. Те ж саме і з кріпленнями . Він може не мати доступу до пошуку /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

Ми можемо адресувати 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як даний користувач.

Що таке доступ до пошуку ? Я ніколи не чув про це в традиційних дозволах: читати, писати, виконувати.
bela83

2
@ bela83, виконання дозволу в каталозі (ви не виконуєте каталоги) перекладається на пошук . Це можливість доступу до файлів у ньому. Ви можете перерахувати вміст каталогу, якщо ви маєте дозвіл на читання, але нічого не можете зробити з файлами в ньому, якщо ви також не маєте xдозволу на пошук ( біт) у каталозі. Ви також можете мати дозволи на пошук, але не читати , тобто файли там приховані для вас, але якщо ви знаєте їх ім’я, ви можете отримати доступ до них. Типовий приклад - каталог файлів сеансів php (щось на кшталт / var / lib / php).
Стефан Шазелас

2

Ви можете комбінувати параметри з findкомандою, так вона знайде файли із заданим режимом та власником. Наприклад:

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

Вищевказана команда перелічить усі записи, які належать до групи "персонал" або "користувачі", і матиме дозвіл на написання для цієї групи.

Ви також повинні перевірити записи, які належать вашому користувачеві, а також файли, які можуть бути записані у всьому світі, так:

$ 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:anygroupабо r-xr-xrwx anyone:staffпідлягає запису.
Стефан Шазелас

Він також повідомляє, що файли, що записуються, що знаходяться в каталогах yourusername, не мають доступу до них.
Стефан Шазелас

1

Підхід залежить від того, що ви насправді випробовуєте.

  1. Ви хочете забезпечити доступ для запису?
  2. Ви хочете забезпечити відсутність доступу для запису?

Це тому, що існує стільки способів домогтися 2), і відповідь Стефана охоплює це добре (незмінним є те, що потрібно пам’ятати), і нагадуємо, що існують і фізичні засоби, такі як відключення накопичувача або переведення його в режим читання лише на апаратний рівень (дискети). Я здогадуюсь, що ваші тисячі файлів знаходяться в різних каталогах, і ви хочете отримати звіт або перевіряєте основний список. (Ще одне зловживання Лялькою, яке тільки чекає, що станеться).

Ви, ймовірно, хочете, щоб Stéphane перейшов з дерева дерева і "приєднати" висновок зі списком, якщо це потрібно (su також буде ловити відсутні файли у батьківських каталогах?). Якщо сурогатство - це питання ефективності, чи робите ви це для "великої кількості" користувачів? Або це онлайн-запит? Якщо це постійні постійні вимоги, може бути час розглянути товар третьої сторони.


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.

... і зустрічі ...

[EACCES]

Дозвіл на пошук заборонено на компоненті префікса шляху, або файл існує, а дозволи, визначені oflag, відхилено, або файл не існує, і дозвіл на запит відхилено для батьківського каталогу файлу, який слід створити, або O_TRUNC є вказаний і в дозволі на запит відмовлено.

... як зазначено тут :

teeУтиліта буде копіювати стандартне введення в стандартний висновок, що робить копію в нуль або більше файлів. Утиліта трійника не повинна захищати вихід.

Якщо -aпараметр не вказаний, виводяться файли виводу (див. Читання, запис та створення файлів ) ...

... POSIX.1-2008 вимагає функціоналу, еквівалентного використанню O_APPEND ...

Тому що це має перевірити так само test -w...

-w ім'я шляху

Істинно, якщо ім'я шляху доходить до існуючої записи каталогу для файлу, на який буде надано дозвіл на запис у файл, як визначено у розділі « Читання файлів, запис та створення» . Неправильно, якщо ім'я шляху не вдалося вирішити, або якщо ім'я шляху дозволено до існуючої записи каталогу для файлу, для якого дозвіл на запис у файл не буде надано.

Вони обидва перевіряють EACCESS .


Ви, ймовірно, зіткнетеся з обмеженням кількості одночасно відкритих файлів при такому підході (якщо тільки кількість файлів не мала). Остерігайтеся побічних ефектів із пристроями та названими трубами. Ви отримаєте інше повідомлення про помилку для розеток.
Стефан Шазелас

@ StéphaneChazelas - Все вт - я думаю, що може бути правдою і те, що teeбуде висіти, якщо явно не буде перервано один раз на пробіг. Це було найближче, про що я міг придумати [ -w... хоча - це ефекти повинні бути близькими, щоб це гарантувало користувачеві ОПЦІЯТЬ файл. Набагато простіше, ніж будь-який варіант, було б paxз -oпараметрами формату та / або -tдля перевірки проти EACCESS- але кожен раз, коли я пропоную paxлюдям, здається, знищують його. І, в будь-якому випадку, єдине, що paxя знайшов, що відповідає std там, - це AST, і в цьому випадку ви можете також використовувати їх ls.
mikeserv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.