ฉันเขียนสิ่งนี้เป็นรูปแบบการสอนใหม่ของคำตอบที่ยอดเยี่ยมโดย Chris Down ด้านบน
ในทุบตีคุณสามารถมีตัวแปรเชลล์เช่นนี้
$ t="hi there"
$ echo $t
hi there
$
โดยค่าเริ่มต้นตัวแปรเหล่านี้ไม่ได้รับการสืบทอดโดยกระบวนการลูก
$ bash
$ echo $t
$ exit
แต่ถ้าคุณทำเครื่องหมายเพื่อส่งออก bash จะตั้งค่าสถานะซึ่งหมายความว่าพวกเขาจะเข้าสู่สภาพแวดล้อมของกระบวนการย่อย (แม้ว่าenvp
พารามิเตอร์จะไม่เห็นมากนักmain
ในโปรแกรม C ของคุณมีสามพารามิเตอร์: main(int argc, char *argv[], char *envp[])
โดยที่ตัวชี้อาร์เรย์สุดท้ายคืออาร์เรย์ ของตัวแปรเชลล์พร้อมคำจำกัดความ)
ลองส่งออกt
ดังนี้:
$ echo $t
hi there
$ export t
$ bash
$ echo $t
hi there
$ exit
ในขณะที่ข้างต้นt
ไม่ได้ถูกกำหนดใน subshell ตอนนี้มันปรากฏขึ้นหลังจากที่เราส่งออกมัน (ใช้export -n t
ถ้าคุณต้องการที่จะหยุดการส่งออก)
แต่ฟังก์ชั่นในการทุบตีเป็นสัตว์ที่แตกต่างกัน คุณประกาศให้พวกเขาเช่นนี้:
$ fn() { echo "test"; }
และตอนนี้คุณสามารถเรียกใช้ฟังก์ชันได้โดยเรียกมันราวกับว่ามันเป็นคำสั่งเชลล์อื่น:
$ fn
test
$
อีกครั้งหากคุณวางไข่ subshell ฟังก์ชันของเราจะไม่ถูกส่งออก:
$ bash
$ fn
fn: command not found
$ exit
เราสามารถส่งออกฟังก์ชันด้วยexport -f
:
$ export -f fn
$ bash
$ fn
test
$ exit
นี่คือส่วนที่ยุ่งยาก: ฟังก์ชั่นที่ส่งออกเช่นfn
ถูกแปลงเป็นตัวแปรสภาพแวดล้อมเช่นเดียวกับการส่งออกตัวแปรเชลล์ของt
เรา สิ่งนี้ไม่ได้เกิดขึ้นเมื่อfn
เป็นตัวแปรโลคอล แต่หลังจากการส่งออกเราจะเห็นว่ามันเป็นตัวแปรเชลล์ อย่างไรก็ตามคุณยังสามารถมีตัวแปรเชลล์ (เช่นไม่ใช่ฟังก์ชัน) ปกติที่มีชื่อเดียวกัน bash แยกตามเนื้อหาของตัวแปร:
$ echo $fn
$ # See, nothing was there
$ export fn=regular
$ echo $fn
regular
$
ตอนนี้เราสามารถใช้env
เพื่อแสดงตัวแปรเชลล์ทั้งหมดที่ทำเครื่องหมายเพื่อการส่งออกและทั้งปกติfn
และฟังก์ชั่นการfn
แสดง:
$ env
.
.
.
fn=regular
fn=() { echo "test"
}
$
sub-shell จะรับทั้งคำจำกัดความ: หนึ่งเป็นตัวแปรปกติและอีกหนึ่งเป็นฟังก์ชั่น:
$ bash
$ echo $fn
regular
$ fn
test
$ exit
คุณสามารถกำหนดได้fn
เหมือนที่เราทำด้านบนหรือโดยตรงเป็นการกำหนดตัวแปรตามปกติ:
$ fn='() { echo "direct" ; }'
หมายเหตุนี่เป็นสิ่งที่ผิดปกติอย่างมากที่ต้องทำ! โดยปกติเราจะกำหนดฟังก์ชั่นfn
ตามที่เราทำข้างต้นด้วยfn() {...}
ไวยากรณ์ แต่เนื่องจากทุบตีส่งออกผ่านสภาพแวดล้อมเราสามารถ "ลัด" โดยตรงกับคำนิยามปกติข้างต้น โปรดทราบว่า (อาจขัดกับสัญชาตญาณของคุณ) สิ่งนี้ไม่ได้ส่งผลให้เกิดฟังก์ชั่นใหม่ที่fn
มีอยู่ในเชลล์ปัจจุบัน แต่ถ้าคุณวางไข่เปลือก ** ย่อยแล้วมันจะ
มายกเลิกการส่งออกฟังก์ชั่นfn
และปล่อยให้ปกติใหม่fn
(ดังที่แสดงไว้ด้านบน) ไม่เป็นอันตราย
$ export -nf fn
ตอนนี้ฟังก์ชั่นfn
จะไม่ถูกส่งออกอีกต่อไป แต่ตัวแปรปกติfn
คือและมันมี() { echo "direct" ; }
อยู่ในนั้น
ตอนนี้เมื่อ subshell เห็นตัวแปรปกติที่เริ่มต้นด้วย()
มันตีความส่วนที่เหลือเป็นนิยามฟังก์ชั่น แต่นี่เป็นเพียงเมื่อเชลล์ใหม่เริ่มต้นขึ้น ดังที่เราเห็นด้านบนเพียงแค่กำหนดตัวแปรเชลล์ปกติที่เริ่มต้นด้วย()
ไม่ทำให้มันทำงานเหมือนฟังก์ชั่น คุณต้องเริ่ม subshell
และตอนนี้บั๊ก "shellshock":
อย่างที่เราเพิ่งเห็นเมื่อเชลล์ใหม่นำเข้านิยามของตัวแปรปกติที่เริ่มต้นด้วย()
การตีความมันเป็นฟังก์ชั่น อย่างไรก็ตามหากมีการกำหนดเพิ่มเติมหลังจากวงเล็บปิดที่กำหนดฟังก์ชั่นจะดำเนินการสิ่งที่มีเช่นกัน
นี่คือข้อกำหนดอีกครั้ง:
- ทุบตีใหม่จะเกิดขึ้น
- ตัวแปรสภาพแวดล้อมถูกกลืนเข้า
- ตัวแปรสภาพแวดล้อมนี้เริ่มต้นด้วย "()" จากนั้นจะมีส่วนของฟังก์ชั่นภายในวงเล็บปีกกาจากนั้นก็มีคำสั่งหลังจากนั้น
ในกรณีนี้การทุบตีที่มีช่องโหว่จะดำเนินการคำสั่งหลัง
ตัวอย่าง:
$ export ex='() { echo "function ex" ; }; echo "this is bad"; '
$ bash
this is bad
$ ex
function ex
$
ตัวแปรที่ส่งออกปกติex
ถูกส่งผ่านไปยัง subshell ซึ่งถูกตีความว่าเป็นฟังก์ชั่นex
แต่คำสั่งต่อท้ายได้รับการดำเนินการ ( this is bad
) เป็น subshell วางไข่
อธิบายการทดสอบแบบบรรทัดเดียวที่ลื่นไหล
หนึ่งซับในที่นิยมสำหรับการทดสอบสำหรับช่องโหว่ Shellshock เป็นหนึ่งที่อ้างถึงในคำถามของ @ jippie:
env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
นี่คือการทำลายลงครั้งแรกในทุบตีเป็นเพียงชื่อย่อสำหรับ :
และทั้งคู่ประเมินว่า (คุณคาดเดา) จริงในทุบตี:true
true
:
$ if true; then echo yes; fi
yes
$ if :; then echo yes; fi
yes
$
ประการที่สองenv
คำสั่ง (สร้างขึ้นใน bash) พิมพ์ตัวแปรสภาพแวดล้อม (ดังที่เราเห็นด้านบน) แต่ยังสามารถใช้เพื่อเรียกใช้คำสั่งเดียวด้วยตัวแปรส่งออก (หรือตัวแปร) ที่กำหนดให้กับคำสั่งนั้นและbash -c
เรียกใช้คำสั่งเดียวจาก บรรทัดคำสั่ง:
$ bash -c 'echo hi'
hi
$ bash -c 'echo $t'
$ env t=exported bash -c 'echo $t'
exported
$
ดังนั้นการเย็บสิ่งทั้งหมดนี้เข้าด้วยกันเราสามารถรัน bash เป็นคำสั่งให้มันทำสิ่งที่ต้องทำ (เช่นbash -c echo this is a test
) และส่งออกตัวแปรที่เริ่มต้นด้วย()
ดังนั้น subshell จะตีความมันเป็นฟังก์ชั่น หาก shellshock มีอยู่ก็จะดำเนินการคำสั่งต่อท้ายใด ๆ ใน subshell ทันที เนื่องจากฟังก์ชั่นที่เราส่งผ่านนั้นไม่เกี่ยวข้องกับเรา (แต่ต้องแยกวิเคราะห์!) เราจึงใช้ฟังก์ชั่นที่สั้นที่สุดเท่าที่จะเป็นไปได้:
$ f() { :;}
$ f
$
ฟังก์ชั่นf
ที่นี่เพียงแค่รัน:
คำสั่งซึ่งจะส่งกลับจริงและออก ตอนนี้ต่อท้ายว่าคำสั่ง "ความชั่วร้าย" และส่งออกตัวแปรปกติไปยัง subshell และคุณชนะ นี่คือหนึ่งซับอีกครั้ง:
$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test"
ดังนั้นx
จะถูกส่งออกเป็นตัวแปรปกติที่มีฟังก์ชั่นที่ใช้งานง่ายและecho vulnerable
ติดอยู่กับส่วนท้าย สิ่งนี้ถูกส่งผ่านไปยัง bash และตีความ bash x
เป็นฟังก์ชั่น (ซึ่งเราไม่สนใจ) แล้วอาจดำเนินการecho vulnerable
ถ้ามี shellshock อยู่
เราสามารถย่อขนาดหนึ่งซับให้สั้นลงได้โดยลบthis is a test
ข้อความ:
$ env x='() { :;}; echo vulnerable' bash -c :
สิ่งนี้ไม่รบกวนthis is a test
แต่รัน:
คำสั่งเงียบอีกครั้ง (ถ้าคุณออกไป-c :
จากนั้นคุณนั่งใน subshell และต้องออกด้วยตนเอง) บางทีรุ่นที่เป็นมิตรกับผู้ใช้มากที่สุดน่าจะเป็นอันนี้:
$ env x='() { :;}; echo vulnerable' bash -c "echo If you see the word vulnerable above, you are vulnerable to shellshock"