Làm thế nào tôi có thể đảm bảo một cách an toàn một biến chỉ chứa một tên tệp hợp lệ?


8

Đưa ra tập lệnh bên dưới, làm cách nào tôi có thể đảm bảo rằng đối số chỉ chứa tên tệp hợp lệ bên trong /home/charlesingalls/chứ không phải đường dẫn ( ../home/carolineingalls/) hoặc ký tự đại diện, v.v.?

Tôi chỉ muốn tập lệnh có thể xóa một tập tin từ thư mục được mã hóa cứng. Kịch bản này sẽ chạy như một người dùng đặc quyền.

#!/bin/bash

rm -f /home/charlesingalls/"$1"

2
Nếu bạn không muốn hỗ trợ "foo / bar", chỉ cần kiểm tra xem nó không chứa /. Ký tự đại diện không được giải thích bên trong dấu ngoặc kép.
Random832

3
Nếu bạn đang xóa một tập tin , đừng sử dụng -rvới rm. rm -rlà để xóa đệ quy của một thư mục và tất cả các tệp và thư mục bên dưới nó. Nó chỉ hữu ích khi xóa các thư mục. Nói chung, không sùng bái hàng hóa. tức là không chỉ sao chép những thứ có vẻ hữu ích vào dòng lệnh hoặc tập lệnh của bạn mà không hiểu chúng làm gì hoặc chúng hoạt động như thế nào. Các vị thần máy bay mang hàng hóa ma thuật có thể tức giận và xóa tất cả các tập tin của bạn.
cas

Điểm hay về cờ -r - Tôi thực sự hiểu công dụng của nó, chỉ là không suy nghĩ rõ ràng.
Aaron Cicali

Câu trả lời:


7

Nếu bạn chỉ muốn xóa một tệp trong /home/charlesingalls(chứ không phải một tệp trong thư mục con) thì thật dễ dàng: chỉ cần kiểm tra xem đối số không chứa a /.

