สรุปสั้น

บริษัท Bloom Security เปิดเผยช่องโหว่ชื่อ SlugFest ในฟีเจอร์ Trusted Publishing ของ npm ซึ่งเปิดทางให้ผู้โจมตียึดสิทธิ์ปล่อยเวอร์ชันใหม่ของแพ็กเกจได้กว่า 1,500 แพ็กเกจ ที่มียอดดาวน์โหลดรวมกว่า 10 ล้านครั้ง และกว่า 1 ล้านครั้งต่อเดือน ต้นเหตุคือการตั้งค่า Trusted Publishing ที่ยังไม่เคยปล่อยแพ็กเกจสำเร็จครั้งแรกจะผูกแพ็กเกจกับชื่อ repo GitHub แบบข้อความ (slug เช่น organization/project) แทนรหัสถาวรของ repo และเจ้าของ เมื่อ repo ถูกเปลี่ยนชื่อ ย้าย ลบ หรือเจ้าของเปลี่ยนชื่อบัญชี ผู้โจมตีจึงจดชื่อเดิมไปสร้าง repo ใหม่แล้วรับโทเคนปล่อยแพ็กเกจที่ npm ยอมรับได้

การโจมตีแบบนี้ไม่ต้องขโมยโทเคน npm ไม่ต้องเจาะบัญชีผู้ดูแล และไม่ต้องเข้าถึง repo ต้นฉบับ หากสำเร็จ ผู้โจมตีปล่อยเวอร์ชันที่มีโค้ดอันตราย เช่น สคริปต์ postinstall ภายใต้ชื่อแพ็กเกจที่ถูกต้อง แล้วโค้ดจะไปถึงผู้ใช้ผ่านการติดตั้ง dependency ตามปกติ Bloom เน้นว่านี่คือเส้นทางยึดแพ็กเกจ ไม่ใช่แคมเปญมัลแวร์ที่เกิดขึ้นจริง และไม่ได้เปิดเผยรายชื่อแพ็กเกจที่ได้รับผลกระทบ GitHub แก้ไขแล้วเมื่อ 2 ตุลาคม 2569 หลังได้รับรายงานกว่า 120 วัน

รายละเอียดข่าว

เว็บไซต์ Cyber Security News รายงานเมื่อวันที่ 13 ตุลาคม 2569 ว่า Trusted Publishing ถูกออกแบบมาเพื่อให้การปล่อยแพ็กเกจ npm ปลอดภัยขึ้น แทนที่จะเก็บโทเคน npm อายุยาวไว้ใน GitHub Actions หรือระบบ CI/CD อื่น ผู้ดูแลเชื่อมแพ็กเกจกับ repo และ workflow ผ่าน OpenID Connect (OIDC) เมื่อถึงเวลาปล่อยเวอร์ชัน workflow จะขอโทเคนยืนยันตัวตนอายุสั้นแล้วแลกเป็นสิทธิ์ปล่อยแพ็กเกจจาก npm ช่องโหว่ SlugFest แสดงให้เห็นว่าแม้ข้อมูลรับรองจะมีอายุสั้น แต่หากการผูกตัวตนเบื้องหลังอิงชื่อที่นำกลับมาใช้ใหม่ได้ ก็ยังถูกนำไปใช้ในทางที่ผิดได้

SlugFest flaw in npm Trusted Publishing exposed over 1,500 packages to takeover

Bloom รายงานปัญหาผ่านโครงการ HackerOne ของ GitHub เมื่อ 6 มิถุนายน 2569 ได้รับการยืนยันเมื่อ 9 มิถุนายน และ GitHub แก้ไขเมื่อ 2 ตุลาคม การแก้ไขมี 2 ส่วน คือการตั้งค่า Trusted Publishing ที่สร้างใหม่แต่ยังไม่ผ่านการยืนยันจะหมดอายุใน 48 ชั่วโมง โดยการตั้งค่าจะถือว่ายืนยันแล้วเมื่อปล่อยแพ็กเกจสำเร็จครั้งแรก ซึ่งจะผูกกับรหัสถาวรของ repo และ GitHub บล็อกโทเคน Trusted Publishing ที่ถูกเรียกจากเหตุการณ์ issue_comment เพิ่มจากที่บล็อก pull_request_target อยู่แล้ว

วิธีการโจมตี

ขั้นตอนที่ Bloom อธิบายมีดังนี้

  1. ผู้โจมตีหาแพ็กเกจที่ตั้งค่า Trusted Publishing ชี้ไปยังชื่อ repo GitHub ที่ไม่มีเจ้าของแล้ว และการตั้งค่ายังไม่เคยปล่อยแพ็กเกจสำเร็จครั้งแรก
  2. จดชื่อเจ้าของหรือ repo นั้นบน GitHub แล้วสร้าง repo ใหม่ในชื่อเดิม
  3. เพิ่ม workflow ที่ใช้ชื่อไฟล์ตรงกับที่ตั้งค่าไว้ใน npm แล้วขอโทเคน OIDC
  4. นำโทเคนไปแลกกับ npm ซึ่งเทียบชื่อ repo แล้วถือว่าตรงกัน จึงออกสิทธิ์ปล่อยแพ็กเกจให้
  5. ปล่อยเวอร์ชันใหม่ที่มีโค้ดอันตรายภายใต้ชื่อแพ็กเกจที่ถูกต้อง

