Viking chạy bất kỳ lệnh nào sẽ truyền dữ liệu không đáng tin cậy cho các lệnh diễn giải các đối số là các lệnh


18

Từ hướng dẫn của findutils:

Ví dụ các cấu trúc như hai lệnh này

# risky
find -exec sh -c "something {}" \;
find -execdir sh -c "something {}" \;

rất nguy hiểm Lý do cho điều này là '{}' được mở rộng thành tên tệp có thể chứa dấu chấm phẩy hoặc các ký tự khác đặc biệt cho trình bao. Ví dụ, nếu ai đó tạo tệp /tmp/foo; rm -rf $HOMEthì hai lệnh trên có thể xóa thư mục chính của ai đó.

Vì vậy, vì lý do này, không chạy bất kỳ lệnh nào sẽ truyền dữ liệu không đáng tin cậy (chẳng hạn như tên của fi les) cho các lệnh diễn giải các đối số dưới dạng các lệnh sẽ được diễn giải thêm (ví dụ 'sh').

Trong trường hợp của vỏ, có một cách giải quyết thông minh cho vấn đề này:

# safer
find -exec sh -c 'something "$@"' sh {} \;
find -execdir sh -c 'something "$@"' sh {} \;

Cách tiếp cận này không được đảm bảo để tránh mọi vấn đề, nhưng nó an toàn hơn nhiều so với việc thay thế dữ liệu của sự lựa chọn của kẻ tấn công vào văn bản của lệnh shell.

  1. Là nguyên nhân của vấn đề trong find -exec sh -c "something {}" \;đó thay thế cho không {}được trích dẫn và do đó không được coi là một chuỗi?
  2. Trong giải pháp find -exec sh -c 'something "$@"' sh {} \;,

    • đầu tiên {}được thay thế, nhưng vì không {}được trích dẫn, nên "$@"cũng không có vấn đề tương tự như lệnh ban đầu? Ví dụ, "$@"sẽ được mở rộng để "/tmp/foo;", "rm", "-rf", và "$HOME"?

    • Tại sao {}không thoát hoặc trích dẫn?

  3. Bạn có thể đưa ra các ví dụ khác (vẫn có sh -choặc không có nếu có thể, có hoặc không findcó thể không cần thiết) khi áp dụng cùng loại vấn đề và giải pháp, và đó là những ví dụ tối thiểu để chúng tôi có thể tập trung vào vấn đề và giải pháp với ít phân tâm nhất có thể? Xem các cách để cung cấp các đối số cho một lệnh được thực thi bởi `bash -c`

Cảm ơn.


Câu trả lời:


29

Điều này không thực sự liên quan đến trích dẫn, mà là xử lý đối số.

Hãy xem xét ví dụ rủi ro:

find -exec sh -c "something {}" \;
  • Đây được phân tách bằng vỏ, và chia thành sáu chữ: find, -exec, sh, -c, something {}(không có dấu ngoặc kép nữa), ;. Không có gì để mở rộng. Shell chạy findvới sáu từ đó như là đối số.

  • Khi findphát hiện một cái gì đó để xử lý, chẳng hạn foo; rm -rf $HOME, nó sẽ thay thế {}với foo; rm -rf $HOME, và chạy shvới các đối số sh, -csomething foo; rm -rf $HOME.

  • shbây giờ nhìn thấy -cvà như là một kết quả phân tích cú pháp something foo; rm -rf $HOME( đối số không phải tùy chọn đầu tiên ) và thực hiện kết quả.

Bây giờ hãy xem xét các biến thể an toàn hơn:

