การป้องกันคำสั่งเชลล์ด้วยตัวแปรสตริง


9

ภายในภาษาการเขียนโปรแกรมฉันรันคำสั่งเชลล์แบบง่าย ๆ

cd var; echo > create_a_file_here

ด้วยvarเป็นตัวแปรที่มีสตริง (หวังว่า) ไดเร็กทอรีไปยังตำแหน่งที่ฉันต้องการสร้างไฟล์ "create_a_file_here" ตอนนี้ถ้ามีคนเห็นบรรทัดของรหัสนี้มันเป็นไปได้ที่จะใช้ประโยชน์จากมันโดยการกำหนดเช่น:

var = "; rm -rf /"

สิ่งต่าง ๆ อาจดูน่าเกลียด วิธีหนึ่งในการหลีกเลี่ยงกรณีข้างต้นอาจเป็นการค้นหาสตริงในvarสำหรับอักขระพิเศษบางตัวเช่น ';' ก่อนดำเนินการคำสั่งเชลล์ แต่ฉันสงสัยว่าสิ่งนี้ครอบคลุมการหาประโยชน์ที่อาจเกิดขึ้นทั้งหมด

ไม่มีใครรู้วิธีที่ดีในการตรวจสอบว่า "cd var" เปลี่ยนเฉพาะไดเรกทอรีและไม่มีอะไรอีกหรือไม่


4
ขึ้นอยู่กับวิธีที่คุณสามารถเรียกใช้เชลล์คุณสามารถส่งผ่านvarเป็นอาร์กิวเมนต์ได้ เช่นเรียกร้องshกับการขัดแย้ง-c, 'cd "$1"; echo > create_a_file_here', 'sh', ผลงานและไม่จำเป็นต้องเปลี่ยนแปลงใดvar ๆ อาร์กิวเมนต์จะถูกส่งเป็น var'sh'$0
ipsec

1
ภาษาโปรแกรมอะไร คุณใช้ POSIX shหรือสร้างภาษาการเขียนโปรแกรมของคุณเองด้วยไวยากรณ์ที่คล้ายกัน แต่ขยายออกvarแทนที่จะให้คุณเขียนcd "$var"หรือไม่ หรือนี่คือbashด้วยshopt -s cdable_vars? โอ้ฉันคิดว่าคุณหมายถึงว่าโปรแกรมอื่นบางโปรแกรมบังคับให้เชลล์รันคำสั่งเหล่านี้ ดังนั้นเพียงแค่พูดvarแต่ให้แน่ใจว่าจะไม่รวมถึงตัวละครที่อ้างตัวเอง ...
ปีเตอร์ Cordes

@PeterCordes หากคุณกำลังพูดถึง Bash ตัวแปรที่มีเครื่องหมายคำพูดคู่ซึ่งมีเครื่องหมายคำพูดคู่จะใช้ได้ เช่นการพิมพ์s='"'; echo "$s" "
wjandrea

@WJAndrea: ใช่ แต่ไม่มีเครื่องหมาย "ทรัมป์" ที่ไม่สามารถเอาชนะได้เมื่อสร้างการกำหนดตัวแปรจากอินพุตที่ไม่น่าเชื่อถือ โอ้วิธีการแก้ปัญหา: ทำvar=untrusted stringในโปรแกรมแม่จึงเป็นตัวแปรสภาพแวดล้อมที่กำหนดไว้แล้วเมื่อกล่าวอ้างvar shจากนั้นคุณต้องอ้างมันทุกครั้งที่คุณขยายมันซึ่งเป็นไปได้ที่จะทำอย่างน่าเชื่อถือ อาฉันเห็นว่าความคิดนี้เป็นส่วนหนึ่งของคำตอบของStéphaneแล้ว> <
Peter Cordes

คำตอบ:


9

หากฉันเข้าใจถูกต้องvarเป็นตัวแปรในภาษาการเขียนโปรแกรมของคุณ

และในการเขียนโปรแกรมภาษาของคุณคุณกำลังขอให้เปลือกจะตีความสตริงที่มีการเรียงต่อกันของเนื้อหาของตัวแปรนั้นและ"cd ""; echo > create_a_file_here"

