ช่องโหว่ Nginx ที่ทำให้คลังข้อมูลของ Bitwarden รั่วไหล
(labs.hakaioffsec.com)- Nginx คือเว็บเซิร์ฟเวอร์อเนกประสงค์ที่ครองส่วนแบ่งตลาดอย่างโดดเด่นมาตั้งแต่ปี 2004
- การตั้งค่า Nginx ที่ไม่ถูกต้องอาจนำไปสู่ช่องโหว่ด้านความปลอดภัยและการเปิดเผยข้อมูล
- แนะนำ NavGix เครื่องมืออัตโนมัติสำหรับตรวจจับช่องโหว่ใน Nginx
- ช่องโหว่นี้ทำให้สามารถเข้าถึงไฟล์และไดเรกทอรีนอกขอบเขตที่ตั้งใจไว้ได้
- กรณีศึกษาของ Bitwarden และ HPC Toolkit ของ Google แสดงให้เห็นถึงความร้ายแรงของช่องโหว่นี้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
เผื่อไว้เป็นข้อมูล gixy (เครื่องมือตรวจสอบคอนฟิก nginx) สามารถตรวจจับปัญหานี้ได้: https://github.com/yandex/gixy/blob/master/docs/en/plugins/a...
และ NixOS จะรัน gixy อัตโนมัติกับคอนฟิกที่สร้างผ่านระบบของตัวเอง และถ้ามีปัญหาก็จะปฏิเสธการ build ระบบ
$uriอยู่ใน 301 redirectแต่สงสัยว่ามี ตัวค้นหาออปชัน ที่ดีกว่านี้ไหม การค้นจากออปชันทั้งหมดหลายพันรายการไม่ค่อยเวิร์กนัก อยากดูแค่แพ็กเกจอย่าง
sshแล้วเห็นเฉพาะออปชันที่เกี่ยวข้อง แต่ผลลัพธ์ที่ไม่เกี่ยวข้องปนมาเยอะเกินไปอาจเป็นคำถามโง่ ๆ แต่มีเหตุผลดีอะไรไหมที่ nginx ควรยอมให้ใช้
..ใน URL path เพื่อไต่ขึ้นไปยังไดเรกทอรีแม่? มันดูเหมือนพฤติกรรมที่รอให้เกิดปัญหาชัด ๆแก้ไข: ตอนแรกยังงงนิดหน่อยว่าในช่องโหว่ต้นฉบับเกิดอะไรขึ้น ดูเหมือนว่า http://localhost/foo../secretfile.txt จะถูกตีความเป็นอะไรอย่าง
/var/www/foo/../secretfile.txtแต่ถ้าเป็นเซิร์ฟเวอร์ที่ไม่มีช่องโหว่ ทำไม http://localhost/foo/../secretfile.txt ถึงไม่ถูกตีความแบบเดียวกันด้วยล่ะ? ไม่เข้าใจว่าทำไม..ใน path ถึงทำงานเฉพาะบางกรณีในกรณีนี้ nginx ตีความ URL ส่วนหนึ่งเป็นไดเรกทอรี (http://localhost/foo) เพราะวิธีที่คอนฟิกแมปมันเข้ากับไฟล์ซิสเต็มภายในเครื่อง เมื่อมันดูเหมือนชี้ไปที่ไดเรกทอรี ตอน nginx สร้าง full path ของทรัพยากรที่ร้องขอ มันจึงกลายเป็น
"${mapped_path}/../secretfile.txt"และถึงแม้จากมุมมองของ URL จะไม่สมเหตุสมผล แต่มันกลับใช้ได้บนไฟล์ซิสเต็มภายในเครื่อง URL ไม่ใช่องค์ประกอบ path จริง ๆ แต่เป็นแค่สตริง ดังนั้นตำแหน่งของ slash จึงไม่ได้สำคัญโดยเนื้อแท้นี่เป็นปัญหาที่พบได้บ่อยมากในเว็บเซิร์ฟเวอร์มาตั้งแต่ยุคแรกของเว็บ การแมป URL เข้ากับ file path โดยตรงได้รับความนิยมเพราะเริ่มต้นจากการเป็นไฟล์เซิร์ฟเวอร์แบบง่ายที่มี index แต่หลังจากนั้นไม่นานก็กลายเป็นสภาพแวดล้อมแบบผสมที่ URL ไม่ได้เป็น path อีกต่อไป แต่เป็นตัวระบุแอปพลิเคชัน เช่น ระบุแอปด้วยส่วนหนึ่งของ path แล้วใช้ส่วนที่เหลือเป็นพารามิเตอร์หรืออาร์กิวเมนต์
และโดยทั่วไปแล้ว การเคารพ
.หรือ..ของ URL กับอ็อบเจ็กต์ในไฟล์ซิสเต็มมักไม่สมเหตุสมผล แอปของฉันจะทำความสะอาด request path เพื่อให้แมปได้ถูกต้อง เบราว์เซอร์ปฏิบัติต่อ URL เหมือน path เวลาสร้าง relative link ดังนั้นจึงต้องระวังด้วยว่าเมื่อไรและอย่างไรถึงจะใช้/ปิดท้าย เพราะฝั่งเซิร์ฟเวอร์อาจหมายถึงทรัพยากรคนละตัวlocationเช่นallow_parent_traversal on;"/foo/bar/.."แล้วบล็อกไว้หรือทำ normalization ให้เป็น"/foo/"แต่"/foo/bar.."เป็นชื่อไฟล์ที่ถูกต้องสมบูรณ์ เลยอาจหลุดรอดจากการตรวจแบบนั้นไปได้..จะทำงานเมื่อไรใน path ขึ้นอยู่กับ สิทธิ์ของไฟล์ ทั้งหมดในกรณีนี้ ถ้าสมมติว่ามีสิทธิ์อ่านไฟล์จากไดเรกทอรี web index (
../index.html) ย้อนขึ้นไปจนถึง root directory (/) ก็แปลว่าคุณมีสิทธิ์ไล่ขึ้นไปถึง root ได้ และเมื่อถึงตรงนั้นก็จะอ่านไฟล์ที่ world-readable ทุกไฟล์ที่เข้าถึงได้จาก root เช่น/etc/passwdได้ด้วยลองนึกภาพส้อมสามง่าม โดยเว็บเซิร์ฟเวอร์อยู่ที่ง่ามขวาสุด ถ้าส่วนด้ามที่ง่ามทั้งหลายมาบรรจบกันคือไฟล์ซิสเต็ม เมื่อเว็บเซิร์ฟเวอร์มีสิทธิ์เข้าถึงไฟล์และไดเรกทอรีจากง่ามขวาสุดไปจนถึงด้าม พอไปถึงด้ามแล้วก็สามารถเข้าถึงไฟล์ในง่ามอื่นต่อได้เหมือนกัน
ไม่เข้าใจว่าทำไมเรื่องนี้ถึงไม่ถูกมองว่าเป็นช่องโหว่ของ nginx พฤติกรรมนี้ไร้เหตุผลโดยสิ้นเชิง ดูไม่มีประโยชน์อะไร และเอาไปโจมตีได้ทันที
ต้องจำไว้ด้วยว่า Nginx ได้รับความนิยมก็เพราะ benchmark ที่ชี้ว่ามัน “web scale” กว่า Apache2
มีคนเสนอแนวคิดแบบนี้ขึ้นมา: ใส่ ตัวเลือกคล้าย Linux capability ในตัวแยกวิเคราะห์ชื่อไฟล์ของ Linux kernel เพื่อเอาตัวเลือก
..ออกในเว็บแอปมีวิธีเลี่ยงสารพัดแบบเพื่อยัดจุดสองจุดเข้าไปมาตั้งแต่สมัยใช้โทรศัพท์โมเด็มแล้ว เหมือนที่ Linux kernel เคยจัดการกับบั๊กใน user space อีกหลายประเภท บางทีถึงเวลาหาวิธีปิดปัญหานี้ทีเดียวให้จบได้แล้ว
RESOLVE_BENEATHใน https://man7.org/linux/man-pages/man2/openat2.2.htmlFreeBSD ก็มีความสามารถนี้ใน
openat(2)ปกติผ่านO_RESOLVE_BENEATHแค่รัน nginx ด้วยผู้ใช้แยกต่างหากที่สิทธิ์จำกัดมาก ๆ หรือรันใน Docker แล้วอัปเดตเป็นประจำ ปกติก็แก้ ปัญหาด้านความปลอดภัย 90% ได้แล้ว
/some/../pathควรถูกห้ามเกือบ 100% ไม่มีกรณีใช้งานที่สมเหตุสมผลนอกจาก “มีใครสักคนเขียนโค้ดได้น่าเกลียด”../some/pathอย่างน้อยก็ยังมีความหมายอยู่บ้างเป็นครั้งคราวแต่ก็คงไม่ได้มีประโยชน์อย่างที่คิด เพราะหลายแอปตีความ
..เองก่อนส่งต่อให้ระบบปฏิบัติการถ้าคุณเสิร์ฟไฟล์จากโฟลเดอร์ออกเว็บ เว็บเฟรมเวิร์กก็ควรจัดการไม่ให้ออกไปนอกโฟลเดอร์รากที่เปิดเผยต่อสาธารณะ ถ้าคุณทำเองก็ต้องคิดเผื่อสถานการณ์สารพัดรวมถึงเรื่องนี้ด้วย
บอกว่า “Google VRP Team ให้รางวัล 500 ดอลลาร์สำหรับการพบบั๊กนี้ โดยมองว่าผลกระทบต่อแอปพลิเคชันไม่ร้ายแรงพอจะได้รางวัลสูงกว่านี้” งั้นเหรอ อีเมลบัญชี GCP กับคีย์ส่วนตัวที่ไม่เปิดเผยถูกเปิดออกมา ยังได้แค่ 500 ดอลลาร์เองเหรอ? อะไรกันเนี่ย สมเป็น Google จริง ๆ
อย่างน้อยสิ่งที่รั่วไหลออกมายัง ถูกเข้ารหัส อยู่ ซึ่งถือว่าโชคดี แม้แต่บริษัทที่เชี่ยวชาญด้านนี้ก็ยังหนีการรั่วไหลไม่พ้น พูดตามตรง นี่ก็คือสถานการณ์ที่ดีที่สุดเท่าที่หวังได้แล้ว
ชื่อเรื่องถูกแก้ไปค่อนข้างเยอะ ชื่อเดิมคือ Hunting for Nginx Alias Traversals in the wild
ชื่อที่ส่งเข้า HN เน้นช่องโหว่ของ Bitwarden แต่ในบทความก็พูดถึงกรณีของ Google ด้วย
ถ้าต้องการแค่ เสิร์ฟไฟล์สแตติก อย่างปลอดภัยและเสถียรโดยใช้ทรัพยากรให้น้อยที่สุด ทุกวันนี้ตัวเลือกที่ดีที่สุดคืออะไร? ถ้าเป็นเมื่อก่อนผมคงเลือก Nginx แต่ในมุมความปลอดภัยก็สงสัยว่าเครื่องมือที่ทำหน้าที่แคบกว่าและตั้งค่าได้น้อยกว่าน่าจะดีกว่าไหม
มันข้ามแพลตฟอร์ม เขียนด้วย Rust ตั้งค่าง่าย และมีค่าเริ่มต้นที่ปลอดภัย ยังมีอิมเมจคอนเทนเนอร์แบบ hardened กับโมดูล NixOS แบบ hardened ด้วย
ผมไม่แนะนำ Caddy เพราะอิมเมจ Docker ทางการรันเป็น root โดยปริยาย [1] และก็ไม่ได้ให้ไฟล์ยูนิต systemd ที่ sandbox อย่างเหมาะสมมาด้วย [2]
[1]: https://github.com/caddyserver/caddy-docker/issues/104
[2]: https://github.com/caddyserver/dist/blob/master/init/caddy.s...
แก้ไข: ปรับถ้อยคำ
file_serverที่แข็งแรงอาจเป็นคำถามงี่เง่า แต่ทำไม Bitwarden ถึงยอมให้มี คำขอที่ไม่ต้องยืนยันตัวตน ไปยัง
/attachmentsตั้งแต่แรก? ต่อให้มีบั๊กใน Nginx ถ้า URL นั้นต้องยืนยันตัวตนก่อน คำขอก็น่าจะล้มเหลวไม่ใช่เหรอ?ถึงอย่างนั้น Bitwarden ก็ยังต้องรับผิดชอบ เพราะปล่อยการตั้งค่าที่เสี่ยงผ่าน Docker ออกมา และดูเหมือน Bitwarden ก็ยอมรับเรื่องนี้แล้วและแก้ไขในภายหลัง
ขออภัยหากเป็นคำถามงี่เง่า แบบนี้ไม่ถูกป้องกันด้วย สิทธิ์ความเป็นเจ้าของไดเรกทอรีและไฟล์ ที่เหมาะสมหรือ?
ถ้า nginx ไม่ได้รันด้วย root แล้ว nginx จะอ่านไฟล์อื่นนอกเหนือจากไฟล์ที่ถูกกำหนดให้ผู้ใช้ nginx โดยชัดเจนได้อย่างไร?
go-rwxให้ไฟล์ทั้งหมดของแอป ตั้งกรุ๊ปของไฟล์ “static” เป็นwww-dataแล้วให้แค่g+rเว็บเซิร์ฟเวอร์ก็จะเข้าถึงไฟล์แอปไม่ได้พูดตรงตัวเลยว่านี่คือ การโฮสต์แอป 101 และคนก็ทำกันแบบนี้มาตั้งแต่ 20 ปีก่อนแล้ว
/homeอาจต้องจัดการเรื่องการเป็นสมาชิกกลุ่มเพิ่มอีกหน่อย แต่คุ้มค่าพอแน่นอน
บางทีฉันอาจกำลังทำพลาดครั้งใหญ่จริง ๆ ก็ได้ โอ๊ย
www-dataโดยชัดเจนด้วย ถ้าแอปพลิเคชันสร้างข้อมูลที่อ่อนไหว ก็ควรใช้ 077 umask อยู่แล้วน่าเสียดายที่ nginx และเว็บเซิร์ฟเวอร์อื่น ๆ มักจำเป็นต้องรันด้วย root ในเว็บแอปพลิเคชันทั่วไป เพราะต้อง listen ที่พอร์ต 80 หรือ 443 พอร์ตที่ต่ำกว่า 1024 นั้นมีแต่ root เท่านั้นที่เปิดได้
มีคำอธิบายละเอียดกว่านี้อยู่ที่นี่: https://unix.stackexchange.com/questions/134301/why-does-ngi...