“ เรียกใช้คำสั่งใด ๆ ซึ่งจะส่งผ่านข้อมูลที่ไม่น่าเชื่อถือไปยังคำสั่งที่ตีความข้อโต้แย้งเป็นคำสั่ง”


18

จากคู่มือของ findutils:

ตัวอย่างเช่นการสร้างเช่นสองคำสั่งเหล่านี้

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

อันตรายมาก เหตุผลนี้คือ '{}' ถูกขยายเป็นชื่อไฟล์ซึ่งอาจมีเครื่องหมายอัฒภาคหรืออักขระอื่น ๆ ที่พิเศษสำหรับเชลล์ หากตัวอย่างมีคนสร้างไฟล์ /tmp/foo; rm -rf $HOMEคำสั่งสองคำสั่งด้านบนสามารถลบโฮมไดเร็กตอรี่ของใครบางคนได้

ดังนั้นด้วยเหตุนี้จึงไม่เรียกใช้คำสั่งใด ๆ ซึ่งจะส่งผ่านข้อมูลที่ไม่น่าเชื่อถือ (เช่นชื่อของไฟล์ les) ไปยังคำสั่งที่ตีความอาร์กิวเมนต์เป็นคำสั่งที่จะตีความเพิ่มเติม (เช่น 'sh')

ในกรณีของเชลล์มีวิธีแก้ไขปัญหาที่ฉลาดสำหรับปัญหานี้:

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

วิธีการนี้ไม่รับประกันว่าจะหลีกเลี่ยงทุกปัญหา แต่จะปลอดภัยกว่าการแทนที่ข้อมูลของตัวเลือกของผู้โจมตีในข้อความของคำสั่งเชลล์

  1. เป็นสาเหตุของปัญหาที่เกิดขึ้นในfind -exec sh -c "something {}" \;การทดแทน{}นั้นไม่มีการอ้างอิงและไม่ถือว่าเป็นสตริงเดี่ยวหรือไม่?
  2. ในการแก้ปัญหาfind -exec sh -c 'something "$@"' sh {} \;,

    • แรก{}จะถูกแทนที่ แต่เนื่องจาก{}ไม่ได้กล่าวถึง"$@"ก็ไม่มีปัญหาเช่นเดียวกับคำสั่งเดิม? ยกตัวอย่างเช่น "$@"จะขยายไป"/tmp/foo;", "rm", "-rf"และ "$HOME"?

    • เหตุใดจึง{}ไม่หนีหรืออ้างถึง

  3. คุณสามารถยกตัวอย่างอื่น ๆ (ยังมีsh -cหรือไม่มีก็ได้ถ้ามีหรือไม่มีfindซึ่งอาจไม่จำเป็น) ที่มีปัญหาและวิธีแก้ไขปัญหาแบบเดียวกันและเป็นตัวอย่างที่น้อยที่สุดเพื่อให้เราสามารถมุ่งเน้นไปที่ปัญหาและวิธีแก้ปัญหาด้วย สิ่งที่ทำให้ไขว้เขวน้อยที่สุดที่เป็นไปได้? ดูวิธีในการจัดเตรียมอาร์กิวเมนต์สำหรับคำสั่งที่ดำเนินการโดย `bash -c`

ขอบคุณ


ที่เกี่ยวข้อง: unix.stackexchange.com/questions/156008
Kusalananda

คำตอบ:


29

สิ่งนี้ไม่เกี่ยวข้องกับการอ้างถึงจริงๆ แต่เป็นการโต้แย้งการประมวลผล

พิจารณาตัวอย่างที่มีความเสี่ยง:

find -exec sh -c "something {}" \;
  • นี้จะแยกจากเปลือกและแยกออกเป็นหกคำ: find, -exec, sh, -c, something {}(ไม่มีคำพูดใด ๆ ;เพิ่มเติม) ไม่มีอะไรจะขยาย เชลล์รันfindด้วยคำทั้งหกเป็นอาร์กิวเมนต์

  • เมื่อfindพบบางสิ่งบางอย่างที่จะดำเนินการพูดfoo; rm -rf $HOMEจะแทนที่{}ด้วยfoo; rm -rf $HOMEและวิ่งshกับข้อโต้แย้งsh, และ-csomething foo; rm -rf $HOME

  • shตอนนี้เห็น-cและเป็นผลแยกวิเคราะห์something foo; rm -rf $HOME( อาร์กิวเมนต์ที่ไม่ใช่ตัวเลือกแรก ) และดำเนินการผล

พิจารณาตัวแปรที่ปลอดภัยกว่า:

