最小限のプロセス作成で、ユーザーが効率的に書き込めるファイルを見つける


20

私はルートです。非ルートユーザーが一部のファイル(数千のファイル)に書き込みアクセスできるかどうかを知りたいのですが。プロセスの作成を避けながら効率的にそれを行う方法は?


実際に何をしているかを教えてください!
F.ハウリ


競合状態を気にしないと仮定access(2)して、適切に設定された実際のUIDを使用して(たとえば、経由setresuid(2)または移植可能な同等の)だけで呼び出してみませんか?つまり、bashからそれを行うのは難しいと思いますが、Perl / Pythonがそれを処理できると確信しています。
ケビン

1
@Kevin、シェル[ -wは一般にaccess(2)または同等のものを使用します。また、uidに加えてgidを設定する必要があります(suまたはsudoのように)。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 ''
ステファンChazelas

Linux statのようなものはないことに注意してください。Linuxは、一部のGNUおよび非GNUシステムで使用されるカーネルです。util-linuxLinux向けに特別に作成されたコマンド(fromなど)がありstatますが、言及しているのはLinuxではなく、ほとんどのシステムに移植されたGNUコマンドです。また、statGNU statが書かれたずっと前にLinuxにコマンドがあったことに注意してください(statzshビルトイン)。
ステファンシャゼル

2
@StéphaneChazelas:Linuxのようなものはないことに注意してください。-「Linuxの化身」を書いたと思うstat(1)。BSD構文とは対照的に、構文stat(1)を受け入れるを参照してい-c <format>ます-f <format>。私が言及していないことはかなり明らかだったと思いstat(2)ます。ただし、一般的なコマンドの履歴に関するWikiページは非常に興味深いものになるはずです。
lcd047

1
@StéphaneChazelas:それは、ユーザーが現在のディレクトリにアクセスできない場合でも、ファイルは書き込み可能であると言えます。-確かに、おそらく合理的な制限。これは、ファイル名に改行文字が含まれていないことを前提としています-Trueであり、おそらく合理的な制限です。そして、stdinで渡されるファイルパスは、次で始まらない --編集済み、ありがとう。
lcd047

17

TL; DR

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

ユーザーに書き込み権限があるかどうかをシステムに尋ねる必要があります。唯一の信頼できる方法は、有効なuid、有効なgid、および補足のgidをユーザーのgidに切り替え、access(W_OK)システムコールを使用することです(システム/構成によっては制限があります)。

また、ファイルへの書き込み権限を持たないからといって、必ずしもそのパスでファイルの内容を変更できないとは限らないことを覚えておいてください。

長い話

のは、それはへの書き込みアクセス持っている$ユーザーのためのインスタンスのにかかるかを考えてみましょう/foo/file.txt(のどれを想定していない/foo/foo/file.txtシンボリックリンクされているが)?

彼が必要とします:

  1. への検索アクセス/(の必要なしread
  2. への検索アクセス/foo(の必要なしread
  3. への書き込みアクセス/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と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

1つのそれプルーンユーザーが権利のためにと他の種類のファイル(彼らは関係ありませんねとして除外シンボリックリンク)、書き込みアクセスのためのチェックのために検索していないことをディレクトリ。

ディレクトリへの書き込みアクセスも検討する場合:

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つのプロセスです:idsedおよびfind

ここでの最良の方法は、ルートとしてツリーを降りて、各ファイルのユーザーとして権限を確認することです。

 find / ! -type l -exec sudo -u "$user" sh -c '
   for file do
     [ -w "$file" ] && printf "%s\n" "$file"
   done' sh {} +

(それは一つだfindプロセスプラスワンsudoshごとに数千のファイルを処理し、[そしてprintf通常はシェルに組み込まれています)。

またはperl

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'

(合計3プロセス:findsudoおよび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の間でグループを作成し、既に9458694のファイルが作成されているため、アクセス許可の下位9ビットに制限します。ディレクトリの場合は、ファイルの場合も同様です。

これにより、可能なすべての組み合わせが作成されますu<x>g<y>/<mode1>/u<z>g<w>/<mode2>。uid 1とgids 1および2を持つユーザーは書き込みアクセス権を持ちますu2g1/010/u2g3/777u1g2/677/u1g1/777、たとえばアクセス権はありません。

現在、これらのソリューションはすべて、ユーザーが書き込みのために開く可能性のあるファイルのパスを特定しようとします。これは、ユーザーがコンテンツを変更できるパスとは異なります。このより一般的な質問に答えるために、考慮すべきことがいくつかあります。

  1. $ userは書き込みアクセス権を持たないかもしれませんが/a/b/file、所有している場合file(および、検索アクセス権を/a/b持ち、ファイルシステムが読み取り専用ではなく、ファイルに不変フラグがなく、システムへのシェルアクセスがある場合)、その後、彼はの許可を変更し、file自分にアクセスを許可することができます。
  2. 彼が所有/a/bしているが検索アクセス権を持っていない場合も同じです。
  3. $ userは/a/b/file/aまたはへの検索アクセス権がないためアクセスできませんが/a/b、そのファイルには/b/c/file、たとえば、ハードリンクが含まれている場合があり、その場合/a/b/file/b/c/fileパスを介してファイルを開くことでコンテンツを変更できる場合があります。
  4. bind-mountsでも同じです。彼はへの検索アクセス権を持っていない可能性/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

デバイスとiノード番号、または$ 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、tビットはUnix )。

  • -print0は、現在いくつかの他の実装でもサポートされているGNU拡張機能です。findそれをサポートしていない実装では、使用することができます-exec printf '%s\0' {} +代わりに、と交換してください-exec sh -c 'exec find "$@" -print0' sh {} +-exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +
  • perlPOSIX指定のコマンドではありませんが、広く利用可能です。perl-5.6.0以上が必要です-Mfiletest=access
  • zshPOSIX指定のコマンドではありません。そのzshコードは、上記のzsh-3(1995)および上記で動作しなければなりません。
  • sudoPOSIX指定のコマンドではありません。システム構成で指定さperlれたユーザーとして実行できる限り、コードはどのバージョンでも動作するはずです。

検索アクセスとは何ですか?読み取り、書き込み、実行という従来の権限で聞いたことはありません。
bela83

2
@ bela83、ディレクトリの実行許可(ディレクトリを実行しない)はsearchに変換されます。それはその中のファイルにアクセスする能力です。読み取り許可がある場合、ディレクトリのコンテンツを一覧表示できますが、ディレクトリに対する検索(xビット)許可も持たない限り、ディレクトリ内のファイルに対して何もできません。また、検索権限は持つことができますが、読み取りはできません。つまり、そこにあるファイルは非表示になりますが、名前がわかっている場合はアクセスできます。典型的な例は、phpセッションファイルディレクトリ(/ var / lib / phpなど)です。
ステファンシャゼル

2

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

つまり、ファイルは書き込み可能r-xrwxrwx yourusername:anygroupまたはr-xr-xrwx anyone:staff書き込み可能です。
ステファンシャゼル

また、ディレクトリにある書き込み可能なファイルにyourusernameアクセスできないと報告します。
ステファンシャゼル

1

アプローチは、実際にテストする対象によって異なります。

  1. 書き込みアクセスが可能なことを確認しますか?
  2. 書き込みアクセス権がないことを確認しますか?

これは、2)に到達する方法が非常に多く、ステファンの答えがこれらを十分にカバーしており(不変は覚えておくべきものです)、物理的な手段もあることを思い出してください。ハードウェアレベル(フロッピータブ)。私はあなたの数千のファイルが異なるディレクトリにあり、レポートを望んでいるか、マスターリストと照合していると推測しています。(Puppetのもう1つの乱用が発生するのを待っています)。

必要に応じて、Stéphaneのperlツリートラバーサルを実行し、必要に応じて出力をリストに「結合」することをお勧めします(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

要するに、2つのフラグのいずれかでファイルをtee試みるとエラーを出力open()します...

O_WRONLY

書き込み専用に開きます。

O_RDWR

読み取りおよび書き込み用に開きます。このフラグがFIFOに適用される場合、結果は未定義です。

...そして出会い...

[EACCES]

パスプレフィックスのコンポーネントで検索許可が拒否されているか、ファイルが存在し、oflagで指定された許可が拒否されているか、ファイルが存在せず、作成するファイルの親ディレクトリの書き込み許可が拒否されているか、O_TRUNCが指定され、書き込み権限が拒否されました。

... ここで指定されているとおり:

teeユーティリティは、ゼロ以上のファイルにコピーを作成し、標準出力に標準入力をコピーするものとします。ティーユーティリティは、出力をバッファリングしてはなりません。

-aオプションが指定されていない場合、出力ファイルが書き込まれます(ファイルの読み取り、書き込み、作成を参照)...

... POSIX.1-2008にはO_APPENDを使用するのと同等の機能が必要です...

同じ方法でチェックする必要があるためtest -w...

-w パス名

File Read、Write、およびCreationで定義されているように、ファイルへの書き込み権限が付与されるファイルの既存のディレクトリエントリにパス名が解決される場合はtrue 。パス名を解決できない場合、またはファイルへの書き込み権限が付与されないファイルの既存のディレクトリエントリにパス名が解決される場合はfalse 。

彼らは両方ともEACCESSをチェックします


その方法では、同時に開くことができるファイルの数の制限に直面する可能性があります(ファイルの数が少ない場合を除く)。デバイスと名前付きパイプの副作用に注意してください。ソケットに対して別のエラーメッセージが表示されます。
ステファンシャゼラス

@StéphaneChazelas-すべての火- tee実行ごとに1回明示的に中断しない限りハングすることも事実だと思います。それは私が考えることができる最も近いものでした[ -w...しかし-それはユーザーがファイルをOAPPENDできることを保証するという点で効果が近いはずです。どちらのオプションよりもはるかに容易になるだろうpax-oフォーマットオプションおよび/または-tに対するチェックのためにEACCESS-しかし、私はお勧めするたびにpax、人々はそれをオフに肩をすくめているように見えます。とにかく、pax私が見つけたのはstdを満たすものだけで、ASTがありますls
mikeserv
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.