TL; DR
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
Bạn cần hỏi hệ thống nếu người dùng có quyền ghi. Cách đáng tin cậy duy nhất là chuyển đổi các gid hiệu quả, gid và bổ sung hiệu quả sang người dùng và sử dụng lệnh access(W_OK)
gọi hệ thống (thậm chí có một số hạn chế đối với một số hệ thống / cấu hình).
Và hãy nhớ rằng việc không có quyền ghi vào tệp không nhất thiết đảm bảo rằng bạn không thể sửa đổi nội dung của tệp tại đường dẫn đó.
Câu chuyện dài hơn
Chúng ta hãy xem xét những gì cần có để người dùng $ có quyền truy cập ghi /foo/file.txt
(giả sử không có /foo
và /foo/file.txt
là liên kết tượng trưng)?
Anh ta cần:
- truy cập tìm kiếm
/
(không cần read
)
- truy cập tìm kiếm
/foo
(không cần read
)
- viết quyền truy cập vào
/foo/file.txt
Bạn có thể thấy các phương pháp tiếp cận (như @ lcd047's hoặc @ apaul's ) chỉ kiểm tra sự cho phép của file.txt
sẽ không hoạt động vì họ có thể nói file.txt
là có thể ghi được ngay cả khi người dùng không có quyền tìm kiếm /
hoặc /foo
.
Và một cách tiếp cận như:
sudo -u "$user" find / -writeble
Sẽ không hoạt động vì nó sẽ không báo cáo các tệp trong thư mục mà người dùng không có quyền truy cập đọc (vì find
đang chạy vì $user
không thể liệt kê nội dung của họ) ngay cả khi anh ta có thể viết thư cho họ.
Nếu chúng ta quên ACL, hệ thống tệp chỉ đọc, cờ FS (như bất biến), các biện pháp bảo mật khác (apparmor, SELinux, thậm chí có thể phân biệt giữa các loại văn bản khác nhau) và chỉ tập trung vào các thuộc tính quyền và quyền sở hữu truyền thống, để có được cho phép (tìm kiếm hoặc viết), điều đó đã khá phức tạp và khó diễn đạt find
.
Bạn cần:
- nếu tệp thuộc sở hữu của bạn, bạn cần quyền đó cho chủ sở hữu (hoặc có uid 0)
- nếu tệp không thuộc sở hữu của bạn, nhưng nhóm là của bạn, thì bạn cần có quyền đó cho nhóm (hoặc có uid 0).
- nếu nó không thuộc sở hữu của bạn và không thuộc bất kỳ nhóm nào của bạn, thì các quyền khác sẽ được áp dụng (trừ khi uid của bạn bằng 0).
Theo find
cú pháp, đây là một ví dụ với người dùng uid 1 và gids 1 và 2, đó sẽ là:
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
Đó là một trong mận khô các thư mục mà người dùng không đã tìm kiếm phù hợp với và với nhiều loại file khác (symlink loại trừ như họ không có liên quan), kiểm tra để truy cập ghi.
Nếu bạn cũng muốn xem xét quyền truy cập ghi vào thư mục:
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
Hoặc cho một $user
thành viên tùy ý và thành viên nhóm của nó được lấy từ cơ sở dữ liệu người dùng:
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
(đó là 3 quá trình trong tổng số: id
, sed
và find
)
Điều tốt nhất ở đây là hạ cây dưới dạng root và kiểm tra các quyền như người dùng cho mỗi tệp.
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(đó là một find
quá trình cộng với một sudo
và sh
xử lý cứ sau vài nghìn tệp [
và printf
thường được xây dựng trong trình bao).
Hoặc với perl
:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
(3 quá trình trong tổng số: find
, sudo
và perl
).
Hoặc với zsh
:
files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
[ -w $f ] && print -r -- $f
}
(Tổng số 0 quá trình, nhưng lưu trữ toàn bộ danh sách tệp trong bộ nhớ)
Những giải pháp dựa trên access(2)
cuộc gọi hệ thống. Đó là thay vì sao chép thuật toán mà hệ thống sử dụng để kiểm tra quyền truy cập, chúng tôi yêu cầu hệ thống thực hiện kiểm tra đó với cùng một thuật toán (có tính đến quyền tài khoản, ACL, cờ bất biến, hệ thống tệp chỉ đọc ... ) nó sẽ sử dụng nếu bạn cố gắng mở tệp để viết, vì vậy đây là lần gần nhất bạn sẽ có được một giải pháp đáng tin cậy.
Để kiểm tra các giải pháp được đưa ra ở đây, với các kết hợp khác nhau của người dùng, nhóm và quyền, bạn có thể làm:
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
}
}
}
}
}
}'
Thay đổi người dùng giữa 1 và 2 và nhóm betweem 1, 2 và 3 và giới hạn bản thân chúng tôi ở mức 9 bit thấp hơn của các quyền như đã tạo các tệp 9458694. Điều đó cho các thư mục và sau đó một lần nữa cho các tập tin.
Điều đó tạo ra tất cả các kết hợp có thể u<x>g<y>/<mode1>/u<z>g<w>/<mode2>
. Người dùng có uid 1 và gids 1 và 2 sẽ có quyền truy cập ghi u2g1/010/u2g3/777
nhưng không u1g2/677/u1g1/777
.
Bây giờ, tất cả các giải pháp đó đều cố gắng xác định đường dẫn của tệp mà người dùng có thể mở để ghi, khác với đường dẫn mà người dùng có thể sửa đổi nội dung. Để trả lời câu hỏi chung chung hơn đó, có một số điều cần tính đến:
- $ user có thể không có quyền ghi vào
/a/b/file
nhưng nếu anh ta sở hữu file
(và có quyền truy cập tìm kiếm /a/b
và hệ thống tệp không ở chế độ chỉ đọc và tệp không có cờ bất biến và anh ta có quyền truy cập hệ thống), sau đó anh ta sẽ có thể thay đổi các quyền của file
và cấp cho chính mình quyền truy cập.
- Điều tương tự nếu anh ta sở hữu
/a/b
nhưng không có quyền truy cập tìm kiếm vào nó.
- $ user có thể không có quyền truy cập
/a/b/file
vì anh ta không có quyền truy cập tìm kiếm /a
hoặc /a/b
, nhưng tệp đó có thể có một liên kết cứng /b/c/file
chẳng hạn, trong trường hợp đó anh ta có thể sửa đổi nội dung /a/b/file
bằng cách mở nó qua /b/c/file
đường dẫn của nó .
- Điều tương tự với gắn kết gắn kết . Ông không thể có quyền truy cập tìm kiếm để
/a
, nhưng /a/b
có thể ràng buộc gắn trong /c
, vì vậy ông có thể mở file
cho văn bản qua nó /c/file
con đường khác.
- Anh ta có thể không có quyền ghi
/a/b/file
, nhưng nếu anh ta có quyền truy cập ghi vào /a/b
anh ta có thể xóa hoặc đổi tên file
trong đó và thay thế bằng phiên bản của chính anh ta. Anh ta sẽ thay đổi nội dung của tập tin /a/b/file
ngay cả khi đó sẽ là một tập tin khác.
- Điều tương tự nếu anh ta có quyền truy cập ghi
/a
(anh ta có thể đổi tên /a/b
thành /a/c
, tạo một /a/b
thư mục mới và một thư mục mới file
trong đó.
Để tìm các đường dẫn $user
có thể sửa đổi. Để giải quyết 1 hoặc 2, chúng tôi không thể dựa vào access(2)
cuộc gọi hệ thống nữa. Chúng tôi có thể điều chỉnh find -perm
cách tiếp cận của mình để giả sử quyền truy cập vào thư mục hoặc ghi quyền truy cập vào tệp ngay khi bạn là chủ sở hữu:
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
Chúng tôi có thể giải quyết 3 và 4, bằng cách ghi lại số thiết bị và số inode hoặc tất cả các tệp $ user có quyền ghi và báo cáo tất cả các đường dẫn tệp có các số dev + inode đó. Lần này, chúng ta có thể sử dụng các access(2)
phương pháp dựa trên đáng tin cậy hơn :
Cái gì đó như:
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 và 6 thoạt nhìn phức tạp bởi t
bit của các quyền. Khi được áp dụng trên các thư mục, đó là bit xóa bị hạn chế ngăn người dùng (những người khác không phải là chủ sở hữu của thư mục) xóa hoặc đổi tên các tệp mà họ không sở hữu (mặc dù họ có quyền truy cập ghi vào thư mục).
Ví dụ, nếu chúng ta quay lại ví dụ trước đó, nếu bạn có quyền truy cập ghi vào /a
, thì bạn sẽ có thể đổi tên /a/b
thành /a/c
, sau đó tạo lại một /a/b
thư mục và một thư mục mới file
trong đó. Nhưng nếu t
bit được đặt /a
và bạn không sở hữu /a
, thì bạn chỉ có thể làm điều đó nếu bạn sở hữu /a/b
. Điều đó mang lại:
- Nếu bạn sở hữu một thư mục, theo 1, bạn có thể cấp cho mình quyền truy cập ghi và bit t không áp dụng (và dù sao bạn cũng có thể xóa nó), vì vậy bạn có thể xóa / đổi tên / tạo lại bất kỳ tệp hoặc thư mục nào trong đó, vì vậy tất cả các đường dẫn tệp bên dưới là của bạn để viết lại với bất kỳ nội dung.
- Nếu bạn không sở hữu nó nhưng có quyền truy cập ghi, thì:
- Hoặc
t
bit không được đặt và bạn ở trong trường hợp tương tự như trên (tất cả các đường dẫn tệp là của bạn).
- hoặc nó được thiết lập và sau đó bạn không thể sửa đổi các tệp bạn không sở hữu hoặc không có quyền truy cập ghi, vì vậy với mục đích của chúng tôi là tìm đường dẫn tệp bạn có thể sửa đổi, điều đó giống như không có quyền ghi.
Vì vậy, chúng tôi có thể giải quyết tất cả 1, 2, 5 và 6 với:
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
Đó và giải pháp cho 3 và 4 là độc lập, bạn có thể hợp nhất đầu ra của chúng để có được danh sách đầy đủ:
{
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{$_}++'
Cần phải rõ ràng nếu bạn đã đọc mọi thứ cho đến nay, một phần của nó ít nhất chỉ liên quan đến quyền và quyền sở hữu, chứ không phải các tính năng khác có thể cấp hoặc hạn chế quyền truy cập ghi (chỉ đọc FS, ACL, cờ bất biến, các tính năng bảo mật khác ...). Và khi chúng tôi xử lý nó trong một số giai đoạn, một số thông tin đó có thể sai nếu các tệp / thư mục đang được tạo / xóa / đổi tên hoặc quyền / quyền sở hữu của chúng bị sửa đổi trong khi tập lệnh đó đang chạy, như trên một máy chủ tệp bận rộn với hàng triệu tệp .
Ghi chú tính di động
Tất cả mã đó là tiêu chuẩn (POSIX, Unix cho t
bit) ngoại trừ:
-print0
là một phần mở rộng GNU hiện cũng được hỗ trợ bởi một vài triển khai khác. Với các find
triển khai thiếu hỗ trợ cho nó, bạn có thể sử dụng -exec printf '%s\0' {} +
thay thế và thay thế -exec sh -c 'exec find "$@" -print0' sh {} +
bằng -exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +
.
perl
không phải là lệnh do POSIX chỉ định nhưng có sẵn rộng rãi. Bạn cần perl-5.6.0
hoặc ở trên cho -Mfiletest=access
.
zsh
không phải là lệnh do POSIX chỉ định. Đó zsh
mã trên nên làm việc với zsh-3 (1995) trở lên.
sudo
không phải là lệnh do POSIX chỉ định. Mã phải hoạt động với bất kỳ phiên bản nào miễn là cấu hình hệ thống cho phép chạy perl
như người dùng đã cho.