ถ้าใช่แล้วถ้าเนื้อหาของvarไม่ได้ถูกควบคุมอย่างแน่นหนามันเป็นช่องโหว่ในการฉีดคำสั่ง

คุณสามารถลองและอ้างถึงเนื้อหาของตัวแปร¹ในไวยากรณ์ของเชลล์ได้อย่างถูกต้องดังนั้นจึงรับประกันได้ว่าจะถูกส่งผ่านเป็นอาร์กิวเมนต์เดียวกับcdบิวด์อิน

อีกวิธีคือส่งเนื้อหาของตัวแปรนั้นอีกวิธีหนึ่ง วิธีที่ชัดเจนคือการส่งสิ่งนั้นในตัวแปรสภาพแวดล้อม ตัวอย่างเช่นใน C:

char *var =  "; rm -rf /";
setenv("DIR", var, 1);
system("CDPATH= cd -P -- \"$DIR\" && echo something > create_a_file_here");

เวลานี้รหัสที่คุณขอให้เชลล์ตีความได้รับการแก้ไขเรายังคงต้องเขียนมันอย่างถูกต้องในไวยากรณ์ของเชลล์ (ในที่นี้ถือว่าเป็นเชลล์ที่สอดคล้องกับ POSIX):

  • การขยายตัวแปรเชลล์จะต้องยกมาเพื่อป้องกันการแยก + glob
  • ที่คุณต้องการ-Pสำหรับการcdที่จะทำง่ายchdir()
  • คุณต้อง--ทำเครื่องหมายจุดสิ้นสุดของตัวเลือกเพื่อหลีกเลี่ยงปัญหาในการvarเริ่มต้นด้วย-(หรือ+ในบางเชลล์)
  • เราตั้งค่าCDPATHเป็นสตริงว่างในกรณีที่มันอยู่ในสภาพแวดล้อม
  • เรารันechoคำสั่งcdก็ต่อเมื่อสำเร็จ

มี (อย่างน้อย) หนึ่งปัญหาที่เหลืออยู่: ถ้าvarเป็น-เช่นนั้นจะไม่ chdir ในไดเรกทอรีที่เรียกว่า-แต่ไปยังไดเรกทอรีก่อนหน้า (ตามที่เก็บไว้ใน$OLDPWD) และOLDPWD=- CDPATH= cd -P -- "$DIR"ไม่รับประกันว่าจะแก้ไขได้ ดังนั้นคุณต้องการบางสิ่งเช่น:

system(
  "case $DIR in\n"
  " (-) CDPATH= cd -P ./-;;\n"
  " (*) CDPATH= cd -P -- \"$DIR\";;\n"
  "esac && ....");

¹โปรดทราบว่าการเพิ่งทำสิ่งsystem(concat("cd \"", var, "\"; echo..."));นั้นไม่ใช่วิธีที่จะไปคุณจะเป็นเพียงการเคลื่อนย้ายปัญหา

เช่น a var = "$(rm -rf /)"จะยังคงเป็นปัญหาอยู่