find -exec sh -c 'something "$@"' sh {} \;
  • Chạy shell findvới các đối số find, -exec, sh, -c, something "$@", sh, {}, ;.

  • Bây giờ khi findphát hiện foo; rm -rf $HOME, nó sẽ thay thế {}một lần nữa, và chạy shvới các đối số sh, -c, something "$@", sh, foo; rm -rf $HOME.

  • shxem -cvà phân tích cú pháp something "$@"như lệnh để chạy shfoo; rm -rf $HOMEnhư là các tham số vị trí ( bắt đầu từ$0 ), mở rộng "$@"thành foo; rm -rf $HOME một giá trị duy nhất và chạy somethingvới đối số duy nhất foo; rm -rf $HOME.

Bạn có thể thấy điều này bằng cách sử dụng printf. Tạo một thư mục mới, nhập nó và chạy

touch "hello; echo pwned"

Chạy biến thể đầu tiên như sau

find -exec sh -c "printf \"Argument: %s\n\" {}" \;

sản xuất

Argument: .
Argument: ./hello
pwned

trong khi biến thể thứ hai, chạy như

find -exec sh -c 'printf "Argument: %s\n" "$@"' sh {} \;

sản xuất

Argument: .
Argument: ./hello; echo pwned

Trong biến thể an toàn hơn, tại sao {}không thoát hoặc trích dẫn?
Tim

1
Nó thường không cần phải được trích dẫn; nó chỉ cần được trích dẫn trong một cái vỏ trong đó nó có một số ý nghĩa khác, và tôi không nghĩ đó là trường hợp với bất kỳ loại vỏ chính nào được sử dụng hiện nay.
Stephen Kitt

Từ gnu.org/software/findutils/manual/html_mono/ khuyên : "Cả hai công trình này ( ;{}) cần phải được thoát (với một '\') hoặc được trích dẫn để bảo vệ chúng khỏi sự mở rộng của vỏ." Bạn có nghĩa là nó không chính xác cho bash?
Tim

3
Tôi có nghĩa là nó không chính xác cho vỏ nói chung. Xem POSIX . Tuyên bố cụ thể đó trong findutilshướng dẫn sử dụng có từ ít nhất là năm 1996 ... Tất nhiên không có hại gì khi trích dẫn {}, dù là '{}'hay "{}", nhưng nó không cần thiết.
Stephen Kitt

@Tim Bạn đang gặp một tình huống phổ biến khi trực giác của bạn chưa tính đến thực tế là có hai loại xử lý đối số rất khác nhau đang diễn ra ở đây: khi / cách trình bao phân tách các đối số ra khỏi một dòng văn bản (mà là nơi trích dẫn các vấn đề) khác với việc chuyển đối số của hệ điều hành thô (trong đó các trích dẫn chỉ là các ký tự thông thường). Trong lệnh shell find some/path -exec sh -c 'something "$@"' {} \;thực sự có ba lớp xử lý / chuyển đối số, hai trong số các loại shell và một trong các loại hệ điều hành thô cơ bản.
mtraceur

3

Phần 1:

find chỉ sử dụng thay thế văn bản.

Có, nếu bạn đã làm nó không được trích dẫn, như thế này:

find . -type f -exec sh -c "echo {}" \;

và kẻ tấn công đã có thể tạo một tệp được gọi ; echo owned, sau đó nó sẽ thực thi

sh -c "echo ; echo owned"

Điều đó sẽ dẫn đến việc vỏ chạy echosau đó echo owned.

Nhưng nếu bạn đã thêm dấu ngoặc kép, kẻ tấn công có thể kết thúc dấu ngoặc kép của bạn sau đó đặt lệnh độc hại sau nó bằng cách tạo một tệp có tên '; echo owned:

find . -type f -exec sh -c "echo '{}'" \;

mà sẽ dẫn đến việc chạy vỏ echo '', echo owned.

(nếu bạn hoán đổi dấu ngoặc kép cho dấu ngoặc đơn, kẻ tấn công cũng có thể sử dụng loại dấu ngoặc kép khác.)


Phần 2:

Trong find -exec sh -c 'something "$@"' sh {} \;, {}ban đầu không được giải thích bởi shell, nó được thực thi trực tiếp với execve, vì vậy việc thêm các trích dẫn shell sẽ không giúp ích gì.