find -exec sh -c 'something "$@"' sh {} \;
  • เปลือกวิ่งfindกับข้อโต้แย้งfind, -exec, sh, -c, something "$@", sh, ,{};

  • ตอนนี้เมื่อfindพบfoo; rm -rf $HOMEจะแทนที่{}อีกครั้งและวิ่งshกับข้อโต้แย้งsh, -c, something "$@", ,shfoo; rm -rf $HOME

  • shเห็น-cและจะแยกวิเคราะห์something "$@"เป็นคำสั่งในการทำงานและshและfoo; rm -rf $HOMEเป็นพารามิเตอร์ตำแหน่ง ( เริ่มต้นจาก$0 ) ขยาย"$@"ไปfoo; rm -rf $HOME เป็นค่าเดียวและวิ่ง ด้วยอาร์กิวเมนต์เดียวsomethingfoo; rm -rf $HOME

printfคุณสามารถดูนี้โดยใช้ สร้างไดเรกทอรีใหม่ป้อนและเรียกใช้

touch "hello; echo pwned"

ใช้ตัวแปรแรกดังนี้

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

ผลิต

Argument: .
Argument: ./hello
pwned

ในขณะที่ตัวแปรที่สองทำงานเป็น

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

ผลิต

Argument: .
Argument: ./hello; echo pwned

ในตัวแปรที่ปลอดภัยกว่าทำไมจึง{}ไม่หนีหรืออ้างถึง
ทิม

1
โดยทั่วไปไม่จำเป็นต้องเสนอราคา มันจะต้องอ้างถึงในเชลล์ที่มีความหมายอื่น ๆ อยู่เท่านั้นและฉันไม่คิดว่าเป็นเช่นนั้นกับเชลล์หลักใด ๆ ที่ใช้อยู่ในปัจจุบัน
สตีเฟ่น Kitt

จากgnu.org/software/findutils/manual/html_mono/… : "สิ่งก่อสร้างทั้งสองนี้ (;และ{}) ต้องได้รับการหลบหนี (ด้วย '\') หรืออ้างเพื่อปกป้องพวกมันจากการขยายตัวของเชลล์" คุณหมายถึงว่ามันไม่ถูกต้องสำหรับทุบตี?
ทิม

3
ฉันหมายถึงว่ามันไม่ถูกต้องสำหรับกระสุนทั่วไป ดูPOSIX ข้อความเฉพาะในfindutilsคู่มือนั้นย้อนกลับไปอย่างน้อยปี 1996 ... แน่นอนว่ามันไม่เป็นอันตรายต่อการอ้างอิง{}ไม่ว่าจะเป็น'{}'หรือ"{}"แต่ไม่จำเป็น
สตีเฟ่น Kitt

@Tim คุณกำลังประสบกับสถานการณ์ทั่วไปที่สัญชาตญาณของคุณยังไม่ได้คำนึงถึงข้อเท็จจริงที่ว่ามีการประมวลผลการโต้เถียงสองประเภทที่แตกต่างกันเกิดขึ้นที่นี่: เมื่อ / เชลล์ทำการแยกวิเคราะห์อาร์กิวเมนต์ของบรรทัดข้อความ (ซึ่ง เป็นที่ที่การอ้างถึงเรื่องต่าง ๆ ) นั้นแตกต่างจากการโต้แย้งผ่านระบบปฏิบัติการ raw (โดยที่เครื่องหมายคำพูดเป็นเพียงอักขระปกติ) ในคำสั่งเชลล์find some/path -exec sh -c 'something "$@"' {} \;มีสามชั้นของการประมวลผลอาร์กิวเมนต์ / การผ่านสองความหลากหลายของเชลล์และหนึ่งในความหลากหลายของระบบปฏิบัติการพื้นฐานดิบ
mtraceur

3

ส่วนที่ 1:

find เพียงแค่ใช้การแทนที่ข้อความ

ใช่ถ้าคุณทำมันไม่ได้ยกตัวอย่างเช่นนี้:

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

และผู้โจมตีสามารถสร้างไฟล์ที่เรียกว่า; echo ownedจากนั้นมันจะดำเนินการ

sh -c "echo ; echo owned"

ซึ่งจะส่งผลในเปลือกทำงานแล้วechoecho owned

แต่ถ้าคุณเพิ่มเครื่องหมายคำพูดผู้โจมตีสามารถจบเครื่องหมายคำพูดของคุณจากนั้นให้ใส่คำสั่งที่เป็นอันตรายหลังจากนั้นโดยสร้างไฟล์ชื่อ'; echo owned:

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

ซึ่งจะทำให้เชลล์ทำงานecho '', echo owned.

(หากคุณสลับการเสนอราคาสองครั้งสำหรับการเสนอราคาครั้งเดียวผู้โจมตีสามารถใช้คำพูดประเภทอื่นได้เช่นกัน)


ส่วนที่ 2:

ในfind -exec sh -c 'something "$@"' sh {} \;ที่{}ไม่ได้รับการตีความครั้งแรกโดยเปลือกก็ดำเนินการโดยตรงกับexecveเพื่อเพิ่มคำพูดเปลือกจะไม่ช่วยเหลือ

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

findไม่มีผลตั้งแต่เปลือกแถบคำพูดคู่ก่อนที่จะใช้

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

เพิ่มเครื่องหมายคำพูดที่เชลล์ไม่จัดการเป็นพิเศษดังนั้นโดยส่วนใหญ่แล้วมันหมายถึงคำสั่งจะไม่ทำในสิ่งที่คุณต้องการ

มีมันขยายไป/tmp/foo;, rm, -rf, $HOMEไม่ควรจะเป็นปัญหาเพราะผู้ที่มีข้อโต้แย้งsomethingและsomethingอาจจะไม่รักษาข้อโต้แย้งที่เป็นคำสั่งในการดำเนินการ


ส่วนที่ 3:

ผมถือว่าการพิจารณาที่คล้ายกันใช้สำหรับสิ่งที่จะเข้าที่ไม่น่าเชื่อถือและทำงานเป็น (ส่วนหนึ่งของ) สั่งตัวอย่างและxargsparallel


xargsจะเป็นอันตรายหากใช้-Iหรือ-Jไม่ใช้ ในการดำเนินงานตามปกติเป็นเพียงการผนวกข้อโต้แย้งไปยังจุดสิ้นสุดของรายการเช่นเดียวกับที่-exec ... {} +ไม่
Charles Duffy

1
( parallelตรงกันข้ามเรียกใช้เชลล์โดยปริยายโดยไม่มีการร้องขอจากผู้ใช้อย่างชัดเจนเพิ่มพื้นผิวที่เปราะบางนี่เป็นเรื่องที่ได้รับการถกเถียงกันมากดูunix.stackexchange.com/questions/349483/สำหรับคำอธิบาย และลิงก์ที่รวมไปยังlists.gnu.org/archive/html/bug-parallel/2015-05/msg00005.html )
ชาร์ลส์ดัฟฟี่

@CharlesDuffy คุณหมายถึง "การลดพื้นผิวที่เปราะบาง" การโจมตีที่ภาพประกอบโดย OP นั้นใช้ไม่ได้กับ GNU Parallel
Ole Tange

1
ใช่ฉันรู้ว่าคุณต้องการสำหรับความเท่าเทียมกันทั่ว SSH - ถ้าคุณไม่ได้วางไข่ระยะไกลparallelกระบวนการ "รับ" ในส่วนอื่น ๆ ของซ็อกเก็ตใด ๆ execvสามารถที่จะทำเปลือกน้อยโดยตรง ซึ่งเป็นสิ่งที่ฉันจะทำถ้าในรองเท้าของคุณใช้เครื่องมือ การมีโปรแกรมแบบไม่โต้ตอบทำหน้าที่แตกต่างกันไปตามค่าปัจจุบันของSHELLแทบไม่น่าประหลาดใจ
Charles Duffy

1
ใช่ - ฉันเชื่อว่าไพพ์และการเปลี่ยนเส้นทางควรเป็นไปไม่ได้เว้นแต่ผู้ใช้จะเริ่มเชลล์อย่างชัดเจน ณ จุดนี้พวกเขาจะได้รับเฉพาะพฤติกรรมที่ชัดเจนของเชลล์ที่พวกเขาเริ่มต้นอย่างชัดเจน หากใครสามารถคาดหวังได้ว่าอะไรexecvจะให้อะไรนั่นก็ง่ายและน่าประหลาดใจน้อยกว่าและกฎที่ไม่เปลี่ยนแปลงในสถานที่ / สภาพแวดล้อมรันไทม์
Charles Duffy

3

1. สาเหตุของปัญหาfind -exec sh -c "something {}" \;ที่การแทนที่ไม่ได้{}ถูกอ้างอิงและไม่ถือว่าเป็นสตริงเดี่ยวหรือไม่?

ในความรู้สึก แต่อ้างไม่สามารถช่วยที่นี่ ชื่อไฟล์ที่ได้รับการแทนที่ในสถานที่ของ{}สามารถมีใด ๆตัวอักษรรวมทั้งคำพูด ไม่ว่าจะใช้รูปแบบการอ้างชื่อใดก็ตามชื่อไฟล์อาจมีชื่อเดียวกันและ "แยกออก" ของการอ้างอิง

2. ... แต่เนื่องจาก{}ไม่มีการอ้างอิงไม่มี"$@"ปัญหาเดียวกันกับคำสั่งเดิมหรือไม่ ตัวอย่างเช่น"$@"จะถูกขยายเป็น"/tmp/foo;", "rm", "-rf", and "$HOME"?

