ในบทความนี้เราจะได้เรียนรู้วิธีการเรียกคำสั่งระบบการโทรอย่างถูกต้องโดยใช้ node.js เพื่อหลีกเลี่ยงช่องโหว่การฉีดบรรทัดคำสั่งทั่วไป
วิธีที่เรามักใช้ในการเรียกคำสั่งคือ child_process.exec ที่ง่ายที่สุด มันมีรูปแบบการใช้งานที่ง่ายมาก มันส่งผ่านในคำสั่งสตริงและส่งผลให้เกิดข้อผิดพลาดหรือผลการประมวลผลคำสั่งไปยังฟังก์ชันการเรียกกลับ
นี่คือตัวอย่างทั่วไปของคำสั่งระบบการโทรของคุณผ่าน child_process.exec
การคัดลอกรหัสมีดังนี้:
child_process.exec ('ls', ฟังก์ชั่น (err, data) {
console.log (ข้อมูล);
-
อย่างไรก็ตามจะเกิดอะไรขึ้นเมื่อคุณต้องการเพิ่มพารามิเตอร์ที่ใช้กับผู้ใช้ในคำสั่งที่คุณเรียก ทางออกที่ชัดเจนคือการสตริงการป้อนข้อมูลผู้ใช้โดยตรงด้วยคำสั่งของคุณ อย่างไรก็ตามประสบการณ์หลายปีของฉันบอกฉันว่า: เมื่อคุณส่งสตริงที่เชื่อมต่อจากระบบหนึ่งไปยังอีกระบบหนึ่งจะมีปัญหาหนึ่งวัน
การคัดลอกรหัสมีดังนี้:
var path = "อินพุตผู้ใช้";
child_process.exec ('ls -l' + พา ธ , ฟังก์ชัน (err, data) {
console.log (ข้อมูล);
-
เหตุใดสตริงการเชื่อมต่อจึงมีปัญหา?
เพราะภายใต้เอ็นจิ้น child_process.exec การดำเนินการของ "/bin/sh" จะถูกเรียก ไม่ใช่โปรแกรมเป้าหมาย คำสั่งที่ส่งผ่านไปยังกระบวนการ "/bin/sh 'ใหม่เพื่อดำเนินการเชลล์ชื่อของ child_process.exec ค่อนข้างทำให้เข้าใจผิด - นี่คือล่ามทุบตีไม่ใช่โปรแกรมซึ่งหมายความว่าอักขระเชลล์ทั้งหมดอาจมีผลกระทบร้ายแรงหากพารามิเตอร์ที่ป้อนโดยผู้ใช้จะถูกดำเนินการโดยตรง
การคัดลอกรหัสมีดังนี้:
[PID 25170] Execve ("/bin/sh", ["/bin/sh", "-c", "อินพุตผู้ใช้ LS -L"], [/ * 16 vars */]
ตัวอย่างเช่นผู้โจมตีสามารถใช้เครื่องหมายอัฒภาค ";"; เพื่อยุติคำสั่งและเริ่มการโทรใหม่และพวกเขาสามารถใช้ backticks หรือ $ () เพื่อเรียกใช้คำสั่งย่อย นอกจากนี้ยังมีการละเมิดที่อาจเกิดขึ้นมากมาย
แล้ววิธีที่ถูกต้องในการเรียกมันคืออะไร?
ExecFile / Spawn
การวางไข่และ ExecFile ใช้พารามิเตอร์อาร์เรย์พิเศษซึ่งไม่ใช่สภาพแวดล้อมเชลล์ที่สามารถเรียกใช้คำสั่งอื่น ๆ และจะไม่เรียกใช้คำสั่งเพิ่มเติม
ลองใช้ ExecFile และ Spawn เพื่อแก้ไขตัวอย่างก่อนหน้านี้เพื่อดูว่าการโทรของระบบแตกต่างกันอย่างไรและทำไมจึงไม่ไวต่อการฉีดคำสั่ง
child_process.execfile
การคัดลอกรหัสมีดังนี้:
var child_process = ต้องการ ('child_process');
var path = "."
child_process.execfile ('/bin/ls', ['-l', พา ธ ], ฟังก์ชั่น (เอ่อ, ผลลัพธ์) {
console.log (ผลลัพธ์)
-
การเรียกใช้ระบบเรียกใช้
การคัดลอกรหัสมีดังนี้:
[PID 25565] Execve ("/bin/ls", ["/bin/ls", "-l", "."], [/ * 16 vars */]
child_process.spawn
ตัวอย่างของการใช้การเปลี่ยนวางไข่นั้นคล้ายกันมาก
การคัดลอกรหัสมีดังนี้:
var child_process = ต้องการ ('child_process');
var path = "."
var ls = child_process.spawn ('/bin/ls', ['-l', path])
ls.stdout.on ('data', function (data) {
console.log (data.toString ());
-
การเรียกใช้ระบบเรียกใช้
การคัดลอกรหัสมีดังนี้:
[PID 26883] Execve ("/bin/ls", ["/bin/ls", "-l", ",". "], [/ * 16 vars *//
เมื่อใช้ Spawn หรือ ExecFile เป้าหมายของเราคือการเรียกใช้คำสั่งเดียว (พารามิเตอร์) เท่านั้น ซึ่งหมายความว่าผู้ใช้ไม่สามารถเรียกใช้คำสั่งที่ฉีดได้เนื่องจาก /bin /ls ไม่ทราบวิธีจัดการ backticks หรือ pipe หรือ; สิ่งที่ /bin /bash จะอธิบายคือพารามิเตอร์ของคำสั่งเหล่านั้น มันคล้ายกับการใช้พารามิเตอร์เพื่อส่งพารามิเตอร์ลงในแบบสอบถาม SQL หากคุณคุ้นเคย
แต่ยังมีคำเตือน: การใช้การวางไข่หรือ ExecFile นั้นไม่ปลอดภัยเสมอไป ตัวอย่างเช่นการเรียกใช้ /bin /ค้นหาและส่งผ่านพารามิเตอร์อินพุตของผู้ใช้อาจยังคงทำให้ระบบถูกขังอยู่ คำสั่งค้นหามีตัวเลือกบางอย่างที่อนุญาตให้อ่าน/เขียนไฟล์โดยพลการ
ดังนั้นนี่คือคำแนะนำบางอย่างเกี่ยวกับคำสั่งระบบที่ใช้งาน Node.js:
หลีกเลี่ยงการใช้ child_process.exec โดยเฉพาะอย่างยิ่งเมื่อคุณต้องการรวมพารามิเตอร์ที่ป้อนโดยผู้ใช้โปรดจำไว้
พยายามหลีกเลี่ยงการปล่อยให้ผู้ใช้ผ่านพารามิเตอร์ การใช้การเลือกนั้นดีกว่าการให้ผู้ใช้ป้อนสตริงโดยตรง
หากคุณต้องอนุญาตให้ผู้ใช้ป้อนพารามิเตอร์โปรดดูพารามิเตอร์ของคำสั่งอย่างกว้างขวางให้กำหนดว่าตัวเลือกใดปลอดภัยและสร้าง whitelist