find -exec sh -c 'something "$@"' sh "{}" \;

không có tác dụng, vì vỏ tách các dấu ngoặc kép trước khi chạy find.

find -exec sh -c 'something "$@"' sh "'{}'" \;

thêm trích dẫn rằng shell không xử lý đặc biệt, vì vậy trong hầu hết các trường hợp, điều đó chỉ có nghĩa là lệnh sẽ không làm những gì bạn muốn.

Có nó mở rộng ra /tmp/foo;, rm, -rf, $HOMEkhông phải là một vấn đề, bởi vì đó là những lập luận để something, và somethingcó lẽ không đối xử với đối số của nó là lệnh để thực thi.


Phần 3:

Tôi giả sử các cân nhắc tương tự áp dụng cho bất kỳ thứ gì lấy đầu vào không tin cậy và chạy nó dưới dạng (một phần) của một lệnh, ví dụ xargsparallel.


xargschỉ nguy hiểm nếu -Ihoặc -Jđược sử dụng. Trong hoạt động bình thường, nó chỉ nối các đối số vào cuối danh sách, giống như -exec ... {} +vậy.
Charles Duffy

1
( parallel, ngược lại, chạy shell theo mặc định mà không có yêu cầu rõ ràng từ người dùng, làm tăng bề mặt dễ bị tổn thương; đây là vấn đề được thảo luận nhiều; xem unix.stackexchange.com/questions/349483/ . và liên kết đi kèm đến danh sách.gnu.org / archive / html / rọ-vô 2015/2015 / msg00005.html ).
Charles Duffy

@CharlesDuffy Ý bạn là " giảm bề mặt dễ bị tổn thương". Cuộc tấn công được minh họa bởi OP thực sự không hoạt động theo mặc định với GNU Parallel.
Ole Tange

1
Có, tôi biết bạn cần nó để tương đương trên SSH - nếu bạn không sinh ra parallelquy trình "người nhận" từ xa ở đầu kia của bất kỳ ổ cắm nào, có thể thực hiện không cần trình bao trực tiếp execv. Đó chính xác là những gì tôi sẽ làm, nếu trong đôi giày của bạn thực hiện công cụ. Có một chương trình không tương tác hoạt động khác nhau dựa trên giá trị hiện tại SHELLlà hầu như không gây ngạc nhiên.
Charles Duffy

1
Có - Tôi cho rằng các đường ống và chuyển hướng sẽ không thể thực hiện được trừ khi người dùng khởi động một cách rõ ràng một vỏ, tại thời điểm đó họ chỉ nhận được hành vi rõ ràng của vỏ mà họ bắt đầu rõ ràng. Nếu người ta chỉ có thể mong đợi những gì một người execvcung cấp, điều đó đơn giản và ít gây ngạc nhiên hơn, và một quy tắc không thay đổi giữa các địa điểm / môi trường thời gian chạy.
Charles Duffy

3

1. Là nguyên nhân của vấn đề trong find -exec sh -c "something {}" \;đó thay thế cho không {}được trích dẫn và do đó không được coi là một chuỗi?

Trong một ý nghĩa, nhưng trích dẫn không thể giúp đỡ ở đây . Tên tệp được thay thế {}có thể chứa bất kỳ ký tự nào , kể cả dấu ngoặc kép . Bất kỳ hình thức trích dẫn nào đã được sử dụng, tên tệp có thể chứa cùng và "thoát ra" trong trích dẫn.

2. ... nhưng vì không {}được trích dẫn, nên "$@"cũng không có vấn đề tương tự như lệnh ban đầu? Chẳng hạn, "$@"sẽ được mở rộng sang "/tmp/foo;", "rm", "-rf", and "$HOME"?