ไม่"$@"ขยายไปยังพารามิเตอร์ตำแหน่งเป็นคำแยกและจะไม่แยกออกไปอีก นี่{}คืออาร์กิวเมนต์findในตัวมันเองและfindส่งชื่อไฟล์ปัจจุบันเป็นอาร์กิวเมนต์ที่shชัดเจน มันสามารถใช้งานได้โดยตรงเป็นตัวแปรในเชลล์สคริปต์ แต่จะไม่ถูกประมวลผลเป็นคำสั่งเชลล์

... ทำไม{}ไม่หลบหนีหรืออ้างถึง

มันไม่จำเป็นต้องเป็นหอยส่วนใหญ่ หากคุณเรียกใช้fishจะต้องมี: fish -c 'echo {}'พิมพ์บรรทัดว่าง แต่มันไม่สำคัญว่าคุณจะพูดอะไรเชลล์จะแค่ลบเครื่องหมายคำพูดออก

3. คุณสามารถยกตัวอย่างอื่น ๆ ...

ทุกครั้งที่คุณขยายชื่อไฟล์ (หรือสตริงอื่นที่ไม่มีการควบคุม) ตามที่อยู่ภายในสตริงที่ใช้เป็นรหัสบางชนิด(*)มีความเป็นไปได้ในการดำเนินการคำสั่งโดยอำเภอใจ

ตัวอย่างเช่นสิ่งนี้จะขยาย$fโค้ด Perl โดยตรงและจะทำให้เกิดปัญหาหากชื่อไฟล์มีเครื่องหมายคำพูดคู่ เครื่องหมายคำพูดในชื่อไฟล์จะสิ้นสุดการอ้างอิงในโค้ด Perl และชื่อไฟล์ที่เหลือสามารถมีโค้ด Perl ได้:

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

(ชื่อไฟล์จะต้องแปลกไปเล็กน้อยเนื่องจาก Perl จะแยกรหัสทั้งหมดออกไปข้างหน้าก่อนที่จะเรียกใช้มันดังนั้นเราจะต้องหลีกเลี่ยงข้อผิดพลาดในการแยกวิเคราะห์)

ขณะนี้ผ่านมันได้อย่างปลอดภัยผ่านการโต้แย้ง:

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

(มันไม่สมเหตุสมผลเลยที่จะรันเชลล์อีกตัวโดยตรงจากเชลล์ แต่ถ้าคุณทำมันคล้ายกับfind -exec sh ...เคส)

(* รหัสบางประเภทรวมถึง SQL ดังนั้น XKCD บังคับ: https://xkcd.com/327/บวกคำอธิบาย: https://www.explainxkcd.com/wiki/index.php/Little_Bobby_Tables )


1

ความกังวลของคุณคือเหตุผลที่ GNU Parallel ให้คำพูดป้อนข้อมูลได้อย่างแม่นยำ:

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

echo pwnedนี้จะไม่ทำงาน

มันจะรันเชลล์ดังนั้นหากคุณขยายคำสั่งของคุณคุณจะไม่แปลกใจในทันที:

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

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับปัญหาการวางไข่ - เชลล์โปรดดู: https://www.gnu.org/software/parallel/parallel_design.html#Always-running-commands-in-a-shell


1
... ที่กล่าวว่าfind . -print0 | xargs -0 printf "Argument: %s\n"มีความปลอดภัย (หรือมากกว่า moreso เนื่องจากมี-print0หนึ่งชื่อไฟล์ที่จัดการกับบรรทัดใหม่อย่างถูกต้อง); การอ้างอิงแบบขนานเป็นวิธีแก้ปัญหาสำหรับปัญหาที่ไม่มีอยู่เลยเมื่อไม่มีเชลล์อยู่
Charles Duffy

แต่ทันทีที่คุณเพิ่ม| wcคำสั่งคุณจะต้องกระโดดผ่านห่วงเพื่อให้ปลอดภัย GNU Parallel นั้นปลอดภัยโดยค่าเริ่มต้น
Ole Tange

ITYM: มันพยายามที่จะครอบคลุมทุกการเล่นโวหารของภาษาเชลล์โดยกระบวนการที่ซับซ้อนในการอ้างอิงทุกอย่างอย่างรอบคอบ นั่นไม่ปลอดภัยเท่ากับการจัดเก็บข้อมูลในตัวแปรอย่างถูกต้องไม่ผสมกับรหัส ทีนี้ถ้ามันจะแปลงมันfoo {} | wcเป็นsh -c 'foo "$1" | wcsh {} ` โดยอัตโนมัตินั่นอาจแตกต่างกัน
ilkkachu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.