- นักวิจัยได้แบ่งปันประสบการณ์ที่ค้นพบ ช่องโหว่ใน Nixpkgs ซึ่งอาจถูกใช้เพื่อฉีดโค้ดอันตรายเข้าสู่ระบบนิเวศ Nix ทั้งหมดได้
- อธิบายความเสี่ยงเชิงโครงสร้างของ GitHub Actions ที่เมื่อใช้ทริกเกอร์
pull_request_target จะทำให้ PR จากผู้มีส่วนร่วมภายนอกก็เข้าถึงสิทธิ์สำคัญและซีเคร็ตได้
- พิสูจน์ให้เห็นจริงว่าใน งานตรวจสอบ editorconfig-checker และ code owners สามารถยกระดับสิทธิ์ได้ผ่านการแทรกคำสั่งและการใช้ symbolic link
- หลังตรวจพบ ทีมดูแล Nixpkgs ได้แก้ไขช่องโหว่อย่างรวดเร็ว พร้อมปิดใช้งานเวิร์กโฟลว์ที่เปราะบางและทบทวนการจัดการสิทธิ์ใหม่
- เน้นย้ำความสำคัญของการ แยกข้อมูลที่ไม่น่าเชื่อถือออกจากการปฏิบัติการที่อ่อนไหวในโครงสร้าง CI/CD, ให้สิทธิ์เท่าที่จำเป็น, และเสริมความเข้มงวดของนโยบาย
แฮ็กระบบนิเวศ Nix ทั้งหมด
บทนำและพื้นหลัง
- ในงาน NixCon ปีที่แล้ว นักวิจัยและเพื่อนร่วมงาน Lexi ได้นำเสนอ ช่องโหว่ของ Nixpkgs
- ช่องโหว่นี้เปิดโอกาสให้เกิด การโจมตีห่วงโซ่อุปทาน ที่อาจแทรกโค้ดอันตรายเข้าสู่ระบบนิเวศ Nix ทั้งหมดได้
- เป็นประสบการณ์การตอบสนองอย่างรวดเร็วตั้งแต่การตรวจพบ การแจ้ง ไปจนถึงการรับมือ ภายในเวลาเพียงหนึ่งวัน
- นักวิจัยไม่สามารถเข้าร่วม NixCon ในปีนี้ได้ จึงเขียนบทความนี้เพื่อสรุปกระบวนการอย่างละเอียด
GitHub Actions: เป้าหมายที่เปราะบาง
- GitHub Actions เป็นระบบที่รองรับงานอัตโนมัติต่าง ๆ (CI/CD) ในที่เก็บโค้ด
- หากเข้าถึงไฟล์เวิร์กโฟลว์ได้ ก็สามารถแทรกโค้ดได้ง่าย ทำให้มันกลายเป็นเป้าหมายหลักของ การโจมตีห่วงโซ่อุปทาน
- ไฟล์เวิร์กโฟลว์เขียนด้วย YAML และไม่ได้เป็นฟอร์แมตที่ออกแบบมาเพื่อการรันโดยตรง จึงอาจมีช่องโหว่ด้านความปลอดภัยที่คาดไม่ถึง
- ตัวอย่างง่าย ๆ คือเวิร์กโฟลว์ที่รันคำสั่งเมื่อมีการ push โค้ด
ทริกเกอร์ pull_request_target ที่อันตราย
- GitHub Actions มีหลายทริกเกอร์ และในนั้น pull_request_target แตกต่างจาก pull_request ทั่วไปอย่างมาก
pull_request_target นั้น แม้มาจาก fork (PR) ก็ยังมีสิทธิ์ read/write และเข้าถึงซีเคร็ตได้โดยปริยาย
- หากใช้ทริกเกอร์นี้ผิดวิธี จะทำให้ ข้อมูลภายนอกที่ไม่น่าเชื่อถือ ถูกผูกเข้ากับสิทธิ์ที่อ่อนไหว
- เอกสารทางการของ GitHub ก็เตือนถึงความเสี่ยงนี้ไว้อย่างชัดเจน
- นักวิจัยได้ตรวจสอบ 14 เวิร์กโฟลว์ ในที่เก็บ Nixpkgs ที่ใช้ pull_request_target
ช่องโหว่ของ editorconfig-checker
- เวิร์กโฟลว์ที่เปราะบางตัวแรกที่พบ มีจุดประสงค์เพื่อ ตรวจสอบกฎของ editorconfig
- หลังจากคำนวณรายชื่อไฟล์ที่เปลี่ยนแปลงแล้ว จะส่งต่อให้ editorconfig-checker ผ่าน xargs
- หากละเลย คำเตือนด้านความปลอดภัยของคำสั่ง xargs จะเกิดช่องโหว่ที่เปิดให้แทรกชื่อไฟล์ที่ออกแบบมาอย่างมุ่งร้ายได้ (เช่น
--help)
- วิธีนี้อาจใช้เพื่อควบคุมการทำงานของ editorconfig-checker ตามอำเภอใจ หรือเปิดทางให้รันคำสั่งเพิ่มเติมได้ (การวิเคราะห์เชิงลึกยังต้องตรวจสอบเพิ่ม)
ช่องโหว่ของ codeowners-validator: การรวมไฟล์โลคัล
- ช่องโหว่ลำดับที่สองซึ่งร้ายแรงกว่าถูกพบใน เวิร์กโฟลว์ตรวจสอบไฟล์ CODEOWNERS
- กระบวนการนี้จะ checkout โค้ดของ PR แล้วใช้ codeowners-validator เพื่อตรวจสอบไฟล์
- ผู้ส่ง PR สามารถแทนที่ไฟล์ OWNERS ด้วย symbolic link เพื่ออ้างอิงไฟล์ใดก็ได้บน runner (เช่น credentials ของ action)
- ผลลัพธ์คือเมื่อทำการตรวจสอบ เนื้อหาของไฟล์นั้นจะถูกพิมพ์ออกมาในล็อก ทำให้ GitHub token ที่มีสิทธิ์ read/write รั่วไหล
- เมื่อได้โทเค็นนี้แล้ว ก็สามารถ push ตรงเข้าไปยังที่เก็บ Nixpkgs ได้
มาตรการและบทเรียน
- หลังมีการรายงานช่องโหว่ เมนเทนเนอร์ของ Nixpkgs อย่าง infinisil ได้ตอบสนองทันที
- ปิดใช้งานเวิร์กโฟลว์ที่เปราะบางชั่วคราว
- แก้ไขและแยกส่วนที่เชื่อมโยงข้อมูลที่ไม่น่าเชื่อถือกับสิทธิ์ออกจากกัน
- หลังแก้ไขด้านความปลอดภัยแล้ว ได้เปลี่ยนชื่อเวิร์กโฟลว์เพื่อลดปัญหา branch targeting
- บทเรียนสำคัญ
- ห้ามอย่างเด็ดขาด หรืออย่างน้อยต้องระวังอย่างยิ่งในการผูก ข้อมูลที่ไม่น่าเชื่อถือ เข้ากับซีเคร็ตและงานที่อ่อนไหว
- ปฏิบัติตามหลัก ให้สิทธิ์เท่าที่จำเป็น
- ต้องทำความเข้าใจ คู่มือทางการเกี่ยวกับสิทธิ์ของ GitHub Actions
- หากเกิดช่องโหว่ลักษณะคล้ายกัน ผู้ดูแลองค์กรสามารถปิดใช้งาน Actions ทั้งหมดได้จากนโยบายส่วนกลาง (มีคำแนะนำวิธีตั้งค่า)
บทสรุป
- นักวิจัยค้นพบ รายงาน และมีส่วนร่วมในการแก้ไข ช่องโหว่ที่อาจทำให้ระบบนิเวศ Nix ทั้งหมดตกอยู่ในความเสี่ยง ภายในเวลาเพียงวันเดียว
- กรณีนี้แสดงให้เห็นว่าต้องใช้ความระมัดระวังเป็นพิเศษกับ GitHub Actions โดยเฉพาะเมื่อใช้ pull_request_target
- ผู้เขียนขอบคุณ Intrigus จาก KITCTF ที่ช่วยงานวิจัย และ infinisil ที่ตอบสนองอย่างรวดเร็ว
- ผู้อ่านที่สนใจแนะนำให้ดูวิดีโอบรรยายและเอกสารเพิ่มเติม
- โดยรวมแล้ว บทความนี้เน้นย้ำถึง ความสำคัญและบทเรียนเชิงปฏิบัติของการดูแลความปลอดภัย GitHub Actions
1 ความคิดเห็น
ความคิดเห็นใน Hacker News
ผมคิดว่า
pull_request_targetมีปัญหาด้านความปลอดภัยในเชิงโครงสร้าง และ GitHub ควรลบฟีเจอร์นี้ออกไปเลย โดยปกติจะบอกกันว่าถ้าอยากใช้pull_request_targetอย่างปลอดภัย ก็แค่ไม่รันโค้ดที่ควบคุมจาก branch ระหว่างงานก็พอ แต่ในความเป็นจริง พื้นที่โจมตีกว้างกว่านั้นมากจากเรื่องอย่าง argument injection หรือ local file inclusion ตอนนี้ use case ที่ดูสมเหตุสมผลจริง ๆ มีแค่การติด label อัตโนมัติหรือคอมเมนต์อัตโนมัติให้ PR จาก third-party เท่านั้น และผมไม่คิดว่างานแบบนี้ควรได้สิทธิ์เขียนลง repository โดยปริยาย GitHub ควรออกโทเคนที่จำกัดเฉพาะงานนั้นได้ ดังนั้นใน zizmor ผมเลยตั้งให้มีการ flag ทุกกรณีที่ใช้ trigger อันตรายอย่างpull_request_targetดูได้ที่ zizmor dangerous triggerspull_requestไม่ถูก trigger แบบนี้pull_request_targetแทบเป็นทางเลือกเดียว ผมอยากให้ github ทำ setting ที่ให้รัน workflow กับ PR ที่ merge ไม่สะอาดได้ โดยปิดเป็นค่าเริ่มต้นและเปิดใช้เฉพาะกับงานอย่าง linter จนกว่าจะมีแบบนั้น ข้อจำกัดนี้ทำให้ต้องจำใจใช้pull_request_targetซึ่งน่าเสียดายมาก อ้อ แล้วเวลาใช้เครื่องมือภายนอกแบบนี้ ถ้าไป merge ด้วยมือใน github flow ทั้งหมดจะพัง ห้าม merge ด้วยมือตามตัวอักษรเลยpull_request_targetด้วยสองเหตุผล อย่างแรกคือ workflow จะรันโดยอิงจาก main เสมอ จึงกันโค้ดทดสอบที่ยังไม่ได้ตรวจสอบได้ อย่างที่สองคือใน sub claim ของ jwt ภายใน workflow จะมีjob_workflow_refแบบชี้ขาด ทำให้ระบบที่อิง OIDC สามารถทำ access control แบบละเอียดได้pull_request_targetจะรันใน base context ของ PR จึงควรป้องกันไม่ให้โค้ดมุ่งร้ายขโมย repo หรือ secrets ได้ แต่พอรู้ว่าการทำ secrets รั่วจริง ๆ ง่ายแค่ไหน สถานการณ์นี้ก็ดูน่าขำอยู่เหมือนกันถ้าทีม Nix ได้นำ reproducible builds ที่ signed อย่างอิสระมาใช้ตาม RFC ที่ผมเสนอ โดยไม่ผูกกับ signed commit/review การโจมตี supply chain ระยะสุดท้ายแบบนี้ก็คงเป็นไปไม่ได้ สุดท้ายแล้ว NixPkgs อยากให้ใคร ๆ ก็แก้ไขได้ง่าย และกลัวว่าความพยายามด้านความปลอดภัยจะไล่อาสาสมัครหนี จึงมุ่งไปที่ distro สำหรับงานอดิเรกเป็นหลัก ซึ่งก็ไม่เป็นไร แต่ถ้าจะสื่อสารคุณลักษณะนี้ให้ชัดและปกป้องสิ่งที่มีคุณค่า ก็ควรเลิกใช้หรือแนะนำ Nix ในสภาพแวดล้อมที่สำคัญต่อความปลอดภัย ระบบปฏิบัติการที่ปกป้องบางอย่างจริง ๆ ควรบังคับใช้ two-party hardware signing อย่างเข้มงวดกับทุกการเปลี่ยนแปลง และไม่ควรฝากความเชื่อถือไว้กับคอมพิวเตอร์เครื่องเดียวหรือคนเพียงคนเดียว นั่นจึงเป็นเหตุผลที่ผมสร้าง Stagex ลิงก์ Stagex ลิงก์ Codeberg
independent signed reproducible buildsเหมือนจะไม่เคยเห็นใน PR และสำหรับขนาดระดับ nixpkgs การจะให้ third-party มาสร้าง infrastructure แบบนี้ถือว่าใหญ่มาก ถึงอย่างนั้น NixOS ก็แทบจะมุ่งไปสู่ reproducible build แบบสมบูรณ์อยู่แล้วลิงก์สถานะ และใกล้มากแล้วแม้ยังไม่ถึง 100% สรุปคือ signed commit น่าจะช่วยเรื่องความปลอดภัยที่ดีขึ้น แต่กับ nixpkgs มันก็มีผลลบไม่น้อย จึงยังทำได้ยากในตอนนี้ ส่วนแนวทาง two-party hardware signing ของ Stagex ผมสนใจ อยากให้เล่าเพิ่ม สุดท้ายผมยอมรับว่าผลงานของ Stagex ทั้งมีประสิทธิผลและน่าสนใจ แค่อยากแก้ความเข้าใจผิดบางจุดทั้งที่เราออกแบบระบบคอมพิวเตอร์กันมาแบบดั้งเดิม แต่ทุกวันนี้ workflow ก็ยังออก bearer token ให้โปรแกรมที่เชื่อถืออยู่ดี แม้จะเป็นโทเคนอายุสั้นก็ตาม เรื่องนี้ทำให้ผมงงมาก ถ้า GitHub Actions framework ให้แค่การเข้าถึง privileged Unix socket หรือ ssh-agent ช่องโหว่แบบนี้ก็คงถูกใช้ประโยชน์ได้ยากกว่านี้มาก
action สำหรับ CI/CD บน pull/merge request นี่เป็นฝันร้ายจริง ๆ ตอนที่นักพัฒนาเขียนขั้นตอนทดสอบหรือยืนยันความถูกต้อง ส่วนใหญ่จะคิดว่า “โค้ดนี้รันใน context ของบัญชี github/gitlab ของฉัน” ซึ่งใช้ได้กับ commit ของตัวเองหรือของเพื่อนร่วมทีม แต่ใน PR นั้น CI/CD pipeline กำลังรันโค้ดที่ไม่น่าเชื่อถือ การตระหนักถึงความต่างนี้ให้แม่นยำตลอดเวลาเป็นเรื่องยาก และถ้าเป็นแค่การรันทดสอบหรือลินเตอร์ก็ยังพอไหว แต่พอต้องเชื่อมกับ infrastructure และต้องใช้สิทธิ์มากขึ้น มันก็อันตรายได้อย่างรวดเร็ว
pull_requestกับpull_request_target) ตัวหนึ่ง (เช่นpull_request) ปลอดภัยแทบตลอดถ้าไม่ได้ใช้ผิดแบบตั้งใจ ส่วนอีกตัว (เช่นpull_request_target) แทบไม่มีทางใช้ให้ปลอดภัยได้ ปัญหาใหญ่กว่านั้นคือ GitHub ทำให้งานธรรมดาอย่างติด label ให้ PR หรือคอมเมนต์อัตโนมัติ ทำได้เฉพาะผ่าน trigger อันตราย (pull_request_target) เท่านั้น ทุกคนเลยถูกบีบให้เลือกทางที่ไม่มั่นคง โครงสร้างของ GitHub Actions เองชวนให้เกิดความผิดพลาดยิ่งเวลาผ่านไป ผมก็ยิ่งกังวลเรื่อง supply chain attack มากขึ้นเรื่อย ๆ ไม่ใช่แค่ระดับ “ฉันจะตกงานเพราะเรื่องนี้ไหม” หรือ “เกิด attack vector ใหม่ใน NixOS, CI/CD, Node ฯลฯ” แต่เป็นความกังวลเชิงปรัชญามากกว่า ยิ่งพึ่งพามากก็ยิ่งมีปัญหาให้ต้องจัดการมากขึ้น แม้แต่สิ่งที่ตั้งใจใช้ให้สบายใจก็ยังซับซ้อนเกินไปแล้ว—VSCode, Emacs, Nix, Vim, Firefox, JS, Node, ปลั๊กอินทั้งหมดกับแพ็กเกจที่พึ่งพากันยุ่งเหยิงไปหมด เพราะงั้นอย่างน่าอาย ผมเริ่มเข้าใกล้ข้อสรุปแปลก ๆ ว่าจะรู้สึกควบคุมได้หรือปลอดภัยได้ ก็ต้องกลับไปใช้กระดาษกับเทคโนโลยีที่น้อยที่สุดและเรียบง่ายจริง ๆ ผมรู้ว่ามันไม่สมเหตุสมผล แต่ยิ่งรู้สึกเอือมกับความซับซ้อนนี้มากขึ้นทุกที ตอนนี้เริ่มรู้สึกถึงขีดจำกัดของความซับซ้อนแล้วด้วย
ถ้าดูคำเตือนใน man page ของ xargs จะมีข้อความว่า “xargs ไม่สามารถใช้อย่างปลอดภัยได้” แต่ประเด็นด้านความปลอดภัยนี้ไม่เหมือนกับกรณีที่นำมาใช้ที่นี่ ในกรณีนี้แค่เติม -- ไว้ท้ายสุดก็หลีกเลี่ยงได้เพียงพอ
ประโยค “xargs ไม่สามารถใช้อย่างปลอดภัยได้” มักถูกเข้าใจผิด ตัวอย่างเช่น ถ้ารัน
cat "$HOME/changed_files" | xargs -r editorconfig-checker --ก็สามารถแก้ปัญหาเฉพาะกรณีนี้ได้<div>{escapeHtml(value)}</div>ทีละจุดให้ทุกค่า html ถ้าต้องคอยเอาวิธีใช้แบบปลอดภัยไปใส่เองทุกแห่ง แปลว่าวิธีนี้ผิดตั้งแต่ต้น--และการใช้ xargs ส่วนใหญ่ก็เสี่ยงต่อ argument injection พูดอีกแบบ มันคล้ายกับการที่การรันคำสั่งใด ๆ ล้วนมีความเสี่ยงโดยเนื้อแท้ ปัญหาไม่ใช่ความผิดของ xargs เอง แต่เป็นความจริงที่ว่าเครื่องมือถูกนำไปใช้ซ้ำในหลาย context ของสิทธิ์ที่ต่างกันในบทความนั้นยังมีช่องโหว่ร้ายแรงที่ส่งผลกว้างกว่านี้มาก: สามารถเปลี่ยนไฟล์ OWNERS ในโค้ด PR ให้เป็น symlink แล้วเปิดเผยไฟล์อะไรก็ได้ เช่นไฟล์ credentials ของ github actions เพราะ git รองรับการ commit softlink จึงเกิดความเสี่ยงลักษณะนี้ได้กับแทบทุก workflow
pull_request_targetcredentials จะเป็นของ target repo หรือก็คือ repo ปลายทางที่ถูกรวมเข้าไป ถ้ารันด้วยpull_requestจะเป็น credentials ของ source repo ที่ผู้โจมตีควบคุมข่าวดีอย่างเดียวก็คือ OpenBSD และ NetBSD ยังใช้ CVS สำหรับการจัดการแพ็กเกจอยู่ จึงไม่ได้รับผลจากช่องโหว่นี้ ส่วน FreeBSD ผมไม่แน่ใจ ความปลอดภัยในแง่หนึ่งก็คือได้อาศัยความคลุมเครือบังไว้ แต่ก็ดูเหมือนว่าโปรเจกต์เหล่านั้นกำลังคิดเรื่องย้ายไปใช้ git อยู่เหมือนกัน และ OpenBSD น่าจะไปทาง got(1)