最小限のプロセス生成で、ユーザーが書き込むことができるファイルを効率的に見つけます。

最小限のプロセス生成で、ユーザーが書き込むことができるファイルを効率的に見つけます。

私は根です。 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)。

彼は以下が必要です:

  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フラグ(例:不変)、その他のセキュリティ対策(衣類、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

それ打つユーザーが検索権限を持たないディレクトリーおよび他のタイプのファイルへの書き込みアクセス権を確認してください(関連のないシンボリックリンクを除く)。

ディレクトリへの書き込みアクセスも考慮するには、次の手順を実行します。

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

または、ユーザーデータベースから取得された1つ$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何千ものファイルを処理するプロセスに1つを加えたsudoもので、通常はシェルに組み込まれています。)sh[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、gid 1、2を持つユーザーには書き込み権限がありますが、u2g1/010/u2g3/777書き込みu1g2/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. 同じものバンドルのインストール。その人は検索権限を持っていないかも/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

デバイスとinode番号、または$ 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/bfilet/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 指定コマンドではありません。上記のコードはzshzsh-3(1995)以降で動作します。
  • sudoPOSIX 指定コマンドではありません。コードは、システム構成でperl特定のユーザーとして実行できるようにする限り、すべてのバージョンで動作する必要があります。

おすすめ記事