Số "$@"mở rộng cho các tham số vị trí, dưới dạng các từ riêng biệt và không phân tách chúng thêm nữa. Ở đây, {}là một đối số cho findchính nó và findchuyển tên tệp hiện tại cũng là một đối số riêng biệt sh. Nó có sẵn trực tiếp như một biến trong tập lệnh shell, nó không được xử lý như chính lệnh shell.

... tại sao {}không thoát hoặc trích dẫn?

Nó không cần phải, trong hầu hết các vỏ. Nếu bạn chạy fish, nó cần phải: fish -c 'echo {}'in một dòng trống. Nhưng nó không thành vấn đề nếu bạn trích dẫn nó, shell sẽ chỉ xóa các trích dẫn.

3. Bạn có thể cho ví dụ khác ...

Bất cứ khi nào bạn mở rộng tên tệp (hoặc một chuỗi không được kiểm soát khác) như bên trong một chuỗi được coi là một loại mã (*) , có khả năng thực thi lệnh tùy ý.

Ví dụ, điều này mở rộng $ftrực tiếp mã Perl và sẽ gây ra sự cố nếu tên tệp chứa dấu ngoặc kép. Báo giá trong tên tệp sẽ kết thúc trích dẫn trong mã Perl và phần còn lại của tên tệp có thể chứa bất kỳ mã Perl nào:

touch '"; print "HELLO";"'
for f in ./*; do
    perl -le "print \"size: \" . -s \"$f\""
done

(Tên tệp phải hơi lạ vì Perl phân tích toàn bộ mã trước, trước khi chạy bất kỳ mã nào. Vì vậy, chúng tôi sẽ phải tránh lỗi phân tích cú pháp.)

Trong khi điều này vượt qua nó một cách an toàn thông qua một đối số:

for f in ./*; do
    perl -le 'print "size: " . -s $ARGV[0]' "$f"
done

(Không có nghĩa gì khi chạy một vỏ khác trực tiếp từ vỏ, nhưng nếu bạn làm vậy, nó tương tự như find -exec sh ...trường hợp)

(* một số loại mã bao gồm SQL, do đó, XKCD bắt buộc: https://xkcd.com/327/ giải thích cộng: https://www.explainxkcd.com/wiki/index.php/Little_Bulk_Tables )


1

Mối quan tâm của bạn chính xác là lý do tại sao GNU Parallel trích dẫn đầu vào:

touch "hello; echo pwned"
find . -print0 | parallel -0 printf \"Argument: %s\\n\" {}

Điều này sẽ không chạy echo pwned.

Nó sẽ chạy shell, do đó, nếu bạn mở rộng lệnh của mình, bạn sẽ không bất ngờ:

# This _could_ be run without spawining a shell
parallel "grep -E 'a|bc' {}" ::: foo
# This cannot
parallel "grep -E 'a|bc' {} | wc" ::: foo

Để biết thêm chi tiết về vấn đề sinh sản, hãy xem: https://www.gnu.org/software/abul/abul_design.html#Always-rucky-commands-in-a-shell


1
... Điều đó nói rằng, find . -print0 | xargs -0 printf "Argument: %s\n"cũng an toàn (hay đúng hơn là, vì với -print0một người xử lý tên tập tin với dòng mới chính xác); trích dẫn song song là một cách giải quyết cho một vấn đề hoàn toàn không tồn tại khi không có vỏ.
Charles Duffy

Nhưng ngay khi bạn thêm | wcvào lệnh, bạn cần phải nhảy qua các vòng để làm cho nó an toàn. GNU Parallel theo mặc định là an toàn.
Ole Tange

ITYM: nó cố gắng bao quát mọi vấn đề của ngôn ngữ shell bằng quy trình phức tạp để trích dẫn cẩn thận mọi thứ. Điều đó gần như không an toàn như lưu trữ dữ liệu đúng cách trong các biến, không được trộn lẫn với mã. Bây giờ, nếu nó tự động chuyển đổi nó foo {} | wcthành sh -c 'foo "$1" | wcsh {} `, thì điều đó có thể khác.
ilkkachu
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.