case "$1" in
  */*) echo 1>&2 "Refusing to remove a file in another directory"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Điều này chạy rmngay cả khi đối số là .hoặc ..trống, nhưng trong trường hợp đó rmsẽ vô tình không thể xóa một thư mục.

Ký tự đại diện không liên quan ở đây vì không có mở rộng ký tự đại diện được thực hiện.

Điều này an toàn ngay cả khi có các liên kết tượng trưng: nếu tệp là liên kết tượng trưng, ​​liên kết tượng trưng (nằm trong /home/charlesingalls) sẽ bị xóa và mục tiêu của liên kết đó không bị ảnh hưởng.

Lưu ý rằng điều này giả định rằng /home/charlesingallskhông thể di chuyển hoặc thay đổi. Điều đó sẽ ổn nếu thư mục được mã hóa cứng trong tập lệnh, nhưng nếu nó được xác định từ các biến thì việc xác định có thể không còn hiệu lực theo thời gian rmlệnh chạy.

Dựa trên thông tin bổ sung rằng đối số là tên máy chủ ảo, bạn nên thực hiện danh sách trắng thay vì đưa vào danh sách đen: kiểm tra xem tên đó có phải là tên máy chủ ảo hợp lý không, thay vì chỉ cấm các dấu gạch chéo. Tôi kiểm tra xem tên bắt đầu bằng chữ cái thường hay chữ số và nó không chứa các ký tự khác với chữ cái thường, chữ số, dấu chấm và dấu gạch ngang.

LC_CTYPE=C LC_COLLATE=C
case "$1" in
  *[!-.0-9a-z]*|[!0-9a-z]*) echo >&2 "Invalid host name"; exit 2;;
  *) rm -f /home/charlesingalls/"$1";;
esac

Trong trường hợp này, các tệp là các tệp cấu hình máy chủ web đã được tạo bởi quy trình trước đó. Tất cả chúng đều có cùng mức độ quan trọng (mỗi cái tạo thành một máy chủ ảo). Tuy nhiên, thư mục họ tồn tại liền kề với các thư mục tương tự và tất cả chúng đều tồn tại dưới một thư mục thuộc sở hữu của máy chủ web. Kịch bản cụ thể này có nghĩa là cho phép xóa các cấu hình máy chủ ảo trong một thư mục nhất định.
Aaron Cicali

phương pháp này có ý nghĩa với bạn với điều kiện trường hợp sử dụng không? Tôi đánh giá cao trải nghiệm của bạn và sẽ thừa nhận chắc chắn đã có lần tôi "mang bazooka đến đấu súng" để tự động hóa một cái gì đó trong linux.
Aaron Cicali

@AaronCicali Tại sao tập lệnh có thể xóa bất kỳ cấu hình máy chủ nào và không chỉ một cấu hình thuộc về thực thể đã thực hiện yêu cầu ban đầu? Tại sao tên máy chủ ảo không được xác thực trước tiên (sau đó nó sẽ không chứa bất kỳ ký tự đặc biệt nào)?
Gilles

Thực thể thực hiện yêu cầu ban đầu là GUI thực sự có quyền xóa bất kỳ máy chủ ảo nào trong thư mục đó. Tên máy chủ ảo được xác thực đầu tiên. Nó xuất phát từ một danh sách các máy chủ ảo được tạo trước đó (tên miền). Tôi tin rằng đây là công việc của tập lệnh này để đảm bảo rằng nó an toàn nhất có thể mà không cần dựa vào tính bảo mật của một phần khác của ứng dụng. Cụ thể, nó chỉ có thể xóa các tập tin từ trong thư mục cụ thể này. Nó cũng sẽ cần phải làm một số công việc dọn dẹp bổ sung.
Aaron Cicali

@AaronCicali Ok, như một sự tỉnh táo bổ sung kiểm tra điều này có ý nghĩa. Trong trường hợp này, bạn nên đưa danh sách trắng: chỉ chấp nhận các tên trông giống như tên máy chủ hợp lý. Nếu bạn không cho phép tên miền phụ, bạn thậm chí có thể cấm.
Gilles

10

Câu trả lời này giả định rằng $1được phép bao gồm các thư mục con. Nếu bạn quan tâm đến trường hợp đơn giản hơn $1là tên thư mục đơn giản, thì hãy xem một trong những câu trả lời khác.


Ký tự đại diện không được mở rộng khi trong dấu ngoặc kép. Vì $1là trong dấu ngoặc kép, ký tự đại diện không phải là vấn đề.

Cả hai ../và liên kết tượng trưng có thể che khuất vị trí thực sự của một tập tin. Dưới đây là các thử nghiệm để xác định xem tập tin có thực sự, không chỉ là dường như, theo con đường chúng ta muốn.

Hệ thống mới hơn: sử dụng realpath

Đối với việc tìm hiểu xem tập tin có thực sự nếu tập tin thực sự nằm dưới /home/charlesingalls/hay không, bạn có thể sử dụng realpath:

realpath --relative-base=/home/charlesingalls/ "/home/charlesingalls/$1"  | grep -q '^/' && exit 1

Ở trên chạy exit 1nếu tập tin được chỉ định bởi $1bất cứ nơi nào khác ngoài thư mục /home/charlesingalls/. realpathchuẩn hóa toàn bộ đường dẫn, loại bỏ cả symlink và ../.

realpath là một phần của lõi GNU và nên có sẵn trên bất kỳ hệ thống Linux nào.

realpathyêu cầu GNU coreutils 8.15 (tháng 1 năm 2012) hoặc tốt hơn .

Ví dụ

Để giải thích cách realpath theo sau ../để xác định vị trí thực của tệp (ví dụ: -qtùy chọn grep được bỏ qua để hiển thị đầu ra thực tế của grep):

$ touch /tmp/test
$ realpath --relative-base=$HOME "$HOME/../../tmp/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Để chứng minh làm thế nào nó theo symlink:

$ ln -s /tmp/test ~/test
$ realpath --relative-base=$HOME "$HOME/test" | grep '^/' && echo FAIL
/tmp/test
FAIL

Hệ thống cũ hơn: sử dụng readlink -e

readlinkcũng có khả năng cấu hình hóa một đường dẫn, theo cả hai liên kết tượng trưng và ../:

readlink -e "$HOME/test" | grep -q "^$HOME" || exit 1

Sử dụng cùng một tệp ví dụ:

$ readlink -e "$HOME/../../tmp/test" | grep "$HOME" || echo FAIL
FAIL
$ readlink -e "$HOME/test" | grep "^$HOME" || echo FAIL
FAIL

Ngoài việc có sẵn trên các hệ thống GNU cũ hơn, các phiên bản readlinkcó sẵn trên BSD.


Ubuntu 14.04 của coreutilstôi không córealpath
heemayl

1
Đây dường như là nhiều hơn những gì tôi đang tìm kiếm, nhưng thật không may, máy chủ Centos 6.5 của tôi không có thông tin thực sự. Tôi không ở vị trí để cài đặt nó. Googling bật lên đề cập đến giao diện thay thế "readlink -f", nhưng tôi vẫn chưa làm cho nó hoạt động.
Aaron Cicali

1
Phụ đề đề cập -f("tất cả trừ thành phần cuối cùng phải tồn tại") và các ví dụ sử dụng -e("tất cả các thành phần phải tồn tại"), điều này hơi khó hiểu.
isanae

2
@AaronCicali Đây chắc chắn không phải là câu trả lời bạn cần, vì nó sai một cách nguy hiểm. Bạn không được giải quyết các liên kết tượng trưng . Mục tiêu của liên kết tượng trưng có thể thay đổi giữa thời gian bạn kiểm tra và thời gian bạn sử dụng nó. (Đây là một loại lỗi thiết kế nổi tiếng ). Bên cạnh đó, sẽ không có ý nghĩa gì khi giải quyết các liên kết tượng trưng vì rmhành động trên chính đối số, chứ không phải mục tiêu của nó.
Gilles

1
Gợi ý: Sử dụng grep -qđể tránh grepxuất ra các dòng khớp. Bạn vẫn nhận được trạng thái thoát như vậy &&||vẫn hoạt động chính xác như bạn đã quen.
một CVn

2

Nếu bạn muốn cấm hoàn toàn các đường dẫn, cách đơn giản nhất là kiểm tra xem biến có chứa dấu gạch chéo ( /) không. Trong bash:

if [[ "$1" = */* ]] ; then...

