Sự khác biệt giữa `curl | sh` và `sh -c phiên $ (curl) '


23

Một phương pháp cài đặt dễ dàng cho Docker (ví dụ) là:

curl -sSL https://get.docker.com/ | sh

Tuy nhiên, tôi cũng đã thấy một số trông giống như thế này (sử dụng ví dụ Docker):

sh -c "$(curl -sSL https://get.docker.com/)"

Chúng có vẻ giống nhau về mặt chức năng, nhưng có lý do để sử dụng cái này hơn cái kia không? Hay nó chỉ là một sở thích / thẩm mỹ?

(Lưu ý, hãy cẩn thận khi chạy tập lệnh từ nguồn gốc không xác định.)

Câu trả lời:


39

Có một sự khác biệt thực tế.

curl -sSL https://get.docker.com/ | shbắt đầu curlshđồng thời, kết nối đầu ra của curlvới đầu vào của sh. curlsẽ thực hiện với việc tải xuống (đại khái) nhanh nhất shcó thể chạy tập lệnh. Máy chủ có thể phát hiện sự bất thường trong thời gian và tiêm mã độc không thể nhìn thấy khi chỉ cần tải tài nguyên vào tệp hoặc bộ đệm hoặc khi xem nó trong trình duyệt.

Trong sh -c "$(curl -sSL https://get.docker.com/)", curlđược chạy nghiêm ngặt trước khi shchạy. Toàn bộ nội dung của tài nguyên được tải xuống và chuyển đến trình bao của bạn trước khi shbắt đầu. Shell của bạn chỉ bắt đầu shkhi curlđã thoát và chuyển văn bản của tài nguyên cho nó. Máy chủ không thể phát hiện shcuộc gọi; nó chỉ được bắt đầu sau khi kết nối kết thúc. Nó tương tự như tải tập lệnh vào một tập tin đầu tiên.

(Điều này có thể không liên quan trong trường hợp docker, nhưng nó có thể là một vấn đề nói chung và làm nổi bật sự khác biệt thực tế giữa hai lệnh.)


2
Cảm ơn, đây có vẻ như là sự khác biệt quan trọng nhất giữa hai người.
Sarke

Bạn có thể trích dẫn một tài nguyên sao lưu yêu cầu này không? Tôi rất muốn biết làm thế nào máy chủ có thể phát hiện cuộc gọi 'sh'.
Alfred Armstrong

1
@AlfredArmstrong Uhm, tôi đặt một liên kết trên vulnerable to server-side detectioncụm từ. Nó dẫn đến một bài viết trên blog giải thích rất chi tiết về cách họ đạt được nó. TL; DR: đặt một giấc ngủ trong kịch bản của bạn và quan sát sự chậm trễ trong việc tiếp nhận trên máy chủ.
Jonas Schäfer

1
@JonasWielicki cảm ơn - liên kết không rõ ràng - không phải lỗi của bạn, theo CSS của SE tôi nghĩ. Mọi người đang lén lút, phải không? :)
Alfred Armstrong

1
Tất cả điều đó khiến tôi tự hỏi liệu có ai từng cố gắng thực hiện mánh khóe đó trong thực tế mà không bị bắt ngay lập tức. Không phải là nó có thể quan trọng nhiều vì bạn có thể chỉ cần kịch bản làm điều gì đó độc hại ngay cả khi không có những thủ thuật như vậy, và bất cứ ai không đọc nó đầy đủ sẽ dễ bị tổn thương.
ilkkachu

11

Tôi tin rằng chúng thực tế giống hệt nhau. Tuy nhiên, hiếm có trường hợp nào khác nhau.

$(cmd)được thay thế bằng kết quả của cmd. Nếu độ dài của lệnh kết quả đó vượt quá giá trị độ dài đối số tối đa được trả về getconf ARG_MAX, nó sẽ cắt bớt kết quả, điều này có thể dẫn đến kết quả không thể đoán trước.

Tùy chọn đường ống không có giới hạn này. Mỗi dòng đầu ra từ curllệnh sẽ được thực hiện bashkhi nó đến từ đường ống.

Nhưng ARG_MAX thường nằm trong phạm vi 256.000 ký tự. Đối với cài đặt docker, tôi tự tin sử dụng một trong hai phương pháp. :-)


Thú vị, vì vậy có một sự khác biệt. Cảm ơn
Sarke

1
Khi nghi ngờ, sử dụng phương pháp đường ống. Tôi đã không chỉ định một ưu tiên trong câu trả lời của mình, nhưng tôi thích phương pháp đường ống cho loại sử dụng này bởi vì bạn không bao giờ biết có bao nhiêu dữ liệu đi qua đường ống.
Greg Tarsa

2
"Nó sẽ cắt bớt kết quả" - Shell sẽ đưa ra thông báo lỗi cho điều đó, không âm thầm cắt ngắn. Khi kiểm tra, tôi thậm chí còn nhận được một lỗi từ trình bao bên dưới ARG_MAX, bash giới hạn một đối số riêng lẻ ở mức 131072 byte trên hệ thống của tôi, khi getconf ARG_MAXin 2097152. Nhưng dù bằng cách nào, lỗi hoặc cắt ngắn, nó sẽ không hoạt động.
hvd

Nhưng việc triển khai vỏ Bourne có giới hạn thấp hơn nhiều. Trong 4.2BSD, giới hạn là 10240 ký tự và trên các hệ thống trước đó thậm chí còn thấp hơn .. Tất nhiên là 30 năm trước, vì vậy bạn không gặp phải các giới hạn thấp như vậy ngày nay. Nếu tôi nhớ lại một cách chính xác, một số vỏ đầu tiên này đã âm thầm cắt ngắn.
AndyB

Giới hạn 128 kB cho một đối số là một điều của Linux, đó không phải là về Bash.
ilkkachu

8

Trong curl -sSL https://get.docker.com/ | sh:

  • Cả hai lệnh curlshsẽ bắt đầu cùng một lúc trong các ô con tương ứng

  • STDOUT từ curlsẽ được chuyển dưới dạng STDIN tới sh(đây là đường ống, |)

Trong khi đó sh -c "$(curl -sSL https://get.docker.com/)":

  • Việc thay thế lệnh $(), sẽ được thực hiện trước, tức là curlsẽ được chạy trước trong một lớp con

  • Thay thế lệnh $(), sẽ được thay thế bằng STDOUT từcurl

  • sh -c (vỏ không tương tác, không đăng nhập) sẽ thực thi STDOUT từ curl


1
Vì vậy, có sự khác biệt thực sự?
Sarke

@Sarke Vâng, về mặt lý thuyết như tôi đã đề cập, nhưng thực tế hầu như không đáng chú ý. (Sẽ có hiệu ứng rõ ràng nếu bạn không thay thế lệnh.)
heemayl

1
@Sarke, nếu các tập lệnh không được tải xuống hoàn toàn, bạn sẽ không nhất thiết phải chú ý đến nó bằng đường ống. Quá trình nhận được đường ống để có thể bỏ qua tín hiệu đó.
Janus Troelsen

@JanusTroelsen bạn có ý gì khi không tải xuống hoàn toàn? Tại sao điều đó xảy ra ngoại trừ một lỗi máy chủ trong trường hợp đó sẽ không có bất cứ điều gì dẫn đến sh.
hasufell

Hoặc gián đoạn kết nối ... có rất nhiều cách chuyển có thể thất bại
Janus Troelsen

0

Một điểm khác biệt giữa hai câu hỏi (được lấy từ các câu trả lời khác trên web) là nếu bạn không tải xuống toàn bộ tập lệnh cùng một lúc, nó có thể cắt ngang một nửa đoạn mã tại một điểm không xác định và thay đổi ý nghĩa của lệnh thành Thực thi. Vì vậy, có vẻ như đầu tiên tải xuống toàn bộ tập tin và sau đó đánh giá nó sẽ tốt hơn.

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.