วิธีเดียวที่เชื่อถือได้ในการอ้างอิงข้อความสำหรับเชลล์ที่เหมือนบอร์นคือการใช้อัญประกาศเดี่ยวและดูแลราคาอัญประกาศเดี่ยวที่อาจเกิดขึ้นในสตริง ยกตัวอย่างเช่นเปิดไปchar *var = "ab'cd" char *escaped_var = "'ab'\\''cd'"นั่นคือแทนที่ทั้งหมด'ไปและห่อภายในสิ่งที่ทั้ง'\'''...'

ที่ยังคงอนุมานว่าที่สายยกไม่ได้ใช้ภายใน backticks และคุณยังคงต้อง--, -P, &&, CDPATH=...


12

วิธีแก้ปัญหาง่าย ๆ : อย่าเรียกเชลล์จากโปรแกรมของคุณ เลย

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

ดังนั้นเช่นในหลามแทนการทำงานใช้os.system("somecmd " + somearg) subprocess.run(["somecmd", somearg])ใน C แทนที่จะsystem()ใช้fork()และexec()(หรือค้นหาไลบรารีที่ทำ)

หากคุณจำเป็นต้องใช้เปลือกพูดอาร์กิวเมนต์บรรทัดคำสั่งหรือผ่านพวกเขาผ่านสภาพแวดล้อมเช่นเดียวกับในคำตอบของStéphane นอกจากนี้หากคุณพบว่าตัวเองกังวลเกี่ยวกับตัวละครพิเศษทางออกที่ถูกต้องคือไม่พยายามกรอง (บัญชีดำ) ตัวละครที่อาจเป็นอันตราย แต่เพื่อให้รู้ว่าตัวละครนั้นปลอดภัยเท่านั้น

อนุญาตเฉพาะตัวละครที่มีฟังก์ชั่นที่คุณรู้จักด้วยวิธีนี้มีความเสี่ยงน้อยกว่าที่จะพลาดบางสิ่งบางอย่าง ผลลัพธ์ที่ได้อาจเป็นไปได้ว่าคุณตัดสินใจอนุญาต[a-zA-Z0-9_]เท่านั้น แต่นั่นอาจเพียงพอที่จะทำให้งานเสร็จ นอกจากนี้คุณยังอาจต้องการตรวจสอบว่าสถานที่ของคุณและชุดเครื่องมือที่ไม่ประกอบด้วยตัวอักษรสำเนียงเหมือนäและöในการที่ พวกมันอาจจะไม่ได้รับการพิจารณาเป็นพิเศษจากกระสุนใด ๆ แต่อีกครั้งมันจะดีกว่าถ้าแน่ใจว่าพวกมันผ่านหรือไม่


1
และตรวจสอบให้แน่ใจว่าสิ่งที่คุณใช้ในการเปรียบเทียบ[a-zA-Z0-9]ไม่ได้รวมสิ่งต่าง ๆ เช่นการàเข้ารหัสซึ่งอาจมีการตีความที่ผิดพลาดโดยบางเชลล์ (เช่นbash) ในบางตำแหน่งที่ตั้ง
Stéphane Chazelas

@ StéphaneChazelas (ไม่สนใจ :) พวกเขา (และอยู่ใต้สถานที่ที่ได้รับผลกระทบเหล่านั้นหรือไม่)
วิลฟ์

10

ภายในภาษาการเขียนโปรแกรมควรมีวิธีที่ดีกว่าในการทำสิ่งต่าง ๆ กว่าการใช้คำสั่งเชลล์ ตัวอย่างเช่นการแทนที่cd varด้วยภาษาโปรแกรมที่เทียบเท่าของคุณchdir (var);ควรตรวจสอบให้แน่ใจว่าการใช้เล่ห์เหลี่ยมใด ๆ ที่มีค่าvarผลลัพธ์เพียงข้อผิดพลาด "ไม่พบไดเรกทอรี" แทนที่จะเป็นการกระทำที่ไม่ได้ตั้งใจและอาจเป็นอันตราย

นอกจากนี้คุณสามารถใช้พา ธ สัมบูรณ์แทนการเปลี่ยนไดเรกทอรี เพียงต่อชื่อไดเรกทอรีเครื่องหมายทับและชื่อไฟล์ที่คุณต้องการใช้

ใน C ฉันอาจทำสิ่งที่ชอบ:

char filepath[PATH_MAX];  /* alternative constant: MAXPATHLEN */

/* Join directory name in var and the filename, guarding against exceeding PATH_MAX */
snprintf (filepath, PATH_MAX, "%s/%s", var, "create_a_file_here");

/* create an empty file/truncate an existing one */
fclose (fopen (filepath, "w") );

แน่นอนว่าภาษาโปรแกรมของคุณสามารถทำสิ่งที่คล้ายกันได้หรือไม่


ขอบคุณสำหรับคำตอบของคุณ แต่น่าเสียดายที่ฉันต้องใช้วิธีแก้ไขกับคำสั่ง bash ฉันทดสอบการอ้างอิงตัวแปร - ตามที่ muru แนะนำ - และดูเหมือนว่าจะใช้งานได้!
ES___
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.