ทำไมทุกคนควรใช้ dependency cooldown
(blog.yossarian.net)- dependency cooldown คือ เทคนิคความปลอดภัยที่เรียบง่ายและได้ผล ซึ่งสามารถบรรเทาการโจมตีซัพพลายเชนของโอเพนซอร์สได้เป็นส่วนใหญ่
- โดยทั่วไปผู้โจมตีจะยึดโครงการโอเพนซอร์สยอดนิยมเพื่อกระจายโค้ดอันตราย แต่ช่วงเวลาที่การโจมตี เปิดเผยอยู่มักสั้นกว่า 1 สัปดาห์
- หากตั้งค่า cooldown ให้รอช่วงเวลาหนึ่งหลังปล่อยเวอร์ชันใหม่ (เช่น 7 วัน) จะช่วย ลดความเสี่ยงการติดเชื้อจากการอัปเดตอัตโนมัติได้มาก
- Dependabot, Renovate, pnpm ฯลฯ รองรับ ฟีเจอร์ cooldown เป็นมาตรฐานอยู่แล้ว ตั้งค่าได้ง่ายและไม่มีค่าใช้จ่ายเพิ่ม
- หากมี cooldown เป็นค่าพื้นฐานในระดับ package manager ก็จะช่วย เสริมความปลอดภัยของซัพพลายเชนและลดการแจ้งเตือนที่ไม่จำเป็น ได้
โครงสร้างและปัญหาของการโจมตีซัพพลายเชน
- การโจมตีซัพพลายเชน (supply chain attack) ส่วนใหญ่มีรูปแบบเดียวกัน
- ผู้โจมตีใช้ การขโมยข้อมูลรับรอง หรือ ช่องโหว่ CI/CD เพื่อเข้าถึงโครงการโอเพนซอร์สยอดนิยม
- อัปโหลดการเปลี่ยนแปลงอันตรายไปยังช่องทางเผยแพร่ เช่น PyPI, npm
- ผู้ใช้ติดตั้งเวอร์ชันที่ปนเปื้อนจากการอัปเดตอัตโนมัติหรือการไม่ตรึงเวอร์ชันไว้
- ผู้ให้บริการด้านความปลอดภัยตรวจพบและแจ้งเตือน จากนั้นคลังแพ็กเกจจึงลบเวอร์ชันดังกล่าวออก
- ช่วงระหว่างขั้นตอน (1)~(2) มักยาว แต่ช่วง (2)~(5) จะเกิดขึ้นภายใน ไม่กี่ชั่วโมงถึงไม่กี่วัน ทำให้ช่วงเวลาที่ผู้โจมตีดำเนินการได้ค่อนข้างสั้น
- ช่วงเวลาเปิดโอกาสในการโจมตี (window of opportunity) ของกรณีสำคัญในช่วง 18 เดือนล่าสุด
- xz-utils: ประมาณ 5 สัปดาห์
- Ultralytics: 12 ชั่วโมง (ขั้นที่ 1), 1 ชั่วโมง (ขั้นที่ 2)
- tj-actions: 3 วัน
- chalk: น้อยกว่า 12 ชั่วโมง
- Nx: 4 ชั่วโมง
- rspack: 1 ชั่วโมง
- num2words: น้อยกว่า 12 ชั่วโมง
- Kong Ingress Controller: ประมาณ 10 วัน
- web3.js: 5 ชั่วโมง
- ในกรณีเหล่านี้ 8 เหตุการณ์มี ช่วงเวลาการโจมตีน้อยกว่า 1 สัปดาห์ และส่วนใหญ่สามารถป้องกันได้ด้วย cooldown
แนวคิดและประสิทธิภาพของ cooldown
- cooldown คือวิธีชะลอการใช้งาน dependency ใหม่ออกไปเป็นช่วงเวลาหนึ่งหลังจากถูกเผยแพร่
- ในช่วงนี้ผู้ให้บริการด้านความปลอดภัยจะมีเวลาตรวจจับได้ว่าเป็นอันตรายหรือไม่
- ข้อดี
- มีประสิทธิภาพที่พิสูจน์ได้จากข้อมูลจริง และป้องกันการโจมตีขนาดใหญ่ได้เป็นส่วนใหญ่
- นำไปใช้ได้ง่ายมาก และในเครื่องมือส่วนใหญ่ ตั้งค่าได้ฟรี
- ตัวอย่างของ Dependabot
version: 2 - package-ecosystem: github-actions directory: / schedule: interval: weekly cooldown: default-days: 7 - ช่วยจูงใจให้ผู้ให้บริการด้านความปลอดภัยมีพฤติกรรมเชิงบวก: มุ่งเน้นการตรวจจับอย่างรวดเร็วแทนการแจ้งเตือนเกินจำเป็นหรือการประชาสัมพันธ์
บทสรุปและข้อเสนอแนะ
- การโจมตี 8 จาก 10 กรณีมี ระยะเวลาไม่เกิน 1 สัปดาห์ และสามารถป้องกันได้เป็นส่วนใหญ่ด้วย cooldown 7 วัน
- หากใช้ cooldown 14 วัน จะป้องกันได้ทุกกรณียกเว้น xz-utils
- cooldown ไม่ใช่คำตอบที่สมบูรณ์แบบ แต่เป็น วิธีง่าย ๆ ที่ลดความเสี่ยงจากการเปิดรับได้ 80~90%
- นอกจาก Dependabot และ Renovate แล้ว ยังควรปรับปรุงให้ package manager เองรองรับ cooldown เป็นค่าพื้นฐาน ด้วย
- ความปลอดภัยของซัพพลายเชนไม่ใช่แค่ปัญหาทางเทคนิค แต่ยังเป็นเรื่องของ โครงสร้างความไว้วางใจทางสังคม และ cooldown ก็เป็นมาตรการบรรเทาที่ใช้งานได้จริง
3 ความคิดเห็น
จริง ๆ ถ้าไม่มีปัญหาอะไร ผมว่าการไม่อัปเดตเลยอาจจะดีกว่าด้วยซ้ำ
จำเป็นจริงหรือที่ต้องยอมรับความเสี่ยงเพื่อนำเวอร์ชันใหม่ที่แทบไม่ได้ต่างจากเวอร์ชันก่อนหน้ามาใช้
ความคิดเห็นบน Hacker News
ผู้คนกังวลว่าหากไม่อัปเดตทันทีจะเสี่ยงต่อ ช่องโหว่ร้ายแรง แต่ในความเป็นจริงส่วนใหญ่ไม่เป็นเช่นนั้น
ซอฟต์แวร์จำนวนมากไม่ได้ดีพลอยต่อเนื่อง แต่ให้ลูกค้าติดตั้งเวอร์ชันใหม่เอง จึงอัปเดตกันเป็นรอบหลายสัปดาห์หรือหลายเดือน
สิ่งสำคัญคือ การเฝ้าติดตาม dependencies และตรวจสอบช่องโหว่ที่ถูกเปิดเผย เมื่อประเมินแล้วว่าผลิตภัณฑ์ได้รับผลกระทบจริง ค่อยอัปเดต dependency นั้นทันที
พอมีเวอร์ชันใหม่ออกมาก็มักมีความเชื่อว่าต้องอัปเดตวันนี้ทันที
การไม่ทบทวนการเปลี่ยนแปลงจริง ๆ แล้วใช้แนวคิดแบบ “เดี๋ยวหลังจากนี้จะยิ่งยาก งั้นทำตอนนี้เลย” เป็นวิธีที่ไม่มีประสิทธิภาพ
การพยายามอยู่แถวหน้าสุดของหมายเลขเวอร์ชันอาจ ส่งผลเสีย ต่อความปลอดภัยด้วยซ้ำ
บริษัทของเรากำหนดว่าถ้าสแกนเนอร์พบช่องโหว่ระดับวิกฤต ต้องอัปเดตภายใน 7 วัน
ถ้าเกินกำหนดจะถือว่าผิดข้อกำหนดและเข้าสู่กระบวนการที่ซับซ้อน ดังนั้นคนส่วนใหญ่จึงอัปเดตทุกอย่างทันทีไปเลย
แอปอย่างเบราว์เซอร์ที่มีอินพุตจากภายนอกจำนวนมากควรอัปเดตบ่อย แต่กรณีอย่างแอปพยากรณ์อากาศที่มีอินพุตจำกัดจะปลอดภัยกว่าค่อนข้างมาก
สู้ทำการอัปเดตเป็นประจำแล้วเสริมด้วย มาตรการป้องกัน supply chain attack จะมีประสิทธิภาพกว่า
โมเดลแบบ Debian stable ที่ดิสทริบิวชันจัดการ dependency กลางและอัปเกรดทั้งชุดทุก ๆ หลายปี ดูสมเหตุสมผลขึ้นเรื่อย ๆ
บาง ecosystem เคลื่อนตัวเร็วเกินไป หรือระบบแพ็กเกจของแต่ละดิสทริบิวชันยังอ่อนแอ
ตัวอย่างเช่น การติดตั้งไลบรารี Node.js ผ่าน apt แล้วนำมาใช้ในโปรเจ็กต์ยังคงทำได้ยาก
ecosystem ที่เคลื่อนไหวเร็วโดยไม่มีการเปลี่ยนแปลงเชิงรากฐานไม่ได้ถือว่าสุขภาพดี
JS แทบไม่มีความก้าวหน้าที่เป็นรูปธรรมในช่วง 3 ปีที่ผ่านมา แต่ถ้าจะ build โปรเจ็กต์ที่มีอายุ 3 ปีขึ้นมาใหม่กลับต้องเจอ ความเจ็บปวดระดับรีไรต์
ดิสทริบิวชันอย่าง Arch บางครั้งไม่มีเลย
มีสมมติฐานว่าการกำหนด ช่วงคูลดาวน์ ให้กับการอัปเดต dependency เพื่อ ป้องกัน supply chain attack ดีกว่าการใช้เวอร์ชันล่าสุดทันทีเพื่อป้องกัน 0-day
นี่คือการเปรียบเทียบระหว่างความน่าจะเป็นที่การอัปเดตจะนำช่องโหว่ใหม่เข้ามา กับความน่าจะเป็นที่จะซ่อมช่องโหว่เดิม
ตามเกณฑ์ของ SemVer เวอร์ชันแพตช์ค่อนข้างปลอดภัยกว่า จึงอาจใช้แนวทางให้คูลดาวน์สั้นกว่าได้
เช่น เมื่อจาก 2.3.4 มี 2.4.0 ออกมา หากไม่มีฟีเจอร์เร่งด่วน อาจรอจน 2.4.1 ออกก่อนจะดีกว่า
ช่องโหว่ส่วนใหญ่ไม่ได้มาจากการโจมตีโดยเจตนา แต่เกิดจาก บั๊กทั่วไป
นโยบายที่จำกัด จำนวนและความซับซ้อน ของ dependency เป็นแนวทางที่ทรงพลังยิ่งกว่า
แทนที่จะเพิ่มไลบรารีแบบ “ทำได้ทุกอย่าง” ควรเพิ่มเฉพาะ dependency ที่เล็กและมีจุดประสงค์ชัดเจน
นอกจากนี้ หากไลบรารีมี เวอร์ชัน LTS ที่รวมเฉพาะ security patch ก็จะช่วยให้จัดการได้ง่ายขึ้น
การลงมือเขียนใหม่เองอาจเป็นความสิ้นเปลือง จึงอยากรู้ตัวอย่างที่ชัดเจนของ “everything library” ที่เป็นปัญหา
หากคุณไว้ใจนักพัฒนาคนเดียวกันผ่านหลายไลบรารี สิ่งที่สำคัญกว่าจำนวนแพ็กเกจคือ ความสัมพันธ์แห่งความไว้วางใจ
ความซับซ้อนมีความสัมพันธ์กับช่องโหว่ แต่ไม่ใช่สาเหตุโดยตรง
ตอนนี้จึงเลือกโดยคำนึงถึงการบำรุงรักษาระยะยาวได้ ไม่ใช่ดูแค่ความเร็วระยะสั้น
ในโลกของ C++ จุดนี้บางครั้งกลายเป็นจุดแข่งขันด้วย
แรงกดดันให้ต้องอัปเดตเป็นเวอร์ชันล่าสุดทุกครั้ง มีที่มาจากความเชื่อผิด ๆ ว่า ซอฟต์แวร์จะดีขึ้นเสมอ
ในความเป็นจริง มันอาจเป็นเพียงการเปลี่ยนจากบั๊กที่รู้จักอยู่แล้ว ไปเป็น บั๊กใหม่ที่ยังไม่รู้จัก
การติดตาม issue ที่เปิดเผยไว้และแพตช์เฉพาะเมื่อจำเป็นเป็นแนวทางที่สมเหตุสมผล
นั่นคือกรณีที่เวอร์ชันเก่ากลับปลอดภัยกว่า
อาจเป็นเพราะเรากำลังใช้ซอฟต์แวร์ที่เสถียรมากพออยู่แล้วก็ได้
ถ้าทุกคนบอกว่า “รออีกนิดดีกว่า” สุดท้ายอาจกลายเป็น โศกนาฏกรรมของทรัพยากรร่วม ที่ไม่มีใครลุกขึ้นมาตรวจสอบก่อน
ยิ่งรอนาน หนี้ทางเทคนิค ก็ยิ่งสะสม ดังนั้นจึงต้องมีทั้งการอัปเดตแบบค่อยเป็นค่อยไปและมาตรการบรรเทาอย่าง zero trust และ monitoring
ในช่วงนั้นสแกนเนอร์ด้านความปลอดภัยก็สามารถตรวจพบช่องโหว่ได้แล้ว
หากผู้โจมตีอัปโหลดรีลีสโดยไม่ได้รับอนุญาต บางครั้งก็จะถูกจับได้ทันที
แนวคิดเรื่องคูลดาวน์นั้นดี แต่ก็มีความเสี่ยงที่ผู้โจมตีจะใช้ประโยชน์จากมันเพื่อสร้าง ความเร่งด่วนปลอม
โดยอ้างว่าเป็น “security patch ด่วน” เพื่อหลอกให้ติดตั้งก่อนเวลา ทั้งที่เวอร์ชันนั้นอาจเป็นอันตรายจริง
จึงต้องเตรียมรับมือการโจมตีแบบ กดดันทางจิตวิทยา เช่นนี้
อีกเหตุผลหนึ่งของคูลดาวน์คือการ ให้เวลาผู้ดูแลโครงการรับรู้ด้วยตัวเองว่าถูกเจาะแล้ว
ผู้โจมตีมักเลือกจังหวะที่ผู้ดูแลไม่อยู่ เช่น ช่วงลาพักร้อน งานคอนเฟอเรนซ์ หรือวันหยุดนักขัตฤกษ์
หลายโครงการมีผู้ดูแลเพียงหนึ่งหรือสองคน จึงทำให้ช่วงเวลาที่ต่างกันนี้สำคัญมาก
ทั้งหมดนี้ขึ้นอยู่กับลักษณะของโปรเจ็กต์และ พื้นผิวการโจมตี
ยุคที่เพียงแค่ทำตาม “best practice” แบบสำเร็จรูปควรจบลงได้แล้ว
ความปลอดภัยเป็นเหมือน กีฬาปะทะ ที่ต้องคิดเชิงวิพากษ์กับรายละเอียดทุกวัน
ยังมี ข้อจำกัดของคูลดาวน์ ในมุมที่ว่าแค่ปล่อยเวลาให้ผ่านไปไม่ได้แปลว่าจะปลอดภัยขึ้น
หากไม่มีใครตรวจโค้ดเลย ต่อให้ผ่านไปหนึ่งสัปดาห์ความเสี่ยงก็ยังเท่าเดิม
วิธีอย่าง gradual rollout อาจมีประสิทธิภาพมากกว่า
โดยให้ผู้ใช้แต่ละรายตั้งค่า delay factor ของตนเอง ฝั่งที่รับความเสี่ยงได้สูงจะเจอปัญหาก่อน
ระหว่างนั้นฝั่งที่เหลือก็ได้รับการปกป้อง
อัปเดตที่อันตรายจะถูกนำออกจากคิว ทำให้ผู้ใช้ที่หน่วงเวลาไว้ไม่ต้องเจอมันเลย
ช่วงนี้บางทีก็สับสนเหมือนกันว่า ระหว่างการลงแรงเพื่อประดิษฐ์ล้อขึ้นมาใหม่กับการพยายามจัดการ dependency tetris แบบไหนพอรับมือได้มากกว่ากัน
ถ้า
If for whileผิดไป ก็แค่แก้โค้ดของตัวเอง แต่พอเป็น dependency tetris แล้วจู่ ๆ ไม่รู้ว่าล้อตัวไหนบิดเบี้ยว ก็ทั้งดีบักยากด้วย