Điều này sẽ chặn tất cả các đường dẫn, mặc dù, bao gồm foo/bar. Bạn có thể kiểm tra ..thay thế, nhưng điều đó sẽ để lại khả năng các liên kết tượng trưng trỏ đến các thư mục bên ngoài đường dẫn đích.

Nếu bạn chỉ muốn cho phép xóa một tệp duy nhất, tôi không nghĩ bạn nên sử dụng rm -r.


Ngoài ra, tùy thuộc vào những gì bạn đang làm, bạn có thể sử dụng quyền truy cập tệp của hệ thống để chỉ cho phép xóa các tệp mà người dùng có thể tự xóa. Một cái gì đó như thế này:

su charlesingalls -c "rm /home/charlesingalls/'$1'"

Mặc dù như @Gilles đã nhận xét, nhưng điều này có một vấn đề trích dẫn: nó sẽ thất bại nếu $1chứa một trích dẫn duy nhất, do đó, biến này phải được kiểm tra trước đó (ví dụ if [[ "$1" = *\'* ]] ; then fail...hoặc bằng cách đưa vào danh sách các ký tự hợp lý) hoặc tên tệp được chuyển qua một biến môi trường với vd

file="$1" su charlesingalls -c 'rm "/home/charlesingalls/$file"'

Điểm hay về cờ -r, điều đó thực sự không nên có. Xóa ngay bây giờ ...
Aaron Cicali

Lưu ý rằng sulệnh của bạn bị hỏng vì trích dẫn là sai. Bạn đang thực thi một lệnh với đối số được nội suy dưới dạng một đoạn mã shell. Ví dụ, nếu đối số là $(touch foo)mã của bạn chạy touch foo.
Gilles

@Gilles, tào lao, tôi biết phải có một cái gì đó sai. trích dẫn lồng nhau không phải là người bạn yêu thích của tôi. Tôi nghĩ rằng phiên bản hiện tại hoạt động tốt hơn, dấu ngoặc kép sẽ đánh giá biến ở vỏ ngoài và dấu ngoặc đơn giữ cho nó không bị đánh giá ở lớp vỏ bên trong. Không bảo hành.
ilkkachu

@ilkkachu Phiên bản đó không thành công nếu đối số chứa một trích dẫn. (Nhưng chỉ trong trường hợp đó, điều này giúp xác thực dễ dàng hơn rất nhiều so với khi sử dụng dấu ngoặc kép.) Không thể nội suy trực tiếp một chuỗi tùy ý. Bạn cần phải xoa bóp chuỗi (ví dụ thay thế tất cả 'bằng '\'') hoặc chuyển nó qua một kênh khác như biến môi trường (đó là những gì tôi sẽ làm ở đây file_to_remove="$1" su -c 'rm "/home/charlesingalls/$file_to_remove"':). Hóa ra tên tệp được cho là tên máy chủ ảo, vì vậy từ chối tất cả các ký tự đặc biệt cũng sẽ ổn ở đây.
Gilles
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.