Bloom ยังชี้ว่า AI coding agent ทำให้ความเสี่ยงสูงขึ้น เครื่องผู้ใช้ทั่วไปติดตั้งแพ็กเกจผ่าน AI agent ราว 70 แพ็กเกจต่อเดือน และเครื่องนักพัฒนาเกือบ 350 แพ็กเกจต่อเดือน ไม่รวม dependency ที่ถูกดึงตามมา agent มักเลือกเวอร์ชันล่าสุดและรันสคริปต์ช่วงติดตั้งโดยแทบไม่มีคนตรวจ หากแพ็กเกจถูกแทนที่ด้วยเวอร์ชันอันตราย agent จะติดตั้งให้เองระหว่างทำงานปกติ

ผลกระทบ

ช่องโหว่นี้กระทบแนวคิดสำคัญของความปลอดภัยห่วงโซ่อุปทานซอฟต์แวร์ เพราะ Trusted Publishing และ provenance ถูกมองว่าเป็นทางแก้ปัญหาโทเคนรั่วที่เป็นต้นเหตุของการยึดแพ็กเกจ npm หลายครั้งที่ผ่านมา SlugFest แสดงว่าเส้นทางปล่อยแพ็กเกจที่ดูน่าเชื่อถือก็ถูกยึดได้หากการผูกตัวตนไม่รัดกุม และ provenance ที่ถูกต้องบอกได้เพียงว่าแพ็กเกจถูกสร้างที่ไหน ไม่ได้พิสูจน์ว่า repo บัญชี หรือสภาพแวดล้อมการ build นั้นปลอดภัย

แม้ GitHub แก้ไขแล้ว แต่การตั้งค่าที่ผูกกับ repo ซึ่งถูกเปลี่ยนชื่อหรือย้ายไปแล้วยังควรตรวจสอบ และเนื่องจาก Bloom ไม่เปิดเผยรายชื่อแพ็กเกจ ผู้ใช้ปลายทางจึงตรวจเองไม่ได้ว่า dependency ของตนเคยอยู่ในกลุ่มเสี่ยงหรือไม่ จนถึงขณะนี้ไม่มีรายงานว่ามีการยึดแพ็กเกจผ่านช่องโหว่นี้จริง

คำแนะนำ

  • ผู้ดูแลแพ็กเกจ npm ตรวจการตั้งค่า Trusted Publisher ทุกรายการว่า repo GitHub ที่ผูกไว้ยังอยู่ภายใต้การควบคุมของตน และลบแล้วสร้างการผูกใหม่ทุกครั้งที่ repo ถูกเปลี่ยนชื่อ ลบ หรือย้าย
  • การตั้งค่าใหม่ต้องปล่อยแพ็กเกจสำเร็จภายใน 2 วัน มิฉะนั้นจะหมดอายุ
  • ย้าย workflow ที่ปล่อยแพ็กเกจไปใช้เหตุการณ์ที่ปลอดภัยกว่า เช่น push, release หรือ workflow_dispatch แทน issue_comment หรือ pull_request_target
  • เฝ้าระวังการปล่อยเวอร์ชันที่ไม่ได้คาดไว้ของแพ็กเกจตนเอง
  • ฝั่งผู้ใช้ ปักหมุดเวอร์ชัน dependency ใช้ lockfile พิจารณาปิดสคริปต์ช่วงติดตั้ง (--ignore-scripts) ในจุดที่ทำได้ และตรวจ provenance โดยไม่ถือว่าเป็นหลักประกันความปลอดภัย
  • จำกัดสิทธิ์ของ AI coding agent ในการติดตั้งแพ็กเกจและรันสคริปต์ หรือให้ผ่านรีจิสทรีภายในที่มีการกรอง

Indicators of Compromise (IoCs)

Bloom ไม่ได้เผยแพร่ hash โดเมน หรือ IP เนื่องจากไม่ใช่แคมเปญมัลแวร์ และงดเปิดเผยรายชื่อแพ็กเกจที่ได้รับผลกระทบเพื่อป้องกันการโจมตีก่อนผู้ดูแลตรวจสอบ เงื่อนไขที่ใช้ตรวจคือ การตั้งค่า Trusted Publishing ที่ผูกกับชื่อ repo GitHub ซึ่งถูกนำกลับมาจดใหม่ได้ และ workflow ที่ใช้ชื่อไฟล์ตรงกับที่ตั้งค่าไว้

แหล่งอ้างอิง