“ไม่มีทางป้องกันได้” ผู้จัดการแพ็กเกจเพียงรายเดียวที่เรื่องแบบนี้เกิดขึ้นเป็นประจำกล่าวไว้
(kevinpatel.xyz)- การโจมตีซัพพลายเชนต่อ npm registry ทำให้แอปองค์กรนับล้านและข้อมูลผู้ใช้นับพันล้านรายการถูกเปิดเผย แต่ระบบนิเวศกลับยอมรับเรื่องนี้ราวกับเป็นสิ่งที่หลีกเลี่ยงไม่ได้
- Senior Frontend Engineer Mark Vance เสียดสีความจริงที่ว่าการแปลงสตริงให้เป็นตัวพิมพ์ใหญ่ยังต้องพึ่งพา dependency แบบซ้อนลึก 40 ชั้นจากแพ็กเกจที่ไม่ผ่านการตรวจสอบ
- การที่แพ็กเกจยูทิลิตีซึ่งถูกปล่อยทิ้งร้างมานานถูกยึดไปใช้เพื่อฝัง crypto-miner ลงในโปรดักชันบิลด์ทั่วโลก ถูกปฏิบัติราวกับเป็นภัยพิบัติทางธรรมชาติ
- ระบบนิเวศ Node.js ยอมรับ remote code execution ที่เป็นอันตรายเหมือนเป็นโศกนาฏกรรมที่คาดเดาไม่ได้ ขณะที่ทีม DevOps ก็วุ่นอยู่กับการหมุน AWS key
- ระบบนิเวศของ Go, Rust และ Native Web API เป็นภาพเปรียบเทียบที่ตัดกัน โดยอาศัย standard library ที่แข็งแรงและการตรวจสอบเชิงเข้ารหัสเพื่อลดการพึ่งพา third-party
เสียดสีการโจมตีซัพพลายเชนของ npm
- การโจมตีซัพพลายเชนของ npm registry ทำให้แอปพลิเคชันองค์กรนับล้านรายการถูกเจาะและข้อมูลผู้ใช้นับพันล้านรายการรั่วไหล แต่เหล่านักพัฒนาในระบบนิเวศ JavaScript กลับรับมันเหมือนเป็นเรื่องที่ “หลีกเลี่ยงไม่ได้โดยสิ้นเชิง”
- Senior Frontend Engineer Mark Vance มองว่าการต้องพึ่ง dependency tree แบบซ้อนลึก 40 ชั้นจากแพ็กเกจที่ไม่ผ่านการตรวจสอบ เพียงเพื่อทำให้สตริงเดี่ยวกลายเป็นตัวพิมพ์ใหญ่ คือราคาที่ต้องจ่ายของการพัฒนาเว็บแอปสมัยใหม่
- สถานการณ์ที่แพ็กเกจยูทิลิตีซึ่งถูกปล่อยทิ้งไว้นานถูกยึดและใช้ฝัง crypto-miner ลงในโปรดักชันบิลด์ทั่วโลก ถูกปฏิบัติราวกับเป็นภัยธรรมชาติ
- ระบบนิเวศ Node.js ยอมรับ remote code execution ที่เป็นอันตรายเหมือนเป็นโศกนาฏกรรมที่คาดเดาไม่ได้ และส่ง “ความคิดและคำภาวนา” ไปยังทีม DevOps ที่กำลังยุ่งกับการเปลี่ยน AWS key
ความต่างระหว่างระบบนิเวศอื่นกับ npm
- ระบบนิเวศของ Go, Rust และ Native Web API มี standard library ที่แข็งแรง จึงลดการพึ่งพาโค้ดจาก third-party ได้มาก และยังมีการตรวจสอบเชิงเข้ารหัสอย่างเข้มงวดใน toolchain หลัก
- เมื่อเทียบกันแล้ว ในระบบนิเวศเหล่านั้นยังสามารถพูดได้ว่า “วันนี้มี 0 ครั้งที่โปรเจกต์สุดสัปดาห์ของคนลาออกจากมหาวิทยาลัยมาทำลายโครงสร้างพื้นฐานโลจิสติกส์ระดับโลก”
- โฆษกของ npm ย้ำว่าในโลกที่มีผู้ไม่หวังดีอยู่ ก็ต้องยอมรับเรื่องนี้ และไม่มีนโยบายของ registry หรือ guardrail แบบ build sandbox ที่จะป้องกันได้
- npm registry ถูกวาดภาพว่าเป็นโอเพนซอร์ส registry ที่รัน install script ตามค่าเริ่มต้นบนเครื่องโลคัล ทำให้คำพูดของโฆษกสอดรับกับความเสี่ยงเชิงโครงสร้าง
- ตอนท้ายกล่าวปลอบใจผู้เสียหาย แต่ก็ปิดด้วยน้ำเสียงทำนองว่าต้องรักษาความยืดหยุ่นไว้จนกว่าจะถึง “การเจาะระบบที่หลีกเลี่ยงไม่ได้ครั้งถัดไป” ในเช้าวันพรุ่งนี้
1 ความคิดเห็น
ความเห็นจาก Hacker News
แต่ละคนอาจมีความเห็นต่างกันเรื่อง cooldown แต่การโจมตี supply chain ของ npm จำนวนมากในช่วงหลัง รวมถึง axios, tanstack น่าจะหลีกเลี่ยงได้ด้วย cooldown
ถ้าใช้ Artifactory / Nexus ก็มีโอกาสสูงว่าอาจมี cooldown อยู่แล้ว และถ้ายังไม่มีก็ตั้งค่าได้ง่าย
การเจาะ npm หรือ PyPI ส่วนใหญ่ถูกถอดลงภายในไม่กี่ชั่วโมง ดังนั้น cooldown จึงหมายถึง “เมินแพ็กเกจที่เพิ่งปล่อยออกมาไม่เกิน N วัน” แม้ 1 วันก็ช่วยได้, 3 วันก็กำลังดี, และ 7 วันอาจจะเยอะไปหน่อยแต่ก็ใช้ได้
วิธีตั้งค่าคือใช้ pnpm รุ่นล่าสุดที่มี cooldown เริ่มต้น 1 วันอยู่แล้ว https://pnpm.io/supply-chain-security หรือถ้าอยากแก้ทีเดียวจบก็ใช้ https://depsguard.com ที่ใส่ cooldown และค่าที่แนะนำให้กับ npm, pnpm, yarn, bun, uv, dependabot ได้เลย ผมเป็นผู้ดูแลมันเอง
หรือจะใช้ https://cooldowns.dev ที่เน้นเรื่อง cooldown มากกว่า และมีสคริปต์ช่วยตั้งค่าในเครื่องด้วย ทั้งหมดเป็นโอเพนซอร์สหรือใช้ฟรี
ถ้าคุณแก้
~/.npmrcเองได้ก็อาจไม่จำเป็น แต่สำหรับคนรอบตัวที่ต้องการวิธีแก้แบบคลิกเดียว มันน่าจะช่วยให้รอดจากการโจมตีครั้งถัดไปได้เพียงแต่เวลาต้องแพตช์ CVE ร้ายแรงตัวใหม่ ก็ต้องมีวิธีข้าม cooldown ซึ่งแต่ละตัวก็มีวิธีข้ามของมันเอง ไม่มีตัวเลขที่แม่นยำ แต่ในช่วงไม่กี่สัปดาห์ที่ผ่านมา ดูเหมือนความเสี่ยงจาก การโจมตีซัพพลายเชนซอฟต์แวร์ จะมากกว่า CVE zero-day ตัวใหม่
การอัปเกรด dependency ตามรอบก็เหมือนกัน ยกเว้นกรณีที่ต้องรีบอัปเพราะช่องโหว่ ซึ่งตอนนั้นผมก็คิดว่าให้ dev ระบุเวอร์ชันใหม่ที่ต้องการแบบชัดเจนไปเลยก็ได้
ถ้าทุกคนตั้ง cooldown 7 วัน มันก็แค่ระเบิดช้าลงไม่ใช่หรือ?
แต่พอพิมพ์ไปก็คิดเพิ่มว่า ถึงอย่างนั้นผมก็เห็นด้วยกับ cooldown 10 วัน ที่จะไม่ติดตั้งอะไรที่ออกมาในช่วง 10 วันที่ผ่านมา เพียงแต่ไม่ควรคาดหวังว่านี่จะเป็นมาตรการบรรเทาเพียงอย่างเดียว
สงสัยว่า Go หรือ Rust ให้การรับประกันอะไรจริง ๆ มากกว่า Python/npm บ้าง หรือจริง ๆ แล้ว Python/npm แค่เป็นเป้าหมายที่ล่อตาล่อใจกว่า
ตอนนี้ยิ่งพยายามหลีกเลี่ยงแพ็กเกจ third-party ทั้งหมดมากขึ้นเรื่อย ๆ
Maven Central มีมาหลายสิบปีแล้ว แต่เหตุการณ์ขโมย namespace เกิดขึ้นน้อยมาก
คุณจะอัปโหลดแพ็กเกจด้วย groupId
com.ycombinatorไม่ได้ ถ้าไม่มีการยืนยันว่าคุณเป็นเจ้าของโดเมน ycombinator.com และเมื่อแพ็กเกจถูกอัปโหลดแล้ว มันจะ immutable 100% แม้จะมีโค้ดอันตรายอยู่ข้างในก็ตาม แน่นอนว่าไลบรารีแบบนั้นจะถูกติดป้ายว่าเปราะบางในหลายที่ผมไม่เข้าใจว่า NPM ใช้เวลานานขนาดนี้แล้วยังลอกมาตรการป้องกันแบบ Maven Central ไม่ได้อย่างไร
แทนที่จะมีชุดไลบรารีที่ผ่านการตรวจสอบและมากับภาษา แอปพลิเคชันกลับต้องเขียนเองหรือดึงมาจากคลังแพ็กเกจ third-party และเพราะเราถูกสอนมาตลอดให้หลีกเลี่ยง NIH ผู้คนเลยมักหยิบแพ็กเกจมาใช้
มันไม่ใช่เรื่องแย่เสมอไป แต่บ่อยครั้งก็ดึงโค้ดเข้ามามากเกินความจำเป็น ระบบนิเวศ JS ยังนิยมโมดูลเล็ก ๆ จึงต้องใช้หลายโมดูล และทุกคนก็ซ้อนทับกันต่อไปจนกราฟ dependency ใหญ่มหาศาล ไม่ว่าจะตั้งใจหรือไม่ พื้นที่เสี่ยงต่อปัญหาก็มหาศาลเกินไป
ภาษาอื่นมีของที่ให้มาพร้อมใช้มากกว่า ไม่ใช่ว่าไม่เคยมีบั๊กหรือปัญหาความปลอดภัย แต่เมื่อเทียบกับที่เห็นในระบบนิเวศ JS แล้วถือว่าน้อยมาก กราฟ dependency ภายนอกก็เล็กกว่ามาก และฟังก์ชันหลักก็มาจาก third party ที่เชื่อถือได้
นี่ไม่ใช่ข้อแก้ตัวให้ NPM กลับยิ่งเป็นอีกปัจจัยที่ทำให้ NPM เสียเปรียบ
จะบอกว่านี่สะท้อนความต่างระหว่างนักพัฒนาฟรอนต์เอนด์กับแบ็กเอนด์ได้ชัดขึ้นอีกก็ได้ แต่ผมจะไม่ไปไกลถึงขั้นนั้น
หลายที่ทำงานของผมต้องลำบากกับการติดตั้งค่าตั้งต้น npm แบบปลอดภัยระดับ global ลงในเครื่องนักพัฒนาทุกคน ขอร้องไม่ให้ปิด และใช้เครื่องมือ MDM คอยตรวจสอบ
ค่าตั้งต้นที่ปลอดภัยกว่า ควรมีมาตั้งนานแล้ว
ไม่มีเหตุผลที่ชอบธรรมที่ postinstall script ควรมีอยู่ ทีม npm ควรโตพอแล้วที่จะประกาศว่า “ตั้งแต่ npm เวอร์ชันใดก็ตาม จะรัน postinstall script เฉพาะกับเวอร์ชันแพ็กเกจที่ถูก publish ก่อน ${today} เท่านั้น”
เพราะ toolchain ฝั่ง dev อย่าง esbuild ถูกเขียนด้วยภาษาแบบคอมไพล์แล้วแจกเป็นไบนารีผ่าน npm registry ถ้าคุณใช้ Node/npm รุ่นใหม่และ OS/แพลตฟอร์มยอดนิยมที่ค่อนข้างใหม่ ก็น่าจะปิด postinstall script ทั้งหมดได้โดยไม่เกิดปัญหาที่ชอบธรรม
โค้ด npm ที่ติดตั้งแล้วแทบทั้งหมดก็ถูกนำไปรันอยู่ดี
แต่โค้ดปกติภายในแพ็กเกจก็เหมือนกัน ถึงจะไม่ถูกรันตอนติดตั้ง แต่สุดท้ายอะไรบางอย่างข้างในก็ต้องถูกรัน ไม่อย่างนั้นมันก็คงไม่ถูกใส่เป็น dependency ตั้งแต่แรก
การคิดว่าการเอา postinstall script ออกจะส่งผลต่ออัตราการถูกโจมตีได้มากกว่าชั่วครู่ เป็นสัญญาณว่ายังคิดปัญหานี้ไม่สุด น่าเสียดายที่เรื่องนี้ซับซ้อนกว่าที่บทความต้นฉบับชวนให้คิดมาก
มันไม่ใช่ปัญหาแบบ “อย่าเอาปุ่มปล่อยปีกเครื่องบินไปไว้ข้างสวิตช์ไฟ” แต่แก่นจริง ๆ คือเราไม่มีวิธีแยก “โค้ดแย่ของคนอื่นที่เราไม่อยากให้มารันบนเครื่องเรา” ออกจาก “โค้ดดีของคนอื่นที่เราอยากให้มารันบนเครื่องเรา” ได้เลย ถ้าไม่ใช้แรงคนจำนวนมากตรวจด้วยมือ และเหตุผลที่เรายอมรันโค้ดคนอื่นก็เพื่อหลีกเลี่ยงงานตรวจด้วยมือนั่นเอง
ทุกโปรเจกต์ Node.js เริ่มต้นด้วย
npm installแล้วจู่ ๆ ก็มี แพ็กเกจ 500 ตัว โผล่มา ทั้งที่ครึ่งหนึ่งไม่มีใครแตะมาหลายปีแล้วมี ปัญหาเชิงวัฒนธรรม ที่อยากอัปทุกอย่างให้เป็นแพ็กเกจใหม่ล่าสุด ทั้งที่ของเดิมก็ทำงานดีอยู่แล้ว บางทียังไม่อ่าน changelog ด้วยซ้ำว่าการเปลี่ยนนั้นเกี่ยวข้องไหม
cooldown เป็นแค่วิธีบังคับให้ผู้ดูแลโครงการอดทนขึ้นอีกนิด และมันก็ได้ผลจริง
แพ็กเกจ Lisp ใช้แบบไม่เปลี่ยนอะไรมา 15 ปีก็ยังได้ แต่แพ็กเกจ JS ถ้าไม่ได้ดูแลจะถูกมองเหมือนเป็นเรื่องใหญ่ ทั้งที่จริงมันอาจเสร็จสมบูรณ์ตั้งแต่ 15 ปีก่อนแล้ว แต่เพื่อให้ดูเหมือนยังมีการดูแลบน npm และ GitHub ก็เลยออกเวอร์ชันใหม่ทั้งที่ไม่ได้เพิ่มอะไร หรือบางทีก็ใส่ breaking change เข้าไปด้วย สุดท้ายทุกอย่างก็ต้องอัปเดตตาม
cooldown 7 วัน ดูเหมือนมาตรการเฉพาะหน้าที่ติดได้ด้วยแรงน้อย ทางแก้จริงคงเป็น reproducible builds กับ signed attestations แต่ทีมส่วนใหญ่คงไม่ยอมจ่ายต้นทุนนี้จนกว่าจะโดนเข้ากับตัวก่อน
อ่านแล้วเหมือนบทความของ Onion มาก
ลิงก์นี้เป็นเวอร์ชันที่เอามุกที่ Xe Iaso เล่นมานานไป ฟอกด้วย AI แบบเห็นชัด เสียดาย
https://xeiaso.net/shitposts/no-way-to-prevent-this/CVE-2024...
https://news.ycombinator.com/item?id=40438408
[0]: https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_...
https://en.wikipedia.org/wiki/%27No_Way_to_Prevent_This,%27_...